diff --git a/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx b/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx index 9efc261c8..d5b952246 100644 --- a/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx +++ b/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx @@ -1285,7 +1285,6 @@ const SKELETON_NAGBAR_ROW_SHAPES: Record = { [NagbarType.VISIONARY_MFA]: {tone: SkeletonNagbarTone.BRAND, hasActions: true}, [NagbarType.VOICE_SESSION_RESTORE]: {tone: SkeletonNagbarTone.VOICE, hasActions: true}, [NagbarType.TERMS_ACCEPTANCE]: {tone: SkeletonNagbarTone.LEGAL, hasActions: true}, - [NagbarType.LINUX_INPUT_ACCESS]: {tone: SkeletonNagbarTone.BRAND, hasActions: true}, [NagbarType.SOFTWARE_ENCODER]: {tone: SkeletonNagbarTone.ENCODER, hasActions: true}, [NagbarType.STREAMER_MODE]: {tone: SkeletonNagbarTone.STREAMER, hasActions: true}, [NagbarType.DOMAIN_MOVED]: {tone: SkeletonNagbarTone.BRAND, hasActions: true}, diff --git a/fluxer_app/src/features/app/components/layout/UserArea.tsx b/fluxer_app/src/features/app/components/layout/UserArea.tsx index ce2f31f49..1c32a18b1 100644 --- a/fluxer_app/src/features/app/components/layout/UserArea.tsx +++ b/fluxer_app/src/features/app/components/layout/UserArea.tsx @@ -13,7 +13,7 @@ import {useContextMenuHoverState} from '@app/features/app/hooks/useContextMenuHo import * as VoiceStateCommands from '@app/features/devtools/commands/VoiceStateCommands'; import DeveloperOptions from '@app/features/devtools/state/DeveloperOptions'; import Keybind from '@app/features/input/state/InputKeybind'; -import {formatKeyCombo} from '@app/features/input/utils/KeybindUtils'; +import {getPushToTalkHoldLabel} from '@app/features/input/utils/PushToTalkHint'; import Presence from '@app/features/presence/state/Presence'; import {SettingsContextMenu} from '@app/features/ui/action_menu/SettingsContextMenu'; import * as ContextMenuCommands from '@app/features/ui/commands/ContextMenuCommands'; @@ -205,8 +205,6 @@ const UserAreaInner = observer( }; }, [hasVoiceConnection]); const wrapperClassName = styles.userAreaInnerWrapper; - const pushToTalkCombo = Keybind.getByAction('voice_push_to_talk').combo; - const pushToTalkHint = formatKeyCombo(i18n, pushToTalkCombo); const isPushToTalkEffective = Keybind.isPushToTalkEffective(); const microphoneState = selectUserAreaMicrophoneState({ effectiveAudioMuted: isMuted, @@ -226,7 +224,7 @@ const UserAreaInner = observer( if (isGuildDeafened) return getVoiceDeafenedByModeratorsStatusLabel(i18n, true); if (isGuildMuted) return i18n._(VOICE_MUTED_BY_MODERATORS_DESCRIPTOR); if (isPermissionMuted || muteReason === 'permission') return i18n._(VOICE_NO_SPEAK_PERMISSION_DESCRIPTOR); - if (isPushToTalkEffective) return i18n._(PUSH_TO_TALK_IS_ON_HOLD_TO_SPEAK_DESCRIPTOR, {pushToTalkHint}); + if (isPushToTalkEffective) return getPushToTalkHoldLabel(i18n, PUSH_TO_TALK_IS_ON_HOLD_TO_SPEAK_DESCRIPTOR); if (effectiveMuted) return i18n._(UNMUTE_MICROPHONE_DESCRIPTOR); return i18n._(MUTE_MICROPHONE_DESCRIPTOR); })(); @@ -234,7 +232,7 @@ const UserAreaInner = observer( if (isGuildDeafened) return getVoiceDeafenedByModeratorsStatusLabel(i18n, true); if (isGuildMuted) return i18n._(VOICE_MUTED_BY_MODERATORS_DESCRIPTOR); if (isPermissionMuted || muteReason === 'permission') return i18n._(VOICE_NO_SPEAK_PERMISSION_DESCRIPTOR); - if (isPushToTalkEffective) return i18n._(PUSH_TO_TALK_IS_ON_HOLD_TO_SPEAK_DESCRIPTOR, {pushToTalkHint}); + if (isPushToTalkEffective) return getPushToTalkHoldLabel(i18n, PUSH_TO_TALK_IS_ON_HOLD_TO_SPEAK_DESCRIPTOR); if (effectiveMuted) return i18n._(UNMUTE_MICROPHONE_DESCRIPTOR); return i18n._(MUTE_MICROPHONE_DESCRIPTOR); })(); diff --git a/fluxer_app/src/features/app/components/layout/app_layout/AppLayoutHooks.ts b/fluxer_app/src/features/app/components/layout/app_layout/AppLayoutHooks.ts index c68165ea3..6c019b412 100644 --- a/fluxer_app/src/features/app/components/layout/app_layout/AppLayoutHooks.ts +++ b/fluxer_app/src/features/app/components/layout/app_layout/AppLayoutHooks.ts @@ -18,7 +18,6 @@ import Channels from '@app/features/channel/state/Channels'; import DeveloperOptions from '@app/features/devtools/state/DeveloperOptions'; import GatewayConnection from '@app/features/gateway/transport/GatewayConnection'; import * as NotificationUtils from '@app/features/notification/utils/NotificationUtils'; -import NativePermission from '@app/features/permissions/system/state/NativePermission'; import {resolvePriceAnnouncementCampaign} from '@app/features/premium/config/PriceAnnouncementCampaign'; import PremiumState from '@app/features/premium/state/PremiumState'; import {getPremiumGraceEndDate} from '@app/features/premium/utils/PremiumGrace'; @@ -257,7 +256,6 @@ export const useNagbarConditions = (): NagbarConditions => { startupVoiceSessionRestoreSnapshotKey && startupVoiceSessionRestoreSnapshotKey === voiceSessionRestoreSnapshotKey, ); })(); - const canShowLinuxInputAccess = NativePermission.shouldShowLinuxInputAccessNagbar; const canShowSoftwareEncoder = SoftwareEncoderWarning.showWarning; const canShowStreamerMode = StreamerMode.shouldShowNagbar; const canShowDesktopUpdateReady = Updater.shouldShowUpdateReadyNagbar; @@ -328,7 +326,6 @@ export const useNagbarConditions = (): NagbarConditions => { canShowVisionaryMfa, canShowVoiceSessionRestore, needsTermsAcceptance, - canShowLinuxInputAccess, canShowSoftwareEncoder, canShowStreamerMode, canShowDesktopUpdateReady, @@ -446,12 +443,6 @@ export const useActiveNagbars = (conditions: NagbarConditions): Array = observer(({nagbar data-flx="app.app-layout.nagbar-container.voice-session-restore-nagbar" /> ); - case NagbarType.LINUX_INPUT_ACCESS: - return ( - - ); case NagbarType.SOFTWARE_ENCODER: return ( { - const {i18n} = useLingui(); - const [submitting, setSubmitting] = useState(false); - const needsRelogin = NativePermission.linuxInputAccessGrantNeedsRelogin; - const hasError = NativePermission.linuxInputAccessGrantError !== null; - const shortcutsSettingsMenuName = getUserSettingsTabLabel(i18n, 'keybinds'); - const handleEnable = async () => { - setSubmitting(true); - try { - await NativePermission.grantLinuxInputAccess(); - await KeybindManager.reapplyGlobalShortcuts(); - } finally { - setSubmitting(false); - } - }; - const handleRecheck = async () => { - setSubmitting(true); - try { - await NativePermission.recheckLinuxInputAccess(); - await KeybindManager.reapplyGlobalShortcuts(); - } finally { - setSubmitting(false); - } - }; - const message = needsRelogin - ? i18n._(LINUX_INPUT_ACCESS_RELOGIN_DESCRIPTOR, {productName: PRODUCT_NAME}) - : hasError - ? i18n._(LINUX_INPUT_ACCESS_ERROR_DESCRIPTOR, { - settingsMenuName: shortcutsSettingsMenuName, - productName: PRODUCT_NAME, - }) - : i18n._(LINUX_INPUT_ACCESS_DESCRIPTION_DESCRIPTOR, {productName: PRODUCT_NAME}); - return ( - - - {needsRelogin - ? i18n._(LINUX_INPUT_ACCESS_RECHECK_DESCRIPTOR) - : i18n._(LINUX_INPUT_ACCESS_ENABLE_DESCRIPTOR)} - - } - data-flx="app.app-layout.nagbars.linux-input-access-nagbar.nagbar-content" - /> - - ); -}); diff --git a/fluxer_app/src/features/app/config/I18nDisplayConstants.ts b/fluxer_app/src/features/app/config/I18nDisplayConstants.ts index 99ed3e8b4..1a51e59b8 100644 --- a/fluxer_app/src/features/app/config/I18nDisplayConstants.ts +++ b/fluxer_app/src/features/app/config/I18nDisplayConstants.ts @@ -9,6 +9,8 @@ export {PREMIUM_PRODUCT_FULL_NAME, PREMIUM_PRODUCT_NAME, PRODUCT_NAME}; export const PRODUCT_API_NAME = `${PRODUCT_NAME} API`; export const PRODUCT_HQ_COMMUNITY_NAME = `${PRODUCT_NAME} HQ`; export const CANARY_RELEASE_CHANNEL_NAME = `${PRODUCT_NAME} Canary`; +export const DESKTOP_ENTRY_NAME = 'Fluxer'; +export const CANARY_DESKTOP_ENTRY_NAME = 'Fluxer Canary'; export const VOICE_PROVIDER_NAME = 'LiveKit'; export const PAYMENT_PROVIDER_NAME = 'Stripe'; export const BLUESKY_PROVIDER_NAME = 'Bluesky'; diff --git a/fluxer_app/src/features/app/keybindings/KeybindManager.ts b/fluxer_app/src/features/app/keybindings/KeybindManager.ts index 6715f3ad5..60b14ebb5 100644 --- a/fluxer_app/src/features/app/keybindings/KeybindManager.ts +++ b/fluxer_app/src/features/app/keybindings/KeybindManager.ts @@ -33,9 +33,11 @@ import { shouldSuppressLocalShortcutForModalFocus, shouldSuppressShortcutForFullscreenMedia, } from '@app/features/app/keybindings/utils/ModalSuppression'; +import {reactToPushToTalkModeChanges} from '@app/features/app/keybindings/utils/PushToTalkModeReconcile'; import { buildCustomRuntimeKeybinds, buildDefaultRuntimeKeybinds, + gamepadSourceIdForKeybind, HOLD_ACTIONS, HOLD_ACTIONS_FOR_PTT_MODE, HOLD_ACTIONS_FOR_VOICE_ACTIVITY_MODE, @@ -43,6 +45,7 @@ import { hookShortcutIdForAction, hookShortcutIdForKeybind, type RuntimeKeybind, + sourceIdForKeybind, } from '@app/features/app/keybindings/utils/RuntimeKeybinds'; import {LOCAL_SHORTCUT_ACTION_PRIORITY} from '@app/features/app/keybindings/utils/ShortcutPriority'; import Authentication from '@app/features/auth/state/Authentication'; @@ -51,8 +54,10 @@ import Channels from '@app/features/channel/state/Channels'; import type {Guild} from '@app/features/guild/models/Guild'; import GuildList from '@app/features/guild/state/GuildList'; import Guilds from '@app/features/guild/state/Guilds'; +import GlobalShortcuts, {getGlobalShortcutsApi} from '@app/features/input/state/GlobalShortcuts'; import Keybind, { type CustomKeybindEntry, + isKeybindCommand, type KeybindCommand, type KeybindConfig, type KeyCombo, @@ -68,9 +73,7 @@ import * as NavigationCommands from '@app/features/navigation/commands/Navigatio import Navigation from '@app/features/navigation/state/Navigation'; import SelectedChannel from '@app/features/navigation/state/SelectedChannel'; import SelectedGuild from '@app/features/navigation/state/SelectedGuild'; -import NativePermission, { - type LinuxInputAccessNagbarReason, -} from '@app/features/permissions/system/state/NativePermission'; +import NativePermission from '@app/features/permissions/system/state/NativePermission'; import {ensureMacPermission} from '@app/features/permissions/system/utils/MacPermissionGate'; import {Logger} from '@app/features/platform/utils/AppLogger'; import ReadStates from '@app/features/read_state/state/ReadStates'; @@ -87,11 +90,19 @@ import CompactVoiceCallHeight, { } from '@app/features/voice/state/CompactVoiceCallHeight'; import MockIncomingCall from '@app/features/voice/state/MockIncomingCall'; import VoiceCallFullscreen from '@app/features/voice/state/VoiceCallFullscreen'; +import type { + GlobalShortcutActionDefinition, + GlobalShortcutBinding, + GlobalShortcutCombo, + GlobalShortcutEvent, + GlobalShortcutsApi, + GlobalShortcutsSyncPayload, +} from '@app/types/electron.d'; import {ME} from '@fluxer/constants/src/AppConstants'; import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants'; import type {I18n} from '@lingui/core'; import CombokeysImport from 'combokeys'; -import {autorun, reaction} from 'mobx'; +import {autorun, compareStructural, reaction} from 'mobx'; const normalizeKeyboardShortcutKey = (key: string): string => { if (key === ' ') return 'space'; @@ -108,6 +119,86 @@ export { const ROUTE_ALLOWED_ACTIONS = new Set(['system_open_theme_studio_popout']); const GAMEPAD_POLL_INTERVAL_MS = 50; +const PORTAL_SOURCE_ID_PREFIX = 'portal:'; + +interface HoldBindingEntry { + action: HoldAction; + sourceId: string; + gamepadSourceId: string; + combo: KeyCombo; +} + +interface GlobalHoldRouting { + hooksActive: boolean; + supportsMouseButtons: boolean; + supportsModifierOnly: boolean; +} + +function isHoldAction(action: string): action is HoldAction { + return HOLD_ACTIONS.includes(action as HoldAction); +} + +function comboHasModifiers(combo: GlobalShortcutCombo): boolean { + return combo.ctrl || combo.alt || combo.shift || combo.meta; +} + +function toGlobalShortcutCombo(combo: KeyCombo): GlobalShortcutCombo | null { + if (combo.gamepadButton != null) return null; + const hasKey = (combo.key ?? '') !== '' || (combo.code ?? '') !== ''; + if (!hasKey && combo.mouseButton == null) return null; + const isMacOS = isNativeMacOS(); + const result: GlobalShortcutCombo = { + key: combo.key ?? '', + ctrl: Boolean(combo.ctrl) || (!isMacOS && Boolean(combo.ctrlOrMeta)), + alt: Boolean(combo.alt), + shift: Boolean(combo.shift), + meta: Boolean(combo.meta) || (isMacOS && Boolean(combo.ctrlOrMeta)), + }; + if (combo.code) result.code = combo.code; + if (combo.mouseButton != null) result.mouseButton = combo.mouseButton; + if (combo.modifierOnly) result.modifierOnly = true; + if (combo.modifierOnly && combo.bothSides) result.bothSides = true; + return result; +} + +function createHoldBindingRuntime(entry: HoldBindingEntry): HoldBindingRuntime { + const {combo} = entry; + return { + action: entry.action, + sourceId: entry.sourceId, + gamepadSourceId: entry.gamepadSourceId, + combo, + keycode: null, + keyName: null, + physicalKeyName: null, + mouseButton: combo.mouseButton ?? null, + gamepadButton: combo.gamepadButton ?? null, + isModifierOnly: Boolean(combo.modifierOnly), + ctrlOrMeta: Boolean(combo.ctrlOrMeta), + requireBothSides: Boolean(combo.modifierOnly && combo.bothSides), + modifiers: { + ctrl: Boolean(combo.ctrl), + alt: Boolean(combo.alt), + shift: Boolean(combo.shift), + meta: Boolean(combo.meta), + }, + routing: null, + pressedKeycodes: new Set(), + localPressedCodes: new Set(), + localActiveCode: null, + localMouseActive: false, + globalMouseActive: false, + localKeyDown: null, + localKeyUp: null, + localMouseDown: null, + localMouseUp: null, + gamepadHeld: false, + }; +} + +function legacyHoldSourceId(binding: HoldBindingRuntime): string { + return `legacy:${hookShortcutIdForAction(binding.action, binding.combo)}`; +} class KeybindManager { private handlers = new Map(); @@ -117,8 +208,11 @@ class KeybindManager { private disposers: Array<() => void> = []; private combokeys: CombokeysInstance | null = null; private inputMonitoringHookStatus: 'unknown' | 'granted' | 'denied' = 'unknown'; - pttReleaseTimer: NodeJS.Timeout | null = null; private registeredGlobalHookShortcutIds = new Set(); + private globalShortcutsEventUnsubscribe: (() => void) | null = null; + private syncedGlobalSourceIds = new Set(); + private syncedSourceIdByHookId = new Map(); + private syncedGlobalActions: Array = []; private globalKeyHookUnsubscribes: Array<() => void> = []; private globalKeybindTriggeredUnsubscribe: (() => void) | null = null; private globalKeyHookStarted = false; @@ -420,26 +514,29 @@ class KeybindManager { () => this.refreshLocalShortcuts(), ), ); - this.disposers.push( - autorun(() => { - const desired = this.computeDesiredGlobalHookShortcuts(); - void this.enqueueInputSync(() => this.applyGlobalShortcuts(desired)); - }), - ); + GlobalShortcuts.attach(); + const globalShortcutsApi = getGlobalShortcutsApi(); + if (globalShortcutsApi) { + this.attachGlobalShortcuts(globalShortcutsApi); + } else { + this.disposers.push( + autorun(() => { + const desired = this.computeDesiredGlobalHookShortcuts(); + void this.enqueueInputSync(() => this.applyGlobalShortcuts(desired)); + }), + ); + } + this.disposers.push(reactToPushToTalkModeChanges((options) => MediaEngine.handlePushToTalkModeChange(options))); this.disposers.push( reaction( - () => Keybind.transmitMode, - () => { - MediaEngine.handlePushToTalkModeChange(); + () => ({entries: this.buildHoldBindingEntries(), routing: this.getGlobalHoldRouting()}), + ({entries, routing}) => { + const bindings = entries.map(createHoldBindingRuntime); + void this.enqueueInputSync(() => this.applyHoldBindings(bindings, routing)); }, + {equals: compareStructural, fireImmediately: true}, ), ); - this.disposers.push( - autorun(() => { - const bindings = this.buildHoldBindings(); - void this.enqueueInputSync(() => this.applyHoldBindings(bindings)); - }), - ); this.disposers.push( autorun(() => { const pathname = Navigation.pathname; @@ -461,79 +558,74 @@ class KeybindManager { return this.inputSyncQueue; } - private buildHoldBindings(): Array { - const bindings: Array = []; + private buildHoldBindingEntries(): Array { + const entries: Array = []; const inPttMode = Keybind.isPushToTalkEffective(); const eligibleActions = inPttMode ? HOLD_ACTIONS_FOR_PTT_MODE : HOLD_ACTIONS_FOR_VOICE_ACTIVITY_MODE; const customs: ReadonlyArray = Keybind.getCustomKeybinds(); - const pushBinding = (action: HoldAction, combo: KeyCombo): void => { - if (!this.isActionAllowedForCurrentView(action)) return; - const hasBinding = !!(combo.key || combo.code || combo.gamepadButton != null || combo.mouseButton != null); - if (!hasBinding) return; - bindings.push({ - action, - combo, - keycode: null, - keyName: null, - physicalKeyName: null, - mouseButton: combo.mouseButton ?? null, - gamepadButton: combo.gamepadButton ?? null, - isModifierOnly: Boolean(combo.modifierOnly), - ctrlOrMeta: Boolean(combo.ctrlOrMeta), - requireBothSides: Boolean(combo.modifierOnly && combo.bothSides), - modifiers: { - ctrl: Boolean(combo.ctrl), - alt: Boolean(combo.alt), - shift: Boolean(combo.shift), - meta: Boolean(combo.meta), - }, - routing: null, - pressedKeycodes: new Set(), - localPressedCodes: new Set(), - localActiveCode: null, - localMouseActive: false, - globalMouseActive: false, - localKeyDown: null, - localKeyUp: null, - localMouseDown: null, - localMouseUp: null, - gamepadHeld: false, - }); - }; for (const action of eligibleActions) { for (const entry of customs) { if (!entry.enabled) continue; if (entry.action !== action) continue; - pushBinding(action, entry.combo); + const combo = entry.combo; + const hasBinding = !!(combo.key || combo.code || combo.gamepadButton != null || combo.mouseButton != null); + if (!hasBinding) continue; + const keybind = {id: entry.id, action}; + entries.push({ + action, + sourceId: sourceIdForKeybind(keybind), + gamepadSourceId: gamepadSourceIdForKeybind(keybind), + combo: {...combo}, + }); } } - return bindings; + return entries; } - private async applyHoldBindings(bindings: Array): Promise { + private getGlobalHoldRouting(): GlobalHoldRouting | null { + if (!getGlobalShortcutsApi()) return null; + const status = GlobalShortcuts.status; + return { + hooksActive: GlobalShortcuts.hooksActive && GlobalShortcuts.backend !== 'portal', + supportsMouseButtons: status?.supportsMouseButtons === true, + supportsModifierOnly: status?.supportsModifierOnly === true, + }; + } + + private canRouteHoldBindingGlobally(binding: HoldBindingRuntime, routing: GlobalHoldRouting): boolean { + if (!routing.hooksActive) return false; + if (binding.mouseButton !== null) return routing.supportsMouseButtons; + if (binding.isModifierOnly) return routing.supportsModifierOnly; + return true; + } + + private rebuildHoldBindings(): void { + const bindings = this.buildHoldBindingEntries().map(createHoldBindingRuntime); + const routing = this.getGlobalHoldRouting(); + void this.enqueueInputSync(() => this.applyHoldBindings(bindings, routing)); + } + + private async applyHoldBindings( + bindings: Array, + routing: GlobalHoldRouting | null, + ): Promise { this.detachLocalHoldListener(); this.releaseGlobalHoldBindings(); this.releaseGamepadHoldBindings(); this.holdBindings = bindings; if (bindings.length === 0 || this.suspended || !this.initialized) { - this.maybeStopGlobalKeyHook(); + if (routing === null) this.maybeStopGlobalKeyHook(); this.refreshGamepadPolling(); return; } - const electronApi = getElectronAPI(); - const globalHookAvailable = !!electronApi?.globalKeyHookStart; - const wantsGlobal = bindings.some((b) => { - const hasGlobalRoutable = !!(b.combo.key || b.combo.code || b.mouseButton != null); - return hasGlobalRoutable && (b.combo.global ?? false) && globalHookAvailable; - }); - let globalReady = false; - if (wantsGlobal) { - globalReady = await this.startGlobalKeyHook('push-to-talk'); - } + const legacyGlobalReady = routing === null && (await this.startLegacyGlobalHoldHook(bindings)); let needsLocal = false; for (const binding of bindings) { const hasGlobalRoutable = !!(binding.combo.key || binding.combo.code || binding.mouseButton != null); - if (globalReady && hasGlobalRoutable && (binding.combo.global ?? false)) { + const wantsGlobal = hasGlobalRoutable && (binding.combo.global ?? false); + if (wantsGlobal && routing !== null && this.canRouteHoldBindingGlobally(binding, routing)) { + binding.routing = 'global'; + } else if (wantsGlobal && legacyGlobalReady) { binding.keycode = jsKeyToUiohookKeycode(binding.combo.code ?? binding.combo.key); binding.keyName = keyNameForGlobalHook(binding.combo); binding.physicalKeyName = physicalKeyNameForGlobalHook(binding.combo); @@ -545,13 +637,158 @@ class KeybindManager { binding.routing = null; } } - this.maybeStopGlobalKeyHook(); + if (routing === null) this.maybeStopGlobalKeyHook(); if (needsLocal) { this.attachLocalHoldListener(); } this.refreshGamepadPolling(); } + private async startLegacyGlobalHoldHook(bindings: ReadonlyArray): Promise { + if (!getElectronAPI()?.globalKeyHookStart) return false; + const wantsGlobal = bindings.some((binding) => { + const hasGlobalRoutable = !!(binding.combo.key || binding.combo.code || binding.mouseButton != null); + return hasGlobalRoutable && (binding.combo.global ?? false); + }); + if (!wantsGlobal) return false; + return this.startGlobalKeyHook(); + } + + private attachGlobalShortcuts(api: GlobalShortcutsApi): void { + this.globalShortcutsEventUnsubscribe = api.onEvent((event) => this.handleGlobalShortcutEvent(event)); + void this.enqueueInputSync(() => api.setPaused(this.suspended)); + this.disposers.push( + reaction( + () => this.buildGlobalShortcutsSyncPayload(), + (payload) => { + void this.enqueueInputSync(() => this.syncGlobalShortcuts(api, payload)); + }, + {equals: compareStructural, fireImmediately: true}, + ), + reaction( + () => this.canDedupeGlobalPresses(), + () => this.activeGlobalShortcutPressIds.clear(), + ), + ); + } + + private detachGlobalShortcuts(api: GlobalShortcutsApi): void { + this.globalShortcutsEventUnsubscribe?.(); + this.globalShortcutsEventUnsubscribe = null; + const payload: GlobalShortcutsSyncPayload = {bindings: [], actions: this.syncedGlobalActions}; + this.syncedGlobalSourceIds = new Set(); + this.syncedSourceIdByHookId = new Map(); + void this.enqueueInputSync(() => api.sync(payload)); + const pushToTalkEngaged = Keybind.pushToTalkHeld; + const pushToMuteEngaged = Keybind.pushToMuteHeld; + Keybind.resetPushToTalkState(); + Keybind.resetPushToMuteState(); + if (pushToTalkEngaged) MediaEngine.applyPushToTalkHold(false); + if (pushToMuteEngaged) MediaEngine.applyPushToMuteHold(false); + } + + private buildGlobalShortcutsSyncPayload(): GlobalShortcutsSyncPayload { + const bindings: Array = []; + const seenHookIds = new Set(); + for (const entry of this.resolvedKeybinds) { + if (!entry.allowGlobal || !(entry.combo.global ?? false)) continue; + const combo = toGlobalShortcutCombo(entry.combo); + if (!combo) continue; + const hookId = hookShortcutIdForKeybind(entry); + if (hookId === null || seenHookIds.has(hookId)) continue; + seenHookIds.add(hookId); + bindings.push({ + sourceId: sourceIdForKeybind(entry), + action: entry.action, + combo, + }); + } + const actions: Array = []; + for (const config of Keybind.getDefaults()) { + if (!config.allowGlobal) continue; + const hold = isHoldAction(config.action); + const preferred = bindings.find( + (binding) => + binding.action === config.action && + binding.combo.mouseButton === undefined && + !(hold && comboHasModifiers(binding.combo)), + ); + actions.push({ + action: config.action, + description: config.label, + preferredCombo: preferred ? preferred.combo : null, + }); + } + return {bindings, actions}; + } + + private async syncGlobalShortcuts(api: GlobalShortcutsApi, payload: GlobalShortcutsSyncPayload): Promise { + if (!this.initialized) return; + if (payload.bindings.length > 0) { + await this.checkInputMonitoringPermission(); + } + const sourceIds = new Set(payload.bindings.map((binding) => binding.sourceId)); + const sourceIdByHookId = new Map(); + for (const entry of this.resolvedKeybinds) { + const sourceId = sourceIdForKeybind(entry); + if (!sourceIds.has(sourceId)) continue; + const hookId = hookShortcutIdForKeybind(entry); + if (hookId !== null) sourceIdByHookId.set(hookId, sourceId); + } + this.syncedGlobalSourceIds = sourceIds; + this.syncedSourceIdByHookId = sourceIdByHookId; + this.syncedGlobalActions = payload.actions; + await api.sync(payload); + } + + private isSyncedGlobalShortcutSource(event: GlobalShortcutEvent): boolean { + if (event.sourceId.startsWith(PORTAL_SOURCE_ID_PREFIX)) { + return this.syncedGlobalActions.some((definition) => definition.action === event.action); + } + return this.syncedGlobalSourceIds.has(event.sourceId); + } + + private handleGlobalShortcutEvent(event: GlobalShortcutEvent): void { + if (!isKeybindCommand(event.action)) return; + const action = event.action; + const handler = this.handlers.get(action); + if (!handler) return; + const payload = {type: event.phase, source: 'global', sourceId: event.sourceId} as const; + if (isHoldAction(action)) { + if (event.phase === 'press' && (this.suspended || !this.isSyncedGlobalShortcutSource(event))) return; + handler(payload); + return; + } + if (event.phase === 'release') { + if (!this.activeGlobalShortcutPressIds.delete(event.sourceId)) return; + handler(payload); + return; + } + if (!this.isSyncedGlobalShortcutSource(event)) return; + if (this.activeGlobalShortcutPressIds.has(event.sourceId)) return; + if (this.suspended) return; + if (!this.isActionAllowedForCurrentView(action)) return; + if (shouldSuppressShortcutForFullscreenMedia()) return; + if (Keybind.isActionMuted(action)) return; + this.activeGlobalShortcutPressIds.add(event.sourceId); + handler(payload); + } + + private canDedupeGlobalPresses(): boolean { + const backend = GlobalShortcuts.backend; + return GlobalShortcuts.hooksActive && backend !== 'portal' && backend !== 'none'; + } + + private globalPressDedupeId(entry: RuntimeKeybind): string | null { + const hookId = hookShortcutIdForKeybind(entry); + if (getGlobalShortcutsApi()) { + if (!this.canDedupeGlobalPresses() || hookId === null) return null; + return this.syncedSourceIdByHookId.get(hookId) ?? null; + } + if (!(entry.combo.global ?? false)) return null; + return hookId !== null && this.registeredGlobalHookShortcutIds.has(hookId) ? hookId : null; + } + private attachLocalHoldListener(): void { if (this.localHoldListenerAttached) { this.detachLocalHoldListener(); @@ -566,14 +803,14 @@ class KeybindManager { binding.localPressedCodes.add(event.code); const required = this.requiredModifierKeyCount(binding); if (binding.localPressedCodes.size === required) { - this.fireHoldHandler(binding, 'press', 'local'); + this.fireHoldHandler(binding, 'press', 'local', binding.sourceId); } continue; } if (!this.localKeyEventMatchesBinding(binding, event)) continue; if (binding.localActiveCode === event.code) continue; binding.localActiveCode = event.code; - this.fireHoldHandler(binding, 'press', 'local'); + this.fireHoldHandler(binding, 'press', 'local', binding.sourceId); } }; const onKeyUp = (event: KeyboardEvent): void => { @@ -585,13 +822,13 @@ class KeybindManager { const wasAtThreshold = binding.localPressedCodes.size === required; binding.localPressedCodes.delete(event.code); if (wasAtThreshold) { - this.fireHoldHandler(binding, 'release', 'local'); + this.fireHoldHandler(binding, 'release', 'local', binding.sourceId); } continue; } if (binding.localActiveCode !== event.code) continue; binding.localActiveCode = null; - this.fireHoldHandler(binding, 'release', 'local'); + this.fireHoldHandler(binding, 'release', 'local', binding.sourceId); } }; const onMouseDown = (event: MouseEvent): void => { @@ -602,7 +839,7 @@ class KeybindManager { if (!this.matchesModifiers(binding, event)) continue; if (binding.localMouseActive) continue; binding.localMouseActive = true; - this.fireHoldHandler(binding, 'press', 'local'); + this.fireHoldHandler(binding, 'press', 'local', binding.sourceId); } }; const onMouseUp = (event: MouseEvent): void => { @@ -612,7 +849,7 @@ class KeybindManager { if (event.button !== binding.mouseButton) continue; if (!binding.localMouseActive) continue; binding.localMouseActive = false; - this.fireHoldHandler(binding, 'release', 'local'); + this.fireHoldHandler(binding, 'release', 'local', binding.sourceId); } }; for (const binding of this.holdBindings) { @@ -626,19 +863,19 @@ class KeybindManager { if (binding.routing !== 'local') continue; if (binding.localActiveCode !== null) { binding.localActiveCode = null; - this.fireHoldHandler(binding, 'release', 'local'); + this.fireHoldHandler(binding, 'release', 'local', binding.sourceId); } if (binding.localPressedCodes.size > 0) { const required = this.requiredModifierKeyCount(binding); const wasAtThreshold = binding.localPressedCodes.size >= required; binding.localPressedCodes.clear(); if (wasAtThreshold) { - this.fireHoldHandler(binding, 'release', 'local'); + this.fireHoldHandler(binding, 'release', 'local', binding.sourceId); } } if (binding.localMouseActive) { binding.localMouseActive = false; - this.fireHoldHandler(binding, 'release', 'local'); + this.fireHoldHandler(binding, 'release', 'local', binding.sourceId); } } }; @@ -682,19 +919,19 @@ class KeybindManager { for (const binding of this.holdBindings) { if (binding.localActiveCode !== null) { binding.localActiveCode = null; - this.fireHoldHandler(binding, 'release', 'local'); + this.fireHoldHandler(binding, 'release', 'local', binding.sourceId); } if (binding.localPressedCodes.size > 0) { const required = this.requiredModifierKeyCount(binding); const wasAtThreshold = binding.localPressedCodes.size >= required; binding.localPressedCodes.clear(); if (wasAtThreshold) { - this.fireHoldHandler(binding, 'release', 'local'); + this.fireHoldHandler(binding, 'release', 'local', binding.sourceId); } } if (binding.localMouseActive) { binding.localMouseActive = false; - this.fireHoldHandler(binding, 'release', 'local'); + this.fireHoldHandler(binding, 'release', 'local', binding.sourceId); } binding.localKeyDown = null; binding.localKeyUp = null; @@ -810,23 +1047,24 @@ class KeybindManager { if (type === 'press' && shouldSuppressLocalShortcutForModalFocus(binding, event.target ?? null)) continue; const id = hookShortcutIdForKeybind(binding); if (!id) continue; - const isRegisteredGlobalShortcut = this.isHookShortcutRegistered(binding); + const globalPressId = this.globalPressDedupeId(binding); + const sourceId = sourceIdForKeybind(binding); if (type === 'release') { if (!this.activeLocalShortcutPressIds.delete(id)) continue; - if (isRegisteredGlobalShortcut) { - this.activeGlobalShortcutPressIds.delete(id); + if (globalPressId !== null) { + this.activeGlobalShortcutPressIds.delete(globalPressId); } - this.fireShortcutHandler(binding, type, 'local', {shiftKey: event.shiftKey}); + this.fireShortcutHandler(binding, type, 'local', sourceId, {shiftKey: event.shiftKey}); continue; } if (!this.comboModifiersMatch(combo, event)) continue; if (this.activeLocalShortcutPressIds.has(id)) continue; - if (isRegisteredGlobalShortcut) { - if (this.activeGlobalShortcutPressIds.has(id)) continue; - this.activeGlobalShortcutPressIds.add(id); + if (globalPressId !== null) { + if (this.activeGlobalShortcutPressIds.has(globalPressId)) continue; + this.activeGlobalShortcutPressIds.add(globalPressId); } this.activeLocalShortcutPressIds.add(id); - this.fireShortcutHandler(binding, type, 'local', {shiftKey: event.shiftKey}); + this.fireShortcutHandler(binding, type, 'local', sourceId, {shiftKey: event.shiftKey}); } } @@ -848,13 +1086,9 @@ class KeybindManager { for (const shortcut of comboToCombokeysStrings(entry.combo)) { this.activeLocalShortcutPressIds.delete(shortcut); } - const registeredHookShortcutId = hookShortcutIdForKeybind(entry); - if ( - (entry.combo.global ?? false) && - registeredHookShortcutId && - this.registeredGlobalHookShortcutIds.has(registeredHookShortcutId) - ) { - this.activeGlobalShortcutPressIds.delete(registeredHookShortcutId); + const globalPressId = this.globalPressDedupeId(entry); + if (globalPressId !== null) { + this.activeGlobalShortcutPressIds.delete(globalPressId); } } @@ -884,7 +1118,7 @@ class KeybindManager { if (!keyboardEventStartsComboPress(entry.combo, event, {isMacOS})) continue; if (this.activeLocalShortcutPressIds.has(id)) { if ( - this.isHookShortcutRegistered(entry) || + this.globalPressDedupeId(entry) !== null || !keyboardEventCanRecoverStaleMacMetaPress(entry.combo, event, {isMacOS}) ) { continue; @@ -1013,7 +1247,7 @@ class KeybindManager { for (const binding of this.holdBindings) { if (binding.gamepadHeld) { binding.gamepadHeld = false; - this.fireHoldHandler(binding, 'release', 'local'); + this.fireHoldHandler(binding, 'release', 'local', binding.gamepadSourceId); } } } @@ -1021,7 +1255,7 @@ class KeybindManager { private releaseGamepadShortcutStates(): void { for (const {binding, pressed} of this.gamepadShortcutStates.values()) { if (pressed) { - this.fireShortcutHandler(binding, 'release', 'local'); + this.fireShortcutHandler(binding, 'release', 'local', gamepadSourceIdForKeybind(binding)); } } this.gamepadShortcutStates.clear(); @@ -1058,7 +1292,7 @@ class KeybindManager { const pressed = this.isGamepadButtonPressed(pads, target); if (pressed === binding.gamepadHeld) continue; binding.gamepadHeld = pressed; - this.fireHoldHandler(binding, pressed ? 'press' : 'release', 'local'); + this.fireHoldHandler(binding, pressed ? 'press' : 'release', 'local', binding.gamepadSourceId); } for (const binding of this.localGamepadShortcutKeybinds) { if (!this.isActionAllowedForCurrentView(binding.action)) continue; @@ -1069,7 +1303,7 @@ class KeybindManager { const previous = this.gamepadShortcutStates.get(id)?.pressed ?? false; if (pressed === previous) continue; this.gamepadShortcutStates.set(id, {binding, pressed}); - this.fireShortcutHandler(binding, pressed ? 'press' : 'release', 'local'); + this.fireShortcutHandler(binding, pressed ? 'press' : 'release', 'local', gamepadSourceIdForKeybind(binding)); } } @@ -1082,22 +1316,30 @@ class KeybindManager { return false; } - private fireHoldHandler(binding: HoldBindingRuntime, type: 'press' | 'release', source: ShortcutSource): void { + private fireHoldHandler( + binding: HoldBindingRuntime, + type: 'press' | 'release', + source: ShortcutSource, + sourceId: string, + ): void { const handler = this.handlers.get(binding.action); if (!handler) return; if (type === 'press') { if (this.suspended) return; - if (!this.isActionAllowedForCurrentView(binding.action)) return; - if (shouldSuppressShortcutForFullscreenMedia()) return; - if (Keybind.isActionMuted(binding.action)) return; + if (source === 'local') { + if (!this.isActionAllowedForCurrentView(binding.action)) return; + if (shouldSuppressShortcutForFullscreenMedia()) return; + if (Keybind.isActionMuted(binding.action)) return; + } } - handler({type, source}); + handler({type, source, sourceId}); } private fireShortcutHandler( binding: RuntimeKeybind, type: 'press' | 'release', source: ShortcutSource, + sourceId: string, options: {shiftKey?: boolean} = {}, ): void { if (this.suspended) return; @@ -1106,13 +1348,20 @@ class KeybindManager { if (Keybind.isActionMuted(binding.action)) return; const handler = this.handlers.get(binding.action); if (!handler) return; - handler({type, source, shiftKey: options.shiftKey}); + handler({type, source, sourceId, shiftKey: options.shiftKey}); } async reapplyGlobalShortcuts() { if (!this.initialized) return; - await this.enqueueInputSync(() => this.applyGlobalShortcuts(this.computeDesiredGlobalHookShortcuts())); - await this.enqueueInputSync(() => this.applyHoldBindings(this.buildHoldBindings())); + const globalShortcutsApi = getGlobalShortcutsApi(); + if (globalShortcutsApi) { + const payload = this.buildGlobalShortcutsSyncPayload(); + await this.enqueueInputSync(() => this.syncGlobalShortcuts(globalShortcutsApi, payload)); + } else { + await this.enqueueInputSync(() => this.applyGlobalShortcuts(this.computeDesiredGlobalHookShortcuts())); + } + this.rebuildHoldBindings(); + await this.inputSyncQueue; } destroy() { @@ -1120,6 +1369,11 @@ class KeybindManager { this.initialized = false; this.disposers.forEach((dispose) => dispose()); this.disposers = []; + GlobalShortcuts.detach(); + const globalShortcutsApi = getGlobalShortcutsApi(); + if (globalShortcutsApi) { + this.detachGlobalShortcuts(globalShortcutsApi); + } if (this.globalKeybindTriggeredUnsubscribe) { this.globalKeybindTriggeredUnsubscribe(); this.globalKeybindTriggeredUnsubscribe = null; @@ -1145,7 +1399,7 @@ class KeybindManager { this.combokeys = null; } - async startGlobalKeyHook(reason: LinuxInputAccessNagbarReason = 'global-hotkeys'): Promise { + private async startGlobalKeyHook(): Promise { const electronApi = getElectronAPI(); if (!electronApi?.globalKeyHookStart) return false; if (this.globalKeyHookStarted) return true; @@ -1153,19 +1407,8 @@ class KeybindManager { return false; } const started = await electronApi.globalKeyHookStart(); - if (!started) { - if (NativePermission.isLinuxWaylandDesktop) { - void NativePermission.recheckLinuxInputAccess(); - NativePermission.requestLinuxInputAccessNagbar(reason); - } - return false; - } + if (!started) return false; this.globalKeyHookStarted = true; - if (NativePermission.isLinuxWaylandDesktop) { - void NativePermission.recheckLinuxInputAccess().then((status) => { - if (status === 'blocked') NativePermission.requestLinuxInputAccessNagbar(reason); - }); - } const keyEventUnsub = electronApi.onGlobalKeyEvent?.((event) => { this.handleGlobalKeyEvent( event as { @@ -1222,11 +1465,15 @@ class KeybindManager { if (!this.isActionAllowedForCurrentView(keybind.action)) return; if (event.type === 'keydown' && shouldSuppressShortcutForFullscreenMedia()) return; if (Keybind.isActionMuted(keybind.action)) return; - handler({type: event.type === 'keydown' ? 'press' : 'release', source: 'global'}); + handler({ + type: event.type === 'keydown' ? 'press' : 'release', + source: 'global', + sourceId: `legacy:${event.id}`, + }); }) ?? null; } - stopGlobalKeyHook(): void { + private stopGlobalKeyHook(): void { const electronApi = getElectronAPI(); this.globalKeyHookUnsubscribes.forEach((unsub) => unsub()); this.globalKeyHookUnsubscribes = []; @@ -1251,7 +1498,7 @@ class KeybindManager { if (!keybind || !HOLD_ACTIONS.includes(keybind.action as HoldAction)) continue; const handler = this.handlers.get(keybind.action); if (handler) { - handler({type: 'release', source: 'global'}); + handler({type: 'release', source: 'global', sourceId: `legacy:${id}`}); } } } @@ -1265,11 +1512,11 @@ class KeybindManager { if (binding.routing !== 'global') continue; if (binding.pressedKeycodes.size > 0) { binding.pressedKeycodes.clear(); - this.fireHoldHandler(binding, 'release', 'global'); + this.fireHoldHandler(binding, 'release', 'global', legacyHoldSourceId(binding)); } if (binding.globalMouseActive) { binding.globalMouseActive = false; - this.fireHoldHandler(binding, 'release', 'global'); + this.fireHoldHandler(binding, 'release', 'global', legacyHoldSourceId(binding)); } } } @@ -1316,14 +1563,14 @@ class KeybindManager { if (binding.pressedKeycodes.has(event.keycode)) return; binding.pressedKeycodes.add(event.keycode); if (binding.pressedKeycodes.size === requiredCount) { - this.fireHoldHandler(binding, 'press', 'global'); + this.fireHoldHandler(binding, 'press', 'global', legacyHoldSourceId(binding)); } } else { if (!binding.pressedKeycodes.has(event.keycode)) return; const wasAtThreshold = binding.pressedKeycodes.size === requiredCount; binding.pressedKeycodes.delete(event.keycode); if (wasAtThreshold) { - this.fireHoldHandler(binding, 'release', 'global'); + this.fireHoldHandler(binding, 'release', 'global', legacyHoldSourceId(binding)); } } return; @@ -1332,13 +1579,13 @@ class KeybindManager { if (!this.globalKeyEventMatchesHoldBinding(binding, event)) return; if (event.type === 'keyup') { if (!binding.pressedKeycodes.delete(event.keycode)) return; - this.fireHoldHandler(binding, 'release', 'global'); + this.fireHoldHandler(binding, 'release', 'global', legacyHoldSourceId(binding)); return; } if (!this.globalHoldModifiersMatch(binding, event)) return; if (binding.pressedKeycodes.has(event.keycode)) return; binding.pressedKeycodes.add(event.keycode); - this.fireHoldHandler(binding, 'press', 'global'); + this.fireHoldHandler(binding, 'press', 'global', legacyHoldSourceId(binding)); } private globalKeyEventMatchesHoldBinding( @@ -1422,33 +1669,48 @@ class KeybindManager { if (event.type === 'mouseup') { if (!binding.globalMouseActive) continue; binding.globalMouseActive = false; - this.fireHoldHandler(binding, 'release', 'global'); + this.fireHoldHandler(binding, 'release', 'global', legacyHoldSourceId(binding)); continue; } if (!this.globalHoldModifiersMatch(binding, event)) continue; if (binding.globalMouseActive) continue; binding.globalMouseActive = true; - this.fireHoldHandler(binding, 'press', 'global'); + this.fireHoldHandler(binding, 'press', 'global', legacyHoldSourceId(binding)); } } suspend(): void { + const wasSuspended = this.suspended; this.manualSuspendCount += 1; this.combokeys?.reset(); this.releaseHoldBindingsForSuspension(); this.detachLocalEditableShortcutCaptureListener(); + const globalShortcutsApi = getGlobalShortcutsApi(); + if (globalShortcutsApi) { + if (!wasSuspended && this.initialized) { + void this.enqueueInputSync(() => globalShortcutsApi.setPaused(true)); + } + return; + } void this.enqueueInputSync(() => { this.stopGlobalKeyHook(); }); } resume(): void { + const wasSuspended = this.suspended; this.manualSuspendCount = Math.max(0, this.manualSuspendCount - 1); - if (!this.suspended) { - this.refreshLocalShortcuts(); + if (this.suspended) return; + this.refreshLocalShortcuts(); + const globalShortcutsApi = getGlobalShortcutsApi(); + if (globalShortcutsApi) { + if (wasSuspended && this.initialized) { + void this.enqueueInputSync(() => globalShortcutsApi.setPaused(false)); + } + } else { void this.enqueueInputSync(() => this.applyGlobalShortcuts(this.computeDesiredGlobalHookShortcuts())); - void this.enqueueInputSync(() => this.applyHoldBindings(this.buildHoldBindings())); } + this.rebuildHoldBindings(); } isSuspended(): boolean { @@ -1458,18 +1720,21 @@ class KeybindManager { private setRouteSuspended(value: boolean): void { if (this.routeSuspended === value) return; this.routeSuspended = value; + const globalShortcutsApi = getGlobalShortcutsApi(); if (value) { this.combokeys?.reset(); - this.releaseHoldBindingsForSuspension(); + this.detachLocalHoldListener(); + this.releaseGamepadHoldBindings(); this.detachLocalEditableShortcutCaptureListener(); - void this.enqueueInputSync(() => { - this.stopGlobalKeyHook(); - }); } if (!this.suspended) { this.refreshLocalShortcuts(); - void this.enqueueInputSync(() => this.applyGlobalShortcuts(this.computeDesiredGlobalHookShortcuts())); - void this.enqueueInputSync(() => this.applyHoldBindings(this.buildHoldBindings())); + if (!globalShortcutsApi) { + void this.enqueueInputSync(() => this.applyGlobalShortcuts(this.computeDesiredGlobalHookShortcuts())); + } + if (!this.localHoldListenerAttached && this.holdBindings.some((binding) => binding.routing === 'local')) { + this.attachLocalHoldListener(); + } } } @@ -1536,15 +1801,6 @@ class KeybindManager { return null; } - private isHookShortcutRegistered(binding: RuntimeKeybind): boolean { - return this.isHookShortcutIdRegistered(binding.action, binding.combo); - } - - private isHookShortcutIdRegistered(action: KeybindCommand, combo: KeyCombo): boolean { - const id = hookShortcutIdForAction(action, combo); - return id !== null && this.registeredGlobalHookShortcutIds.has(id); - } - private computeDesiredGlobalHookShortcuts(): Map { const desiredCombos = new Map(); for (const k of this.activeGlobalKeybinds) { @@ -1611,11 +1867,11 @@ class KeybindManager { if (hookShortcutIdForAction(binding.action, binding.combo) !== shortcutId) continue; if (binding.pressedKeycodes.size > 0) { binding.pressedKeycodes.clear(); - this.fireHoldHandler(binding, 'release', 'global'); + this.fireHoldHandler(binding, 'release', 'global', legacyHoldSourceId(binding)); } if (binding.globalMouseActive) { binding.globalMouseActive = false; - this.fireHoldHandler(binding, 'release', 'global'); + this.fireHoldHandler(binding, 'release', 'global', legacyHoldSourceId(binding)); } } } @@ -1737,7 +1993,6 @@ class KeybindManager { const {combo, action} = entry; const requiresKeyboardMode = entry.requiresKeyboardMode ?? false; const requiresMessageFocus = entry.requiresMessageFocus ?? false; - const registeredHookShortcutId = hookShortcutIdForKeybind(entry); const handler = this.handlers.get(action); if (!handler) return false; if (type === 'press' && event.repeat) return false; @@ -1748,11 +2003,6 @@ class KeybindManager { } if (this.shouldIgnoreLocalShortcutEvent(entry, event)) return false; if (Keybind.isActionMuted(action)) return false; - const isRegisteredGlobalShortcut = Boolean( - (combo.global ?? false) && - registeredHookShortcutId && - this.registeredGlobalHookShortcutIds.has(registeredHookShortcutId), - ); if (requiresKeyboardMode && !KeyboardMode.keyboardModeEnabled) { return false; } @@ -1771,17 +2021,19 @@ class KeybindManager { } focusedChannel = MessageFocus.getFocusedChannel(); } - if (isRegisteredGlobalShortcut && registeredHookShortcutId) { + const globalPressId = this.globalPressDedupeId(entry); + if (globalPressId !== null) { if (type === 'press') { - if (this.activeGlobalShortcutPressIds.has(registeredHookShortcutId)) return false; - this.activeGlobalShortcutPressIds.add(registeredHookShortcutId); + if (this.activeGlobalShortcutPressIds.has(globalPressId)) return false; + this.activeGlobalShortcutPressIds.add(globalPressId); } else { - this.activeGlobalShortcutPressIds.delete(registeredHookShortcutId); + this.activeGlobalShortcutPressIds.delete(globalPressId); } } handler({ type, source: 'local', + sourceId: sourceIdForKeybind(entry), context: focusedMessage ? {focusedMessage, focusedChannel} : undefined, shiftKey: event.shiftKey, }); diff --git a/fluxer_app/src/features/app/keybindings/keybind_manager/handlers/defaultHandlers.ts b/fluxer_app/src/features/app/keybindings/keybind_manager/handlers/defaultHandlers.ts index ca367ef56..1d64c9928 100644 --- a/fluxer_app/src/features/app/keybindings/keybind_manager/handlers/defaultHandlers.ts +++ b/fluxer_app/src/features/app/keybindings/keybind_manager/handlers/defaultHandlers.ts @@ -201,57 +201,45 @@ export function registerDefaultKeybindHandlers(host: HandlerHost, i18n: I18n): v if (type !== 'press') return; ThemeStudioCommands.openThemeStudioPopout(); }); - host.register('voice_push_to_talk', ({type}) => { - if (!Keybind.isPushToTalkEffective()) return; - if (type === 'press') { - showPushToTalkDeafenedModalIfNeeded(host, i18n); - if (host.pttReleaseTimer) { - clearTimeout(host.pttReleaseTimer); - host.pttReleaseTimer = null; - } - const shouldUnmute = Keybind.handlePushToTalkPress(); - if (shouldUnmute) { - MediaEngine.applyPushToTalkHold(true); - } - } else { - const shouldMute = Keybind.handlePushToTalkRelease(); - if (shouldMute) { - const delay = Keybind.pushToTalkReleaseDelay; - host.pttReleaseTimer = setTimeout(() => { - host.pttReleaseTimer = null; - MediaEngine.applyPushToTalkHold(false); - }, delay); - } + host.register('voice_push_to_talk', ({type, sourceId}) => { + if (type === 'release') { + Keybind.releaseHoldSource('voice_push_to_talk', sourceId, () => MediaEngine.applyPushToTalkHold(false)); + return; } - }); - host.register('voice_push_to_mute', ({type}) => { - if (Keybind.isPushToTalkEffective()) return; - MediaEngine.applyPushToMuteHold(type === 'press'); - }); - host.register('voice_push_to_talk_priority', ({type}) => { if (!Keybind.isPushToTalkEffective()) return; - if (type === 'press') { - showPushToTalkDeafenedModalIfNeeded(host, i18n); - if (host.pttReleaseTimer) { - clearTimeout(host.pttReleaseTimer); - host.pttReleaseTimer = null; - } - Keybind.handlePushToTalkPress(); - Keybind.setPrioritySpeakerHeld(true); + showPushToTalkDeafenedModalIfNeeded(host, i18n); + if (Keybind.pressHoldSource('voice_push_to_talk', sourceId)) { MediaEngine.applyPushToTalkHold(true); - } else { - Keybind.handlePushToTalkRelease(); - Keybind.setPrioritySpeakerHeld(false); - const delay = Keybind.pushToTalkReleaseDelay; - host.pttReleaseTimer = setTimeout(() => { - host.pttReleaseTimer = null; - MediaEngine.applyPushToTalkHold(false); - }, delay); } }); - host.register('voice_priority_vad', ({type}) => { + host.register('voice_push_to_mute', ({type, sourceId}) => { + if (type === 'release') { + Keybind.releaseHoldSource('voice_push_to_mute', sourceId, () => MediaEngine.applyPushToMuteHold(false)); + return; + } if (Keybind.isPushToTalkEffective()) return; - Keybind.setPrioritySpeakerHeld(type === 'press'); + if (Keybind.pressHoldSource('voice_push_to_mute', sourceId)) { + MediaEngine.applyPushToMuteHold(true); + } + }); + host.register('voice_push_to_talk_priority', ({type, sourceId}) => { + if (type === 'release') { + Keybind.releaseHoldSource('voice_push_to_talk_priority', sourceId, () => MediaEngine.applyPushToTalkHold(false)); + return; + } + if (!Keybind.isPushToTalkEffective()) return; + showPushToTalkDeafenedModalIfNeeded(host, i18n); + if (Keybind.pressHoldSource('voice_push_to_talk_priority', sourceId)) { + MediaEngine.applyPushToTalkHold(true); + } + }); + host.register('voice_priority_vad', ({type, sourceId}) => { + if (type === 'release') { + Keybind.releaseHoldSource('voice_priority_vad', sourceId); + return; + } + if (Keybind.isPushToTalkEffective()) return; + Keybind.pressHoldSource('voice_priority_vad', sourceId); }); host.register('voice_toggle_vad', ({type}) => { if (type !== 'press') return; diff --git a/fluxer_app/src/features/app/keybindings/keybind_manager/handlers/types.ts b/fluxer_app/src/features/app/keybindings/keybind_manager/handlers/types.ts index 023c51310..332703a7a 100644 --- a/fluxer_app/src/features/app/keybindings/keybind_manager/handlers/types.ts +++ b/fluxer_app/src/features/app/keybindings/keybind_manager/handlers/types.ts @@ -10,7 +10,6 @@ export interface HandlerHost { readonly logger: Logger; readonly currentChannelId: string | null; readonly currentGuildId: string | null; - pttReleaseTimer: NodeJS.Timeout | null; navigateToChannel(guildId: string | null, channelId: string): void; navigateToDirectMessages(): void; navigateToLastCommunityChannel(): boolean; diff --git a/fluxer_app/src/features/app/keybindings/keybind_manager/shared.ts b/fluxer_app/src/features/app/keybindings/keybind_manager/shared.ts index 9421c0a6d..3c57e6fb0 100644 --- a/fluxer_app/src/features/app/keybindings/keybind_manager/shared.ts +++ b/fluxer_app/src/features/app/keybindings/keybind_manager/shared.ts @@ -35,6 +35,7 @@ export type ShortcutSource = 'local' | 'global'; export type KeybindHandler = (payload: { type: 'press' | 'release'; source: ShortcutSource; + sourceId: string; context?: { focusedMessage?: Message; focusedChannel?: Channel | null; @@ -44,6 +45,8 @@ export type KeybindHandler = (payload: { export interface HoldBindingRuntime { action: HoldAction; + sourceId: string; + gamepadSourceId: string; combo: KeyCombo; keycode: number | null; keyName: string | null; diff --git a/fluxer_app/src/features/app/keybindings/utils/PushToTalkModeReconcile.test.ts b/fluxer_app/src/features/app/keybindings/utils/PushToTalkModeReconcile.test.ts new file mode 100644 index 000000000..5cc7ca30d --- /dev/null +++ b/fluxer_app/src/features/app/keybindings/utils/PushToTalkModeReconcile.test.ts @@ -0,0 +1,258 @@ +// @vitest-environment happy-dom +// SPDX-License-Identifier: AGPL-3.0-or-later + +import type { + GlobalShortcutsApi, + GlobalShortcutsBackend, + GlobalShortcutsPortalState, + GlobalShortcutsPortalStatus, + GlobalShortcutsStatus, +} from '@app/types/electron.d'; +import {afterAll, beforeAll, describe, expect, it, vi} from 'vitest'; + +vi.mock('@lingui/core/macro', () => ({ + msg: (descriptor: unknown) => descriptor, + t: (descriptor: unknown) => descriptor, +})); + +vi.mock('@lingui/react/macro', () => ({ + Trans: () => null, + useLingui: () => ({i18n: {_: (descriptor: {message?: string}) => descriptor.message ?? ''}}), +})); + +const BOOTSTRAP_ENDPOINT = 'https://primary.test/api'; + +(globalThis.window as unknown as Record).__FLUXER_BOOTSTRAP__ = { + config: { + releaseChannel: 'stable', + bootstrapApiEndpoint: BOOTSTRAP_ENDPOINT, + bootstrapApiPublicEndpoint: BOOTSTRAP_ENDPOINT, + }, + instance: { + api_code_version: Number.MAX_SAFE_INTEGER, + endpoints: { + api: BOOTSTRAP_ENDPOINT, + api_client: BOOTSTRAP_ENDPOINT, + api_public: BOOTSTRAP_ENDPOINT, + gateway: 'wss://gateway.primary.test', + media: 'https://media.primary.test', + static_cdn: 'https://cdn.primary.test', + marketing: 'https://primary.test', + admin: 'https://admin.primary.test', + invite: 'https://primary.test/invite', + gift: 'https://primary.test/gift', + webapp: 'https://app.primary.test', + upload_relay: 'https://upload.primary.test', + }, + captcha: {provider: 'none'}, + features: { + voice_enabled: false, + stripe_enabled: false, + self_hosted: false, + presigned_attachment_uploads: false, + emails_enabled: false, + phone_verification_enabled: false, + }, + gif: {provider: 'klipy', display_name: 'Klipy', attribution_required: false}, + sso: {enabled: false, enforced: false, display_name: null, redirect_uri: ''}, + registration: {mode: 'open', admin_registration_urls_enabled: true}, + community: {single_community: false, single_community_guild_id: null, direct_messages_disabled: false}, + services: {gif_enabled: true, youtube_enabled: false, bluesky_enabled: false}, + limits: undefined, + push: {public_vapid_key: null}, + app_public: { + branding: { + product_name: 'Fluxer', + icon_url: null, + symbol_url: null, + logo_url: null, + wordmark_url: null, + favicon_url: null, + theme_color: null, + }, + setup: {configured: true, admin_url: null}, + legal: {terms_url: null, privacy_url: null}, + registration: {collect_date_of_birth: true}, + }, + }, +}; + +let statusListener: ((status: GlobalShortcutsStatus) => void) | null = null; + +const globalShortcutsApi: Partial = { + sync: async () => {}, + getStatus: () => new Promise(() => {}), + onStatus: (callback) => { + statusListener = callback; + return () => { + statusListener = null; + }; + }, +}; + +vi.mock('@app/features/ui/utils/NativeUtils', async (importOriginal) => ({ + ...(await importOriginal()), + getElectronAPI: () => ({globalShortcuts: globalShortcutsApi}), +})); + +const PORTAL_ASSIGNED_ACTIONS_KEY = 'GlobalShortcuts:portalAssignedActions:v1'; + +interface PortalStatusOptions { + recovering?: boolean; + trigger?: string | null; + error?: string | null; + backend?: GlobalShortcutsBackend; + directInputEnabled?: boolean; +} + +function portalStatus(state: GlobalShortcutsPortalState, options: PortalStatusOptions = {}): GlobalShortcutsStatus { + const trigger = options.trigger === undefined ? 'F13' : options.trigger; + const portal: GlobalShortcutsPortalStatus = { + state, + version: 2, + canConfigure: true, + canRecheck: state === 'unsupported' || state === 'error', + portalAppId: 'app.fluxer.FluxerDesktop', + shortcuts: [ + {action: 'voice_push_to_talk', triggerDescription: state === 'bound' ? trigger : null}, + {action: 'voice_push_to_mute', triggerDescription: null}, + ], + error: options.error ?? (state === 'error' ? 'portal-unavailable' : null), + recovering: options.recovering ?? false, + }; + return { + backend: options.backend ?? (state === 'unsupported' ? 'none' : 'portal'), + platform: 'linux', + linux: { + session: 'wayland', + sandbox: 'none', + desktop: 'gnome', + portal, + directInput: {available: true, enabled: options.directInputEnabled ?? false, locked: false}, + }, + hooksActive: false, + hookError: null, + supportsMouseButtons: false, + supportsModifierOnly: false, + }; +} + +type Modules = { + GlobalShortcuts: typeof import('@app/features/input/state/GlobalShortcuts').default; + Keybind: typeof import('@app/features/input/state/InputKeybind').default; + AppStorage: typeof import('@app/features/platform/state/PersistentStorage').default; + reactToPushToTalkModeChanges: typeof import('@app/features/app/keybindings/utils/PushToTalkModeReconcile').reactToPushToTalkModeChanges; +}; + +let modules: Modules; + +function emit(status: GlobalShortcutsStatus): void { + expect(statusListener).not.toBeNull(); + statusListener?.(status); +} + +describe('push-to-talk mode reconcile', () => { + beforeAll(async () => { + const {default: AppStorage} = await import('@app/features/platform/state/PersistentStorage'); + AppStorage.setJSON(PORTAL_ASSIGNED_ACTIONS_KEY, ['voice_push_to_talk']); + const {default: GlobalShortcuts} = await import('@app/features/input/state/GlobalShortcuts'); + const {default: Keybind} = await import('@app/features/input/state/InputKeybind'); + const {reactToPushToTalkModeChanges} = await import('@app/features/app/keybindings/utils/PushToTalkModeReconcile'); + modules = {GlobalShortcuts, Keybind, AppStorage, reactToPushToTalkModeChanges}; + Keybind.setTransmitMode('voice_push_to_talk'); + }, 180_000); + + afterAll(async () => { + modules.GlobalShortcuts.detach(); + const {default: Idle} = await import('@app/features/ui/state/Idle'); + Idle.destroy(); + }); + + it('restores the last settled portal assignment at startup before any status arrives', () => { + const {GlobalShortcuts, Keybind} = modules; + expect(Keybind.isPushToTalkEffective()).toBe(false); + GlobalShortcuts.attach(); + expect(Keybind.isPushToTalkEffective()).toBe(true); + emit(portalStatus('unknown')); + emit(portalStatus('probing')); + expect(Keybind.isPushToTalkEffective()).toBe(true); + }); + + it('keeps push-to-talk effective through transient and error portal states', () => { + const {Keybind, AppStorage, reactToPushToTalkModeChanges} = modules; + const reconcile = vi.fn(); + const dispose = reactToPushToTalkModeChanges(reconcile); + emit(portalStatus('bound')); + expect(AppStorage.getJSON(PORTAL_ASSIGNED_ACTIONS_KEY)).toEqual(['voice_push_to_talk']); + for (const status of [ + portalStatus('bound', {recovering: true}), + portalStatus('bound', {recovering: true, trigger: null}), + portalStatus('declined', {recovering: true}), + portalStatus('unknown', {recovering: true, backend: 'none'}), + portalStatus('probing', {recovering: true}), + portalStatus('binding'), + portalStatus('error', {recovering: true}), + portalStatus('error', {error: 'portal-unavailable'}), + portalStatus('error', {error: 'timeout'}), + portalStatus('error', {error: 'session-closed'}), + portalStatus('error', {error: 'identity'}), + portalStatus('error', {error: 'bind-failed'}), + portalStatus('unsupported', {recovering: true}), + portalStatus('not-set-up'), + portalStatus('unknown'), + portalStatus('bound'), + ]) { + emit(status); + expect(Keybind.isPushToTalkEffective()).toBe(true); + } + expect(reconcile).not.toHaveBeenCalled(); + expect(AppStorage.getJSON(PORTAL_ASSIGNED_ACTIONS_KEY)).toEqual(['voice_push_to_talk']); + dispose(); + }); + + it('keeps push-to-talk effective while a recovering session reopens with no listed triggers', () => { + const {Keybind, AppStorage, reactToPushToTalkModeChanges} = modules; + const reconcile = vi.fn(); + const dispose = reactToPushToTalkModeChanges(reconcile); + emit(portalStatus('bound')); + for (const status of [ + portalStatus('bound', {recovering: true}), + portalStatus('bound', {recovering: true, trigger: null}), + portalStatus('binding', {recovering: true}), + portalStatus('bound'), + ]) { + emit(status); + expect(Keybind.isPushToTalkEffective()).toBe(true); + expect(AppStorage.getJSON(PORTAL_ASSIGNED_ACTIONS_KEY)).toEqual(['voice_push_to_talk']); + } + expect(reconcile).not.toHaveBeenCalled(); + dispose(); + }); + + it('flips push-to-talk only on settled states and preserves self-mute when it does', () => { + const {Keybind, AppStorage, reactToPushToTalkModeChanges} = modules; + const reconcile = vi.fn(); + const dispose = reactToPushToTalkModeChanges(reconcile); + const expectFlip = (status: GlobalShortcutsStatus, effective: boolean): void => { + reconcile.mockClear(); + emit(status); + expect(Keybind.isPushToTalkEffective()).toBe(effective); + expect(reconcile).toHaveBeenCalledTimes(1); + expect(reconcile).toHaveBeenLastCalledWith({preserveSelfMute: true}); + }; + expectFlip(portalStatus('declined'), false); + expect(AppStorage.getJSON(PORTAL_ASSIGNED_ACTIONS_KEY)).toEqual([]); + expectFlip(portalStatus('bound'), true); + expectFlip(portalStatus('unsupported'), false); + expectFlip(portalStatus('bound'), true); + expectFlip(portalStatus('unknown', {backend: 'evdev', directInputEnabled: true}), false); + expectFlip(portalStatus('bound'), true); + expectFlip(portalStatus('bound', {trigger: null}), false); + expectFlip(portalStatus('bound'), true); + reconcile.mockClear(); + Keybind.setTransmitMode('voice_activity'); + expect(reconcile).toHaveBeenLastCalledWith({preserveSelfMute: false}); + Keybind.setTransmitMode('voice_push_to_talk'); + dispose(); + }); +}); diff --git a/fluxer_app/src/features/app/keybindings/utils/PushToTalkModeReconcile.ts b/fluxer_app/src/features/app/keybindings/utils/PushToTalkModeReconcile.ts new file mode 100644 index 000000000..88ce7db11 --- /dev/null +++ b/fluxer_app/src/features/app/keybindings/utils/PushToTalkModeReconcile.ts @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import Keybind from '@app/features/input/state/InputKeybind'; +import {compareStructural, reaction} from 'mobx'; + +export function reactToPushToTalkModeChanges(onChange: (options: {preserveSelfMute: boolean}) => void): () => void { + return reaction( + () => ({ + transmitMode: Keybind.transmitMode, + pushToTalk: Keybind.isPushToTalkEffective(), + pushToMute: Keybind.isPushToMuteEffective(), + }), + (current, previous) => onChange({preserveSelfMute: current.transmitMode === previous.transmitMode}), + {equals: compareStructural}, + ); +} diff --git a/fluxer_app/src/features/app/keybindings/utils/RuntimeKeybinds.ts b/fluxer_app/src/features/app/keybindings/utils/RuntimeKeybinds.ts index da34916d3..2df9e2271 100644 --- a/fluxer_app/src/features/app/keybindings/utils/RuntimeKeybinds.ts +++ b/fluxer_app/src/features/app/keybindings/utils/RuntimeKeybinds.ts @@ -10,6 +10,7 @@ export { } from '@app/features/app/keybindings/utils/HookShortcutIds'; export type RuntimeKeybind = KeybindConfig & { + id: string | null; combo: KeyCombo; }; export type RuntimeKeybindBaseResolver = (action: KeybindCommand) => KeybindConfig | null; @@ -34,6 +35,16 @@ export const HOLD_ACTIONS_FOR_VOICE_ACTIVITY_MODE: ReadonlyArray = [ 'voice_priority_vad', ]; +export function sourceIdForKeybind(keybind: {id: string | null; action: KeybindCommand}): string { + if (keybind.id === null) return `default:${keybind.action}`; + return `custom:${keybind.id}`; +} + +export function gamepadSourceIdForKeybind(keybind: {id: string | null; action: KeybindCommand}): string { + if (keybind.id === null) return `gamepad:default:${keybind.action}`; + return `gamepad:${keybind.id}`; +} + export function hasTriggerKey(combo: KeyCombo): boolean { return (combo.key ?? '') !== '' || (combo.code ?? '') !== ''; } @@ -55,7 +66,7 @@ export function buildDefaultRuntimeKeybinds( if (overriddenActions.has(entry.action)) continue; const combo = entry.combo; if (!isEnabledDefaultCombo(combo)) continue; - result.push({...entry, combo}); + result.push({...entry, id: null, combo}); } return result; } @@ -69,7 +80,7 @@ export function buildCustomRuntimeKeybinds( if (!custom.action || !isActiveCustomKeybind(custom)) continue; const base = getBaseByAction(custom.action); if (!base) continue; - result.push({...base, combo: custom.combo}); + result.push({...base, id: custom.id, combo: custom.combo}); } return result; } diff --git a/fluxer_app/src/features/i18n/locales/ar/messages.po b/fluxer_app/src/features/i18n/locales/ar/messages.po index 2ab5a2603..74598190c 100644 --- a/fluxer_app/src/features/i18n/locales/ar/messages.po +++ b/fluxer_app/src/features/i18n/locales/ar/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# قناة تتابع} other {# قناة تتابع msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, zero {لم يُعثر على أي مجتمع} one {تم العثور على مجتمع واحد} two {تم العثور على مجتمعين} few {تم العثور على # مجتمعات} many {تم العثور على # مجتمعًا} other {تم العثور على # مجتمع}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, zero {# مجتمع غير متاح مؤقتًا بسبب عطل في مكثف التدفق.} one {مجتمع واحد غير متاح مؤقتًا بسبب عطل في مكثف التدفق.} two {مجتمعان غير متاحين مؤقتًا بسبب عطل في مكثف التدفق.} few {# مجتمعات غير متاحة مؤقتًا بسبب عطل في مكثف التدفق.} many {# مجتمعًا غير متاح مؤقتًا بسبب عطل في مكثف التدفق.} other {# مجتمع غير متاح مؤقتًا بسبب عطل في مكثف التدفق.}}" @@ -1708,6 +1708,31 @@ msgstr "يتجنب {productName} إرسال إشعارات الدفع إلى أ msgid "{productName} Bug Hunter" msgstr "صائد أخطاء {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "يمكن لـ {productName} أن يطلب من سطح المكتب لديك تشغيل اختصاراته عندما لا تكون نافذة {productName} نشطة. سيطلب منك سطح المكتب التأكيد ويتيح لك اختيار المفاتيح." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "لم يعد بإمكان {productName} قراءة لوحة المفاتيح، لذا يستمع إلى الاختصارات عبر X11 بدلًا من ذلك." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "لم يعد بإمكان {productName} قراءة لوحة المفاتيح، لذا تُستخدم اختصارات سطح المكتب بدلًا من ذلك." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "لم يعد بإمكان {productName} قراءة لوحة المفاتيح. لا تعمل الاختصارات إلا عندما تكون نافذة {productName} نشطة." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "يمكن لـ {productName} تسجيل اختصاراته في Hyprland لتعمل عندما لا تكون نافذة {productName} نشطة. بعد ذلك تربط المفاتيح في ملف إعدادات Hyprland." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "يحتاج {productName} إلى الوصول إلى الكاميرا لعرض أجهزتك." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "يحتاج {productName} إلى الوصول إلى الميكروفون لعرض أجهزتك." @@ -1837,6 +1862,11 @@ msgstr "{productName} موجود الآن على {host}. ثبّته من هنا msgid "{productName} Partner" msgstr "شريك {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "سجّل {productName} اختصاراته في Hyprland. اربطها في ملف إعدادات Hyprland لاستخدامها عندما لا تكون نافذة {productName} نشطة." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "ملف شخصي مميز" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "يلزم توفير redirect_uri عندما لا يكون نطاق bot هو النطاق الوحيد." @@ -2847,7 +2877,7 @@ msgstr "في كل الرسائل المباشرة المفتوحة فقط" msgid "Across every community you're in" msgstr "في كل المجتمعات التي تنتمي إليها" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "الإجراء" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "مشاركة النشاط" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "حد النشاط" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "أضف طبقة أمان إضافية إلى حسابك" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "إضافة اختصار مخصص آخر" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "العنوان" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "اضبط كل إعداد بنفسك: إزالة الضوضاء، وإلغاء الصدى، والكسب." @@ -3696,11 +3726,11 @@ msgstr "السماح بـ {everyoneMention} و{hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "اسمح لـ {PRODUCT_NAME} بالوصول إلى الكاميرا في {MACOS_SYSTEM_SETTINGS_NAME} ← {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} ← {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "اسمح لـ {PRODUCT_NAME} بالوصول إلى الميكروفون الخاص بك في {MACOS_SYSTEM_SETTINGS_NAME} ← {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} ← {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "اسمح لـ {PRODUCT_NAME} بالوصول إلى الميكروفون الخاص بك. تحقق من شريط عنوان المتصفح أو الإعدادات." @@ -3803,7 +3833,7 @@ msgstr "السماح للأعضاء الذين لا يملكون إذن {readMes msgid "Allow mentions for this role" msgstr "السماح بالإشارة إلى هذا الدور" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "السماح بالوصول إلى الميكروفون" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "فشل التفويض" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "فشل التفويض. حاول مرة أخرى." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "اكتشاف اللغة تلقائيًا" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "تعيين حد النشاط تلقائيًا" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "أذونات البوت" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "لا يمكن أن تكون أذونات البوت سالبة." @@ -6869,6 +6899,11 @@ msgstr "تغيير الاسم المستعار في المجموعة" msgid "Change icon" msgstr "تغيير الأيقونة" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "تغيير في إعدادات النظام" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "الوصول إلى القناة" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "لا يمكن الوصول إلى القناة" @@ -7294,7 +7329,7 @@ msgstr "تم نسخ معرف القناة إلى الحافظة" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "رابط القناة" @@ -7462,6 +7497,11 @@ msgstr "مربع الكتابة" msgid "Chat shortcuts" msgstr "اختصارات الدردشة" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "تحقق مرة أخرى" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "اختر نافذة من منتقي النظام" msgid "Choose an account" msgstr "اختر حسابًا" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "اختر إجراءً." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "اختيار الخلفية" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "اختر guild_id أو channel_id، وليس كليهما." @@ -7990,7 +8030,7 @@ msgstr "ينقي صوت الميكروفون لضمان وضوح الكلام." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "انقر لفتح منتقي الألوان واختيار لون جدي msgid "Click to react" msgstr "انقر لإضافة تفاعل" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "انقر للتسجيل" @@ -9110,7 +9150,7 @@ msgstr "تأكيد" msgid "Confirmation or double-click for community voice joins" msgstr "التأكيد أو النقر المزدوج للانضمام إلى القنوات الصوتية في المجتمعات" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "يتعارض مع {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "وصّل كاميرا أو جهاز التقاط افتراضي، ثم msgid "Connect a display, then try again." msgstr "وصّل شاشة، ثم حاول مرة أخرى." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "وصّل ميكروفونًا وحاول مرة أخرى." @@ -9700,6 +9740,21 @@ msgstr "نسخ رابط الدعوة" msgid "Copy invite URL" msgstr "نسخ رابط الدعوة" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "نسخ الأسطر" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "نسخ الأسطر لملف hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "نسخ الأسطر لملف hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "مصدر مخصص" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "تحديد مخصص" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "إجراء اختصار مخصص" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "خيارات الاختصارات المخصصة" @@ -11466,7 +11521,7 @@ msgstr "الاختصارات الافتراضية" msgid "Default translator" msgstr "المترجم الافتراضي" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "الافتراضي:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "حذف الوسائط المحفوظة" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "حذف الاختصار" @@ -12102,13 +12157,13 @@ msgstr "حالة المطور" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "أدوات المطور" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "أدوات المطور متاحة من شارة الموظف في رأس القناة." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "تعطيل مزايا Premium في الواجهة الخلفية" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "تعطيل الاختصار المدمج" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "هل تريد تعطيل النوافذ المنبثقة (صورة داخل صورة) لمشاركة الشاشة؟" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "تعطيل الاختصار" @@ -12888,7 +12943,7 @@ msgstr "تبرع بدلاً من ذلك" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "زر التنزيل" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "تنزيل تطبيق سطح المكتب" @@ -13204,6 +13259,11 @@ msgstr "الهولندية" msgid "e.g., YubiKey, iPhone, work computer" msgstr "مثال: YubiKey، أو iPhone، أو حاسوب العمل" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "يمكن لكل مفتاح تشغيل اختصار واحد فقط على مستوى النظام." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "تعديل رابط الدعوة" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "تعديل اختصار لوحة المفاتيح" @@ -13425,7 +13485,7 @@ msgstr "نموذج تعديل الوسائط المحفوظة" msgid "Edit search engine" msgstr "تعديل محرك البحث" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "تعديل الاختصار" @@ -13753,20 +13813,16 @@ msgstr "تعديل فارغ" msgid "Empty favorites" msgstr "المفضلة فارغة" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "تمكين" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "تمكين إذن {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "تفعيل تسريع الجهاز وإعادة التشغيل" msgid "Enable hardware acceleration?" msgstr "تفعيل تسريع الجهاز؟" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "فعّل الوصول إلى الإدخال لاستخدام اختصارات على مستوى النظام في Wayland. بعد تغيير الوصول إلى الإدخال، أغلق {productName} تمامًا ثم أعد تشغيله حتى تتمكن الاختصارات من استخدامه." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "تفعيل التوسيع بمفتاح Shift" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "تفعيل الاختصار" @@ -14357,7 +14408,7 @@ msgstr "صوت النظام بالكامل" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "صوت الدخول" @@ -14467,7 +14518,7 @@ msgstr "وقت المساء" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "يوازن مستوى صوت الميكروفون حتى لا يكون صوتك منخفضًا جدًا." @@ -15682,6 +15733,26 @@ msgstr "للعيون البريطانية فقط..." msgid "For me" msgstr "لنفسي" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "للضغط لكتم الصوت، استخدم مفتاحًا واحدًا بدون Ctrl أو Alt أو Shift. عند استخدام تركيبة مفاتيح، حرّر المفتاح الرئيسي أولًا وإلا فقد يبقى الميكروفون مكتومًا." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "للضغط للتحدث، استخدم مفتاحًا واحدًا بدون Ctrl أو Alt أو Shift. عند استخدام تركيبة مفاتيح، حرّر المفتاح الرئيسي أولًا وإلا فقد يبقى الميكروفون قيد التشغيل." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "لهذا الاختصار، استخدم مفتاحًا واحدًا بدون Ctrl أو Alt أو Shift. عند استخدام تركيبة مفاتيح، حرّر المفتاح الرئيسي أولًا وإلا فقد يبقى الاختصار نشطًا." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "لأولوية النشاط الصوتي، استخدم مفتاحًا واحدًا بدون Ctrl أو Alt أو Shift. عند استخدام تركيبة مفاتيح، حرّر المفتاح الرئيسي أولًا وإلا فقد تبقى الأولوية مفعّلة." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "حفاظًا على أمانك، أكّد هويتك للمتابعة." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "الملف الشخصي العام" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "اختصار على مستوى النظام" @@ -17434,6 +17505,11 @@ msgstr "اضغط باستمرار على مفتاح Shift ثم اضغط على X msgid "Hold to record. Drag up to lock, or release to send." msgstr "اضغط مطولًا للتسجيل. اسحب للأعلى للقفل، أو أفلِت للإرسال." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "الضغط مع الاستمرار على Shift أو Ctrl أو Alt يمنع هذا الاختصار. أضف مفاتيح بديلة في إعدادات النظام." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "رائع! تم تفعيل الإشعارات" msgid "Hyperlink" msgstr "رابط تشعبي" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "لا يحتوي Hyprland على نافذة لإعداد الاختصارات. أضف الأسطر الخاصة بإصدار Hyprland لديك إلى ملف إعدادات Hyprland واستبدل KEY بالمفتاح الذي تريده." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "لا يحتوي Hyprland على نافذة لإعداد الاختصارات. اربط المفاتيح في ملف إعدادات Hyprland." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (قبل Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 والإصدارات الأحدث)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "أوافق على <0>سياسة الخصوصية" @@ -17576,7 +17672,7 @@ msgstr "غيرت رأيي" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "فهمت" @@ -18000,7 +18096,7 @@ msgstr "في" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "في وضع \"نظام التشغيل\"، تكون القواميس المذكورة أعلاه إرشادية فقط. سيمرر {PRODUCT_NAME} علامات اللغة هذه إلى مدقق الإملاء في نظام التشغيل عندما يكون ذلك مدعومًا. يتم تجاهل العلامات التي لا يتعرف عليها." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "في المتصفح، لا تعمل ميزة الضغط للتحدث إلا عندما تكون علامة تبويب {PRODUCT_NAME} نشطة. ثبّت تطبيق سطح المكتب لاستخدام الضغط للتحدث على مستوى النظام." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "الإدخال" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "تعذر تمكين الوصول إلى الإدخال تلقائيًا. غيّره يدويًا، ثم أغلق {productName} تمامًا وأعد تشغيله حتى تتمكن الاختصارات من استخدامه." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "تعذر تمكين الوصول إلى الإدخال. حاول مرة أخرى من إعدادات {settingsMenuName}، ثم أغلق {productName} تمامًا وأعد تشغيله." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "يلزم الوصول إلى الإدخال لاستخدام اختصارات لوحة المفاتيح والماوس على مستوى النظام في Wayland. بعد تغيير الوصول إلى الإدخال، أغلق {productName} تمامًا وأعد تشغيله حتى تتمكن الاختصارات من استخدامه." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "تم تغيير الوصول إلى الإدخال. أغلق {productName} تمامًا وأعد تشغيله حتى تتمكن الاختصارات على مستوى النظام من استخدامه." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "الإدخال والإخراج" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "طلب تفويض غير صالح" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "أذونات البوت غير صالحة." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "الرمز غير صالح أو انتهت صلاحيته. حاول مرة أخرى." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "قيمة redirect_uri غير صالحة" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "رابط الدعوة مخفي أثناء المشاركة:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "تم إخفاء رابط الدعوة أثناء المشاركة" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "الانتقال إلى {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "الانتقال إلى {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "الانتقال إلى المكالمة الحالية" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "الانتقال إلى القناة المرتبطة" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "الانتقال إلى الرسالة المرتبطة" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "الانتقال إلى الرسالة من {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "الانتقال إلى الرسالة في {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "حافظ على سريتها. ستؤدي إعادة الإنشاء إلى تعطيل عمليات التكامل الحالية." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "يستمر الإرسال لفترة وجيزة بعد إفلات زر الضغط للتحدث." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "لوحة المفاتيح" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "تعذّر بدء الوصول إلى لوحة المفاتيح. لا تعمل الاختصارات إلا عندما تكون نافذة {productName} نشطة." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "ترك الإشراف للمجتمع نفسه" msgid "Let the member hear others in this community again" msgstr "تم السماح للعضو بسماع الآخرين في هذا المجتمع مرة أخرى" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "يتيح لـ {productName} قراءة لوحة المفاتيح مباشرةً. نظامك يسمح بذلك بالفعل." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "يسمح لـ CSS المخصص بإظهار مناطق شفافة في النافذة بعد إعادة تشغيل تطبيق سطح المكتب." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "تنسيق الروابط" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "الرابط غير متاح" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "المظهر والشكل" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "يبدو أنك تستخدم {0}. قد تؤدي إضافة معالجة {PRODUCT_NAME} فوقه إلى تذبذب مستوى الصوت أو تشويهه. يمرّر الإدخال المباشر الصوت كما هو دون أي تعديل." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "تخطيط الرسائل" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "رابط الرسالة" @@ -21332,7 +21416,7 @@ msgstr "ميكروفون" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "اختبار الميكروفون" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "متفرقات" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "client_id مفقود" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "المزيد من الخيارات" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "المزيد من خيارات الاختصارات" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "لا يوجد إذن {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "لا يمكن الوصول" @@ -22984,7 +23068,7 @@ msgstr "لم يتم العثور على نتائج مطابقة" msgid "No mentions yet" msgstr "لا توجد إشارات بعد" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "لم يتم العثور على ميكروفون" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "لم يتم العثور على إعدادات" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "لم يتم تعيين اختصار" @@ -23358,6 +23442,11 @@ msgstr "ليس عددًا صحيحًا" msgid "Not accepted" msgstr "لم يتم القبول" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "غير معيّن" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "افتح {host} في متصفحك." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "افتح {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "فتح {labelText} في الإعدادات" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "صفحة لأسفل" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "رابط صفحة" @@ -25611,11 +25700,11 @@ msgstr "الحالة" msgid "Press {shortcut} to react" msgstr "اضغط على {shortcut} للتفاعل" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "اضغط على مفتاح أو زر ماوس أو زر يد تحكم لتعيينه. اضغط Esc للإلغاء." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "اضغط على اختصار…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "مفتاح الضغط للتحدث" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "الضغط للتحدث" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "الضغط للتحدث (محدود)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "لا يوجد بعد مفتاح على مستوى النظام للضغط للتحدث. لتعيينه، انتقل إلى الإعدادات > التطبيقات > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "لا يوجد بعد مفتاح على مستوى النظام للضغط للتحدث. لتعيينه، انتقل إلى إعدادات النظام > لوحة المفاتيح > الاختصارات > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "لا يوجد بعد مفتاح على مستوى النظام للضغط للتحدث. لتعيينه، افتح إعدادات اختصارات لوحة المفاتيح في سطح المكتب لديك." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "يتم تجاهل الضغط للتحدث في وضع الإدخال المباشر. بدّل إلى الصوت المركّز أو المخصص لاستخدامه." @@ -26293,21 +26397,51 @@ msgstr "يتم تجاهل الضغط للتحدث في وضع الإدخال ا msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "الضغط للتحدث مفعّل. اضغط باستمرار على {pushToTalkHint} للتحدث." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "الضغط للتحدث مفعّل. اضغط مع الاستمرار على مفتاح الضغط للتحدث لتتحدث." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "لا يعمل الضغط للتحدث إلا عندما تكون نافذة {productName} نشطة، لأن سطح المكتب لديك لم يُعِدّ الاختصارات على مستوى النظام." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "لا يعمل الضغط للتحدث إلا عندما تكون نافذة {productName} نشطة حتى تفعيل {permissionName}. بعد تغيير الإذن، أغلق {productName} تمامًا وأعد تشغيله لتتمكن الاختصارات على مستوى النظام من استخدامه." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "لا يعمل الضغط للتحدث إلا عندما تكون نافذة {productName} نشطة إلى أن تُعِدّ الاختصارات على مستوى النظام." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "لا يعمل الضغط للتحدث إلا عندما تكون علامة تبويب المتصفح نشطة." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "سيستخدم الضغط للتحدث المفتاح الذي تعيّنه في {productName}. عيّن مفتاحًا أولًا، وإلا فسيعمل الميكروفون بالنشاط الصوتي." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "لن يلغي الضغط للتحدث كتم الميكروفون أثناء تعطيل السمع. ألغِ تعطيل السمع لتتحدث." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "لا يعمل الضغط للتحدث خارج {productName} إلا بعد ربطه في ملف إعدادات Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "لا يعمل الضغط للتحدث خارج {productName} إلا بعد ربطه في ملف إعدادات Hyprland. أضف السطر الخاص بإصدار Hyprland لديك واستبدل KEY بالمفتاح الذي تريده." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "JSON الخام للتضمين" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "إعادة تمكين الاختصار المدمج" @@ -26565,13 +26699,6 @@ msgstr "الإشارات الأخيرة" msgid "Recent searches" msgstr "عمليات البحث الأخيرة" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "إعادة التحقق" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "مُوصى به" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "مُوصى به. ينقّي صوت الميكروفون ليكون الكلام واضحًا." @@ -26600,6 +26727,11 @@ msgstr "إعادة الاتصال" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "أُعيد الاتصال بنجاح. اختر شاشة مرة أخرى إذا أردت استئناف بثك السابق." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "جارٍ إعادة الاتصال بسطح المكتب…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "جارٍ إعادة الاتصال…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "تسجيل الاختصار" @@ -26851,7 +26983,7 @@ msgstr "نسبي" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "تأخير الإفلات" @@ -27739,13 +27871,13 @@ msgstr "أُعيد الإرسال" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "إعادة تعيين" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "إعادة تعيين \"{label}\" إلى الاختصار المدمج" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "إعادة تعيين إلى القيم الفعلية" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "إعادة التعيين إلى الاختصار المدمج" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "اختر الحساب الذي تريد تسجيل الدخول به على جهازك الجديد." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "تحديد وضع معالجة الصوت" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "يرسل صوت الميكروفون الخام بدون معالجة." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "يرسل صوتك دون أي تعديل. مناسب إذا كنت تستخدم برامج صوت خارجية." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "تم التعيين بواسطة إعداد {preset}. يؤدي اختيار قيمة هنا إلى التبديل إلى مخصص." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "تعيين اختصار مخصص" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "تعيين اختصار مخصص لـ \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "تم تعيين منطقة الصوت إلى {region}" msgid "Set the voice region to automatic" msgstr "تم تعيين منطقة الصوت إلى تلقائي" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "إعداد" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "الإعدادات" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "رابط الإعدادات" @@ -30330,8 +30467,8 @@ msgstr "رموز مختصرة" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "التبديل إلى التخطيط المريح" msgid "Switch to dense layout" msgstr "التبديل إلى التخطيط المضغوط" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "التبديل إلى الإدخال المباشر" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "تثبيت مُدار بواسطة النظام" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "اختصارات على مستوى النظام" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "تعذر إعداد الاختصارات على مستوى النظام." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "على مستوى النظام: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "على مستوى النظام: غير معيّن" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "الصفحة التي تبحث عنها غير موجودة أو تم نقلها." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "عنوان redirect_uri المقدّم غير مسجّل لهذا التطبيق." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "يجب أن يستخدم عنوان redirect_uri المقدّم بروتوكول HTTP أو HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "هذه الرسوم المتحركة طويلة جدًا أو كبيرة جدًا بحيث لا يمكن قصها. جرب ملفًا أقصر أو أصغر." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "هذا التطبيق" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "انتهت صلاحية هذا المرفق" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "هذا البوت ليس عامًا ولا يمكن للمستخدمين الآخرين إضافته." @@ -34081,6 +34233,16 @@ msgstr "هذه المحادثة لم تعد متاحة." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "لا يمكن لإصدار سطح المكتب هذا تثبيت التحديثات داخل {productName}. نزّل أحدث مثبّت لسطح المكتب بدلًا من ذلك." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "لا يوفّر سطح المكتب هذا اختصارات على مستوى النظام. لا يعمل الضغط للتحدث إلا عندما تكون نافذة {productName} نشطة." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "لا يوفّر سطح المكتب هذا اختصارات على مستوى النظام. لا تزال الاختصارات تعمل عندما تكون نافذة {productName} نشطة." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "هذا هو آخر مفتاح مرور لديك، ومفاتيح الم msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "هذا هو آخر مفتاح مرور لديك. بمجرد حذفه، لن تتمكن من استخدام مفتاح مرور كعامل ثانٍ." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "يعمل هذا المفتاح عندما تكون نافذة {productName} نشطة. يحدّد سطح المكتب لديك المفتاح على مستوى النظام، ضمن {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "قناة الرابط هذه مصنفة للمحتوى للبالغين وقد تفتح مواد غير مناسبة لبعض المستخدمين." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "هذا الرابط غير متاح لحسابك." @@ -34778,6 +34945,21 @@ msgstr "شريط العنوان" msgid "Title bar platform" msgstr "منصة شريط العنوان" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "لتغيير المفاتيح، انتقل إلى الإعدادات > التطبيقات > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "لتغيير المفاتيح، انتقل إلى إعدادات النظام > لوحة المفاتيح > الاختصارات > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "لتغيير المفاتيح، افتح إعدادات اختصارات لوحة المفاتيح في سطح المكتب لديك." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "لإنشاء ويب هوك، افتح <0>{channelWebhooksSettingsPath}. لا يزال بإمكانك تعديل وتنظيم جميع الويب هوك الحالية هنا." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "غير معروف" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "تطبيق غير معروف، أو جلسة منتهية الصلاحية، أو طلب تفويض غير صالح." @@ -36410,6 +36592,11 @@ msgstr "حتى 4K/60 إطارًا في الثانية" msgid "Up to 720p at 30 FPS" msgstr "حتى 720p بمعدل 30 إطارًا في الثانية" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "حدّث {productName} لاستخدام الاختصارات على مستوى النظام على Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "استخدام السمة الداكنة في التطبيق بأكمل msgid "Use default" msgstr "استخدام القيمة الافتراضية" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "استخدام الوصول المباشر إلى أجهزة الإدخال" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "المشاركون في المكالمة الصوتية" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "معالجة الصوت" @@ -38264,7 +38456,7 @@ msgstr "إلغاء تعطيل السمع" msgid "Voice unmute" msgstr "إلغاء كتم الصوت" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "يستخدم الصوت جهاز الإخراج الخاص بنظامك في هذا المتصفح. قم بتغييره في إعدادات نظامك." @@ -38278,7 +38470,7 @@ msgstr "ملغاة" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "في انتظار انتهاء الوضع البطيء في قناة و msgid "Waiting for you to..." msgstr "في انتظارك لـ..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "في انتظار سطح المكتب…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "أي المحادثات" msgid "Which rule was broken?" msgstr "ما القاعدة التي تم خرقها؟" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "عند تشغيل الضغط للتحدث، يتحكم اختصارك في الميكروفون، لذلك يكون زر كتم الصوت معطّلًا." @@ -39094,6 +39291,11 @@ msgstr "بدون هذا الإذن، يلزم استخدام الضغط للتح msgid "Wordmark" msgstr "الشعار النصي" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "يعمل عندما تكون نافذة {productName} نشطة" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "لقد غيرت علامتك أثناء استخدامك لـ {PREMIUM_PRODUCT_NAME}. ستتغير عشوائيًا عند انتهاء صلاحية {PREMIUM_PRODUCT_NAME} الخاص بك، ما لم يتم تجديده." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "ليس لديك صلاحية الوصول إلى القناة التي أُرسلت فيها هذه الرسالة." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "انتهت صلاحية اشتراكك في {premiumProductName}. لقد فقدت جميع مزايا {premiumProductName}. أعد تفعيل اشتراكك لاستعادة الوصول." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "انتهت صلاحية جلسة {productName} الخاصة بك. سجّل الدخول مرة أخرى لمتابعة تفويض هذا التطبيق." @@ -40144,6 +40346,16 @@ msgstr "يحتاج مجتمعك إلى {minMemberCount, plural, zero {# عضو} msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "تم التحقق من بطاقتك الائتمانية. إذا كانت البطاقة صالحة، فسيتم إلغاء قفل محتوى البالغين قريبًا. يمكنك إغلاق علامة التبويب هذه والعودة إلى التطبيق." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "لم يُعِدّ سطح المكتب لديك الاختصارات على مستوى النظام." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "يشغّل سطح المكتب لديك هذه الاختصارات حتى عندما لا تكون نافذة {productName} نشطة." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/auto-i18n-reviewed-unchanged.json b/fluxer_app/src/features/i18n/locales/auto-i18n-reviewed-unchanged.json index 3ddaebdaf..996b63fb9 100644 --- a/fluxer_app/src/features/i18n/locales/auto-i18n-reviewed-unchanged.json +++ b/fluxer_app/src/features/i18n/locales/auto-i18n-reviewed-unchanged.json @@ -1632,6 +1632,9 @@ { "msgid": "Capture entire system audio" }, + { + "msgid": "Change in system settings" + }, { "msgid": "Change stream" }, @@ -1755,6 +1758,9 @@ { "msgid": "Characters in a single message" }, + { + "msgid": "Check again" + }, { "msgid": "Checkout is blocked for this account." }, @@ -1863,6 +1869,15 @@ { "msgid": "Copy emoji" }, + { + "msgid": "Copy lines" + }, + { + "msgid": "Copy lines for hyprland.conf" + }, + { + "msgid": "Copy lines for hyprland.lua" + }, { "msgid": "Copy sticker" }, @@ -2133,6 +2148,18 @@ { "msgid": "Footnote about custom tags" }, + { + "msgid": "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." + }, + { + "msgid": "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." + }, + { + "msgid": "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." + }, + { + "msgid": "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." + }, { "msgid": "From {sourceGuildName}" }, @@ -2217,9 +2244,18 @@ { "msgid": "Hid the community owner crown." }, + { + "msgid": "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." + }, { "msgid": "How much of the member's recent message history to delete." }, + { + "msgid": "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." + }, + { + "msgid": "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." + }, { "msgid": "Ignore this channel's slowmode cooldown." }, @@ -2271,6 +2307,9 @@ { "msgid": "Keep attachments when clearing an edit" }, + { + "msgid": "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." + }, { "msgid": "Kicked {targetUserTag} from the community" }, @@ -2298,6 +2337,9 @@ { "msgid": "Lets other communities follow this channel and get copies of what you publish." }, + { + "msgid": "Lets {productName} read your keyboard directly. Your system already allows this." + }, { "msgid": "Lightweight classic filter, works on any device." }, @@ -2472,6 +2514,9 @@ { "msgid": "Not accepted" }, + { + "msgid": "Not assigned" + }, { "msgid": "Not sent to followers yet." }, @@ -2661,6 +2706,33 @@ { "msgid": "Push relay notice" }, + { + "msgid": "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." + }, + { + "msgid": "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." + }, + { + "msgid": "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." + }, + { + "msgid": "Push-to-talk is on. Hold your push-to-talk key to speak." + }, + { + "msgid": "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." + }, + { + "msgid": "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." + }, + { + "msgid": "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." + }, + { + "msgid": "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." + }, + { + "msgid": "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." + }, { "msgid": "Read messages sent before they opened a channel. Without it, they only see messages that arrive while it is open." }, @@ -2673,6 +2745,9 @@ { "msgid": "Reason (optional)." }, + { + "msgid": "Reconnecting to your desktop…" + }, { "msgid": "Redeem a gift code" }, @@ -3087,6 +3162,9 @@ { "msgid": "Set to send {resolution} at {frameRate} FPS with {codec}" }, + { + "msgid": "Set up" + }, { "msgid": "Set up {productName}" }, @@ -3312,6 +3390,15 @@ { "msgid": "Syntax highlighting" }, + { + "msgid": "System-wide shortcuts could not be set up." + }, + { + "msgid": "System-wide: Not assigned" + }, + { + "msgid": "System-wide: {trigger}" + }, { "msgid": "Talk in calls and test your input." }, @@ -3438,6 +3525,9 @@ { "msgid": "This community has published a lot of messages recently. Try again in {duration}." }, + { + "msgid": "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." + }, { "msgid": "This device could not keep up with {targetResolution} at {targetFrameRate} FPS last time, so your stream started at {resolution} at {frameRate} FPS." }, @@ -3480,6 +3570,9 @@ { "msgid": "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." }, + { + "msgid": "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." + }, { "msgid": "This message can only be forwarded to age-restricted channels" }, @@ -3507,6 +3600,15 @@ { "msgid": "Timed out {targetUserTag}" }, + { + "msgid": "To change the keys, go to Settings > Apps > {productName}." + }, + { + "msgid": "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." + }, + { + "msgid": "To change the keys, open your desktop's keyboard shortcut settings." + }, { "msgid": "Transferred community ownership to {user}." }, @@ -3576,6 +3678,9 @@ { "msgid": "Update your passkey" }, + { + "msgid": "Update {productName} to use system-wide shortcuts on Wayland." + }, { "msgid": "Updates are on their way!" }, @@ -3594,6 +3699,9 @@ { "msgid": "Use custom emojis and stickers in any community" }, + { + "msgid": "Use direct input device access" + }, { "msgid": "Use emoji from other communities in this channel." }, @@ -3651,6 +3759,9 @@ { "msgid": "Volume sliders" }, + { + "msgid": "Waiting for your desktop…" + }, { "msgid": "We could not renew your subscription. Update your payment method to keep your perks." }, @@ -3693,6 +3804,9 @@ { "msgid": "Wordmark" }, + { + "msgid": "Works while {productName} is focused" + }, { "msgid": "You asked for {resolution} at {frameRate} FPS" }, @@ -3729,6 +3843,12 @@ { "msgid": "Your connection cannot keep up. Your stream is set to {frameRate} FPS at {resolution} and viewers are getting about {deliveredFrameRate} FPS." }, + { + "msgid": "Your desktop didn't set up system-wide shortcuts." + }, + { + "msgid": "Your desktop runs these shortcuts even while {productName} is not focused." + }, { "msgid": "Your device cannot keep up. Viewers get about {deliveredFrameRate} of {frameRate} FPS at {resolution}." }, @@ -3795,6 +3915,12 @@ { "msgid": "greeting" }, + { + "msgid": "hyprland.conf (before Hyprland 0.55)" + }, + { + "msgid": "hyprland.lua (Hyprland 0.55 and later)" + }, { "msgid": "macOS" }, @@ -4371,6 +4497,21 @@ { "msgid": "{productName} application logo" }, + { + "msgid": "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." + }, + { + "msgid": "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." + }, + { + "msgid": "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." + }, + { + "msgid": "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." + }, + { + "msgid": "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." + }, { "msgid": "{productName} cannot start a safe desktop audio route while excluding {productName}'s call audio." }, @@ -4398,6 +4539,9 @@ { "msgid": "{productName} now lives at {host}. Install it from there." }, + { + "msgid": "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." + }, { "msgid": "{productName} wordmark" }, diff --git a/fluxer_app/src/features/i18n/locales/bg/messages.po b/fluxer_app/src/features/i18n/locales/bg/messages.po index 9f4c7519b..69991906f 100644 --- a/fluxer_app/src/features/i18n/locales/bg/messages.po +++ b/fluxer_app/src/features/i18n/locales/bg/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# канал следва} other {# канала с msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {Намерена е # общност} other {Намерени са # общности}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# общност е временно недостъпна поради неизправност на флукс кондензатора.} other {# общности са временно недостъпни поради неизправност на флукс кондензатора.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} избягва да изпраща push известия msgid "{productName} Bug Hunter" msgstr "Ловец на бъгове в {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} може да поиска от работната ти среда да изпълнява клавишните му комбинации, докато {productName} не е на фокус. Работната среда ще поиска потвърждение и ще ти позволи да избереш клавишите." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} вече не може да чете клавиатурата ти, затова следи за клавишни комбинации чрез X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} вече не може да чете клавиатурата ти, затова се използват клавишните комбинации на работната среда." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} вече не може да чете клавиатурата ти. Клавишните комбинации работят само докато {productName} е на фокус." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} може да регистрира клавишните си комбинации в Hyprland, за да работят, докато {productName} не е на фокус. След това задаваш клавишите в конфигурацията на Hyprland." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} се нуждае от достъп до камерата, за да покаже списък с устройствата ти." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} се нуждае от достъп до микрофона, за да покаже списък с устройствата ти." @@ -1837,6 +1862,11 @@ msgstr "{productName} вече се намира на {host}. Инсталира msgid "{productName} Partner" msgstr "Партньор на {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} регистрира клавишните си комбинации в Hyprland. Задай им клавиши в конфигурацията на Hyprland, за да ги използваш, докато {productName} не е на фокус." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Профил, който се откроява" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Изисква се redirect_uri, когато обхватът на бота не е единственият обхват." @@ -2847,7 +2877,7 @@ msgstr "Само в отворените DM" msgid "Across every community you're in" msgstr "Във всяка общност, в която членуваш" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Действие" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Споделяне на активност" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Праг на активност" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Добави допълнително ниво на защита към акаунта си" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Добавяне на друга персонализирана клавишна комбинация" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Адрес" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Настрой всяка опция ръчно: потискане на шума, потискане на ехото и усилване." @@ -3696,11 +3726,11 @@ msgstr "Разрешаване на {everyoneMention} и {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Разреши на {PRODUCT_NAME} достъп до камерата ти в {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Разреши на {PRODUCT_NAME} достъп до микрофона ти в {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Разреши на {PRODUCT_NAME} достъп до микрофона ти. Провери адресната лента или настройките на браузъра си." @@ -3803,7 +3833,7 @@ msgstr "Позволява на членовете без разрешениет msgid "Allow mentions for this role" msgstr "Разрешаване на споменавания за тази роля" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Разреши микрофона" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Неуспешна оторизация" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Неуспешна оторизация. Опитай отново." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Автоматично разпознаване на език" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Автоматично задаване на праг за активност" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Разрешения за бота" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Разрешенията за бот не могат да бъдат отрицателни." @@ -6869,6 +6899,11 @@ msgstr "Промяна на прякор в групата" msgid "Change icon" msgstr "Смяна на икона" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Промени в системните настройки" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Достъп до канал" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Достъпът до канала е отказан" @@ -7294,7 +7329,7 @@ msgstr "ID на канала е копирано в клипборда" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "линк към канал" @@ -7462,6 +7497,11 @@ msgstr "поле за въвеждане в чата" msgid "Chat shortcuts" msgstr "Клавишни комбинации за чат" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Провери отново" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Избери прозорец в системния инструмент msgid "Choose an account" msgstr "Избери акаунт" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Избери действие." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Избери фон" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Избери guild_id или channel_id, не и двете." @@ -7990,7 +8030,7 @@ msgstr "Изчиства звука от микрофона ти за ясна #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Натисни, за да отвориш палитрата с цвет msgid "Click to react" msgstr "Натисни, за да реагираш" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Натисни за запис" @@ -9110,7 +9150,7 @@ msgstr "Потвърждение" msgid "Confirmation or double-click for community voice joins" msgstr "Потвърждение или двойно щракване при присъединяване към гласов канал в общност" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "В конфликт с {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Свържи камера или виртуално устройство msgid "Connect a display, then try again." msgstr "Свържи дисплей и опитай отново." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Свържи микрофон и опитай отново." @@ -9700,6 +9740,21 @@ msgstr "Копирай линк за покана" msgid "Copy invite URL" msgstr "Копирай URL адреса за покана" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Копирай редовете" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Копирай редовете за hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Копирай редовете за hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Персонализиран източник" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Персонализиран избор" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Действие за персонализирана клавишна комбинация" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Опции за персонализирани клавишни комбинации" @@ -11466,7 +11521,7 @@ msgstr "Стандартни клавишни комбинации" msgid "Default translator" msgstr "Преводач по подразбиране" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "По подразбиране:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Изтриване на запазена медия" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Изтриване на пряк път" @@ -12102,13 +12157,13 @@ msgstr "Състояние за разработчици" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Инструменти за разработчици" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Инструментите за разработчици са достъпни от значката на екипа в заглавката на канала." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Деактивиране на премиум предимствата от бекенда" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Деактивиране на вградения пряк път" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Да се изключат ли изскачащите прозорци при споделяне на екрана?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Деактивиране на пряк път" @@ -12888,7 +12943,7 @@ msgstr "Дари вместо това" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Бутон за изтегляне" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Изтегляне на настолното приложение" @@ -13204,6 +13259,11 @@ msgstr "Нидерландски" msgid "e.g., YubiKey, iPhone, work computer" msgstr "напр. YubiKey, iPhone, служебен компютър" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Всеки клавиш може да задейства само една системна клавишна комбинация." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Редактиране на линк за покана" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Редактиране на клавишна комбинация" @@ -13425,7 +13485,7 @@ msgstr "Формуляр за редактиране на запазена ме msgid "Edit search engine" msgstr "Редактиране на търсачка" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Редактиране на клавишната комбинация" @@ -13753,20 +13813,16 @@ msgstr "Празна редакция" msgid "Empty favorites" msgstr "Няма любими" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Активиране" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Включи разрешението {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Включване на хардуерно ускорение и рес msgid "Enable hardware acceleration?" msgstr "Да се активира ли хардуерното ускорение?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Разреши достъп до въвеждане, за да използваш системните клавишни комбинации в Wayland. След като промениш достъпа до въвеждане, затвори напълно и рестартирай {productName}, за да могат клавишните комбинации да го използват." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Разгъване при натискане на Shift" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Активиране на пряк път" @@ -14357,7 +14408,7 @@ msgstr "Звук от цялата система" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Звук при влизане" @@ -14467,7 +14518,7 @@ msgstr "Вечер" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Изравнява силата на микрофона ти, за да не звучиш твърде тихо." @@ -15682,6 +15733,26 @@ msgstr "Само за британски очи..." msgid "For me" msgstr "За мен" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "За „Натисни и изключи микрофона“ използвай единичен клавиш без Ctrl, Alt или Shift. При комбинация първо пусни основния клавиш, иначе микрофонът може да остане изключен." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "За „Натисни и говори“ използвай единичен клавиш без Ctrl, Alt или Shift. При комбинация първо пусни основния клавиш, иначе микрофонът може да остане включен." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "За тази клавишна комбинация използвай единичен клавиш без Ctrl, Alt или Shift. При комбинация първо пусни основния клавиш, иначе тя може да остане активна." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "За приоритета на гласовата активност използвай единичен клавиш без Ctrl, Alt или Shift. При комбинация първо пусни основния клавиш, иначе приоритетът може да остане включен." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "За твоята сигурност потвърди самоличността си, за да продължиш." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Глобален профил" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Глобална клавишна комбинация" @@ -17434,6 +17505,11 @@ msgstr "Задръж Shift и натисни X, за да избереш при msgid "Hold to record. Drag up to lock, or release to send." msgstr "Задръж за запис. Плъзни нагоре за заключване или пусни за изпращане." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Задържането на Shift, Ctrl или Alt блокира тази клавишна комбинация. Добави алтернативни в системните настройки." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Ура! Известията са включени" msgid "Hyperlink" msgstr "Хипервръзка" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland няма прозорец за клавишни комбинации. Добави редовете за твоята версия на Hyprland в конфигурацията му и замени KEY с желания клавиш." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland няма прозорец за клавишни комбинации. Задай клавишите в конфигурацията на Hyprland." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (преди Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 и по-нови)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Приемам <0>Политиката за поверителност" @@ -17576,7 +17672,7 @@ msgstr "Размислих" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Разбрах" @@ -18000,7 +18096,7 @@ msgstr "В" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "В режим „Операционна система“ посочените по-горе речници са препоръчителни. {PRODUCT_NAME} ще предаде тези езикови етикети на проверката на правописа на твоята ОС, когато това се поддържа. Етикетите, които тя не разпознава, се игнорират." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "В браузър „Натисни и говори“ работи само докато разделът на {PRODUCT_NAME} е активен. Инсталирай настолното приложение за „Натисни и говори“ в цялата система." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Въвеждане" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Достъпът до въвеждане не можа да бъде активиран автоматично. Промени го ръчно, след което затвори напълно и рестартирай {productName}, за да могат клавишните комбинации да го използват." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Достъпът до въвеждане не можа да бъде активиран. Опитай отново от настройките „{settingsMenuName}“, след което затвори напълно и рестартирай {productName}." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Необходим е достъп до въвеждане за системните комбинации с клавиатура и мишка в Wayland. След като промениш достъпа до въвеждане, затвори напълно и рестартирай {productName}, за да могат комбинациите да го използват." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Достъпът до въвеждане беше променен. Затвори напълно и рестартирай {productName}, за да могат системните клавишни комбинации да го използват." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Вход и изход" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Невалидна заявка за оторизация" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Невалидни разрешения за бота." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Невалиден или изтекъл код. Опитай отново." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Невалиден redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Линкът за покана е скрит, докато споделяш:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Линкът за покана е скрит, докато споделяш" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Към {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Към {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Към текущия разговор" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Към свързания канал" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Към свързаното съобщение" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Към съобщението от {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Към съобщението в {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Пази ги на сигурно място. Генерирането на нови ще прекъсне съществуващите интеграции." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Продължава да предава за кратко след отпускане на бутона." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Клавиатура" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Достъпът до клавиатурата не можа да стартира. Клавишните комбинации работят само докато {productName} е на фокус." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Нека общността се самомодерира" msgid "Let the member hear others in this community again" msgstr "Позволи на члена отново да чува другите в тази общност" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Позволява на {productName} да чете клавиатурата директно. Системата ти вече разрешава това." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Позволява на персонализирания CSS да показва прозрачни зони на прозореца след рестартиране на настолното приложение." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Стил на линковете" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Линкът не е наличен" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Външен вид" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Изглежда, че използваш {0}. Наслагването на обработката на {PRODUCT_NAME} отгоре може да причини пулсиране или изкривяване на звука. Директният вход предава звука ти непроменен." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Оформление на съобщенията" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "линк към съобщение" @@ -21332,7 +21416,7 @@ msgstr "Микрофон" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Тест на микрофон" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Разни" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Липсващ client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Още опции" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Още опции за клавишната комбинация" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Няма разрешение {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Няма достъп" @@ -22984,7 +23068,7 @@ msgstr "Няма намерени съвпадения" msgid "No mentions yet" msgstr "Все още няма споменавания" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Не е намерен микрофон" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Няма намерени настройки" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Няма зададен пряк път" @@ -23358,6 +23442,11 @@ msgstr "Не е цяло число" msgid "Not accepted" msgstr "Не е прието" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Не е зададено" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Отвори {host} в браузъра си." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Отвори {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Отваряне на {labelText} в настройките" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "връзка към страница" @@ -25611,11 +25700,11 @@ msgstr "Присъствие" msgid "Press {shortcut} to react" msgstr "Натисни {shortcut}, за да реагираш" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Натисни клавиш, бутон на мишката или бутон на геймпада, за да зададеш. Esc отменя." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Натисни клавишна комбинация…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Клавиш за „Натисни и говори“" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Натисни и говори" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Натисни и говори (ограничено)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "„Натисни и говори“ все още няма системен клавиш. За да зададеш такъв, отиди в Настройки > Приложения > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "„Натисни и говори“ все още няма системен клавиш. За да зададеш такъв, отиди в Системни настройки > Клавиатура > Клавишни комбинации > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "„Натисни и говори“ все още няма системен клавиш. За да зададеш такъв, отвори настройките за клавишни комбинации на работната си среда." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "„Натисни и говори“ се игнорира при директен вход. Превключи на „Фокусиран глас“ или „По избор“, за да го използваш." @@ -26293,21 +26397,51 @@ msgstr "„Натисни и говори“ се игнорира при дир msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Включено е „Натисни и говори“. Задръж {pushToTalkHint}, за да говориш." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "„Натисни и говори“ е включено. Задръж клавиша за „Натисни и говори“, за да говориш." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "„Натисни и говори“ работи само докато {productName} е на фокус, защото работната ти среда не настрои системни клавишни комбинации." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "„Натисни и говори“ работи само докато {productName} е на фокус, докато не активираш {permissionName}. След като промениш разрешението, затвори напълно и рестартирай {productName}, за да могат системните бързи клавиши да го използват." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "„Натисни и говори“ работи само когато {productName} е на фокус, докато не настроиш системни клавишни комбинации." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "„Натисни и говори“ работи само докато разделът на браузъра е на фокус." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "„Натисни и говори“ ще използва клавиша, зададен в {productName}. Първо задай такъв, иначе микрофонът ще работи с гласова активност." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "„Натисни и говори“ няма да включи микрофона ти, докато звукът ти е изключен. Включи звука, за да говориш." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "„Натисни и говори“ работи извън {productName} само след като му зададеш клавиш в конфигурацията на Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "„Натисни и говори“ работи извън {productName} само след като му зададеш клавиш в конфигурацията на Hyprland. Добави реда за твоята версия на Hyprland и замени KEY с желания клавиш." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Суров JSON на вградения елемент" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Активиране на вградения пряк път" @@ -26565,13 +26699,6 @@ msgstr "Скорошни споменавания" msgid "Recent searches" msgstr "Скорошни търсения" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Проверка отново" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Препоръчително" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Препоръчително. Изчиства звука от микрофона ти за ясна реч." @@ -26600,6 +26727,11 @@ msgstr "Свързване отново" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Връзката е възстановена. Избери екран отново, ако искаш да възобновиш предишния си поток." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Повторно свързване с работната среда…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Свързване отново…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Запис на пряк път" @@ -26851,7 +26983,7 @@ msgstr "Относително време" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Забавяне при отпускане" @@ -27739,13 +27871,13 @@ msgstr "Изпратено отново" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Нулиране" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Възстановяване на вградения пряк път за „{label}“" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Възстановяване на действителните стойности" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Възстановяване на вградения пряк път" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Избери акаунта, с който искаш да влезеш на новото си устройство." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Избери режим за обработка на глас" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Изпраща звука от микрофона без никаква обработка." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Изпраща звука ти без промени. Най-добре е, ако използваш външен аудио софтуер." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Зададено от {preset} председ. Избирането на стойност тук превключва към „Персонализирано“." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Задаване на персонализирана клавишна комбинация" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Задаване на персонализирана клавишна комбинация за „{label}“" @@ -30066,6 +30198,11 @@ msgstr "Зададе гласовия регион на {region}" msgid "Set the voice region to automatic" msgstr "Зададе гласовия регион на автоматичен" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Настрой" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Настройки" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "линк към настройките" @@ -30330,8 +30467,8 @@ msgstr "Кратки кодове" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Превключване към удобен изглед" msgid "Switch to dense layout" msgstr "Превключване към сбит изглед" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Превключване към директен вход" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Инсталация, управлявана от системата" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Системни клавишни комбинации" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Системните клавишни комбинации не можаха да бъдат настроени." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Системно: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Системно: не е зададено" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Страницата, която търсиш, не съществува или е преместена." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Предоставеният redirect_uri не е регистриран за това приложение." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Предоставеният redirect_uri трябва да използва HTTP или HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Анимацията е твърде дълга или твърде голяма, за да бъде изрязана. Опитайте с по-кратък или по-малък файл." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Това приложение" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Този прикачен файл е изтекъл" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Този бот не е публичен и не може да бъде добавен от други потребители." @@ -34081,6 +34233,16 @@ msgstr "Този разговор вече не е наличен." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Тази версия на настолното приложение не може да инсталира актуализации в {productName}. Вместо това изтегли най-новия инсталатор." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Тази работна среда не поддържа системни клавишни комбинации. „Натисни и говори“ работи само докато {productName} е на фокус." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Тази работна среда не поддържа системни клавишни комбинации. Клавишните комбинации пак работят, докато {productName} е на фокус." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Това е последният ти ключ за достъп, а к msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Това е последният ти ключ за достъп. След като го изтриеш, няма да можеш да използваш ключ за достъп като втори фактор." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Този клавиш работи, докато {productName} е на фокус. Системният клавиш се задава от работната ти среда, в {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Този канал с линкове е маркиран за съдържание за възрастни и може да отваря материали, които може да са неподходящи за някои потребители." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Този линк не е наличен за акаунта ти." @@ -34778,6 +34945,21 @@ msgstr "Заглавна лента" msgid "Title bar platform" msgstr "Платформа на заглавната лента" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "За да смениш клавишите, отиди в Настройки > Приложения > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "За да смениш клавишите, отиди в Системни настройки > Клавиатура > Клавишни комбинации > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "За да смениш клавишите, отвори настройките за клавишни комбинации на работната си среда." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "За да създадеш уебхук, отвори <0>{channelWebhooksSettingsPath}. Тук все още можеш да редактираш и организираш всички съществуващи уебхукове." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Неизвестен" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Неизвестно приложение, изтекла сесия или невалидна заявка за оторизация." @@ -36410,6 +36592,11 @@ msgstr "До 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "До 720p при 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Обнови {productName}, за да използваш системни клавишни комбинации в Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Използване на тъмна тема в цялото прило msgid "Use default" msgstr "Използване на стойността по подразбиране" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Използвай директен достъп до входните устройства" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Участници в гласов чат" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Обработка на глас" @@ -38264,7 +38456,7 @@ msgstr "Включване на звука в гласов чат" msgid "Voice unmute" msgstr "Включване на микрофона в гласов чат" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Гласът използва системното ви изходно устройство в този браузър. Променете го в настройките на системата си." @@ -38278,7 +38470,7 @@ msgstr "Анулирана" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Изчаква се да изтече бавният режим в ед msgid "Waiting for you to..." msgstr "Чакаме те да..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Изчакване на работната среда…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Кои разговори" msgid "Which rule was broken?" msgstr "Кое правило беше нарушено?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Докато „Натисни и говори“ е включено, микрофонът ти се управлява от клавишната комбинация, така че бутонът за изключване на микрофона е неактивен." @@ -39094,6 +39291,11 @@ msgstr "Без това разрешение се изисква „Натисн msgid "Wordmark" msgstr "Текстово лого" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Работи, докато {productName} е на фокус" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Промени тага си, докато ползваше {PREMIUM_PRODUCT_NAME}. Тагът ти ще бъде преизбран на случаен принцип, когато {PREMIUM_PRODUCT_NAME} ти изтече, освен ако не се поднови." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Нямаш достъп до канала, където е изпратено това съобщение." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Абонаментът ти за {premiumProductName} изтече. Загуби всички предимства на {premiumProductName}. Активирай отново абонамента си, за да възстановиш достъпа." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Сесията ти в {productName} изтече. Влез отново, за да продължиш с оторизацията на това приложение." @@ -40144,6 +40346,16 @@ msgstr "Общността ти трябва да има поне {minMemberCoun msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Кредитната ти карта е проверена. Ако картата е валидна, съдържанието за възрастни ще се отключи скоро. Можеш да затвориш този раздел и да се върнеш в приложението." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Работната ти среда не настрои системни клавишни комбинации." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Работната ти среда изпълнява тези клавишни комбинации, дори когато {productName} не е на фокус." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/cs/messages.po b/fluxer_app/src/features/i18n/locales/cs/messages.po index 0c5576de8..f12ab856e 100644 --- a/fluxer_app/src/features/i18n/locales/cs/messages.po +++ b/fluxer_app/src/features/i18n/locales/cs/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# kanál ho sleduje} other {# kanálů ho sleduje}} msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {Nalezena # komunita} few {Nalezeny # komunity} many {Nalezeno # komunity} other {Nalezeno # komunit}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# komunita je dočasně nedostupná kvůli poruše fluxkondenzátoru.} few {# komunity jsou dočasně nedostupné kvůli poruše fluxkondenzátoru.} many {# komunity je dočasně nedostupných kvůli poruše fluxkondenzátoru.} other {# komunit je dočasně nedostupných kvůli poruše fluxkondenzátoru.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} neposílá push oznámení do vašich mobilních zařízen msgid "{productName} Bug Hunter" msgstr "Lovec chyb {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "Aplikace {productName} může požádat vaše desktopové prostředí, aby spouštělo její zkratky, i když okno aplikace {productName} není aktivní. Desktopové prostředí vás požádá o potvrzení a umožní vám vybrat klávesy." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "Aplikace {productName} už nemůže číst vaši klávesnici, a proto místo toho sleduje klávesové zkratky přes X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "Aplikace {productName} už nemůže číst vaši klávesnici, a proto se místo toho používají zkratky vašeho desktopového prostředí." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "Aplikace {productName} už nemůže číst vaši klávesnici. Klávesové zkratky fungují jen při aktivním okně aplikace {productName}." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "Aplikace {productName} může zaregistrovat své zkratky v Hyprlandu, aby fungovaly, i když okno aplikace {productName} není aktivní. Klávesy pak přiřadíte v konfiguraci Hyprlandu." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "Aplikace {productName} potřebuje přístup ke kameře, aby mohla zobrazit vaše zařízení." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "Aplikace {productName} potřebuje přístup k mikrofonu, aby mohla zobrazit vaše zařízení." @@ -1837,6 +1862,11 @@ msgstr "{productName} se teď nachází na adrese {host}. Nainstalujte si ho odt msgid "{productName} Partner" msgstr "Partner {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "Aplikace {productName} zaregistrovala své zkratky v Hyprlandu. Přiřaďte jim klávesy v konfiguraci Hyprlandu, abyste je mohli používat, i když okno aplikace {productName} není aktivní." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Profil, který vynikne" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Parametr redirect_uri je povinný, pokud „bot“ není jediným rozsahem oprávnění." @@ -2847,7 +2877,7 @@ msgstr "Pouze ve všech otevřených soukromých konverzacích" msgid "Across every community you're in" msgstr "Ve všech komunitách, ve kterých jste" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Akce" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Sdílení aktivit" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Práh aktivity" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Přidejte ke svému účtu další vrstvu zabezpečení" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Přidat další vlastní zkratku" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Adresa" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Upravte si každé nastavení sami: potlačení šumu, potlačení ozvěny a zesílení." @@ -3696,11 +3726,11 @@ msgstr "Povolit zmínky {everyoneMention} a {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Povolte aplikaci {PRODUCT_NAME} přístup k vaší kameře v {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Povolte aplikaci {PRODUCT_NAME} přístup k mikrofonu v: {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Povolte aplikaci {PRODUCT_NAME} přístup k mikrofonu. Zkontrolujte adresní řádek nebo nastavení prohlížeče." @@ -3803,7 +3833,7 @@ msgstr "Povolit členům bez oprávnění {readMessageHistoryPermissionLabel} zo msgid "Allow mentions for this role" msgstr "Povolit zmínky pro tuto roli" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Povolit mikrofon" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Autorizace se nezdařila" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Autorizace se nezdařila. Zkuste to znovu." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Automaticky rozpoznat jazyk" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Automaticky nastavit práh aktivity" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Oprávnění bota" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Oprávnění bota nemohou být záporná." @@ -6869,6 +6899,11 @@ msgstr "Změnit přezdívku skupiny" msgid "Change icon" msgstr "Změnit ikonu" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Změnit v systémovém nastavení" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Přístup k kanálu" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Přístup k tomuto kanálu odepřen" @@ -7294,7 +7329,7 @@ msgstr "ID kanálu zkopírováno do schránky" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "odkaz na kanál" @@ -7462,6 +7497,11 @@ msgstr "pole pro psaní zpráv" msgid "Chat shortcuts" msgstr "Zkratky chatu" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Zkontrolovat znovu" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Vyberte okno v systémovém výběru" msgid "Choose an account" msgstr "Vyberte účet" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Vyberte akci." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Vybrat pozadí" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Zvolte buď guild_id, nebo channel_id, ne obojí." @@ -7990,7 +8030,7 @@ msgstr "Vyčistí zvuk mikrofonu, aby vám bylo dobře rozumět." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Kliknutím otevřete výběr barev a zvolte novou barvu." msgid "Click to react" msgstr "Kliknutím zareagujete" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Kliknutím nahrát" @@ -9110,7 +9150,7 @@ msgstr "Potvrzení" msgid "Confirmation or double-click for community voice joins" msgstr "Potvrzení nebo dvojité kliknutí pro připojení k hlasovým kanálům komunity" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "V konfliktu s {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Připojte kameru nebo virtuální záznamové zařízení a zkuste to zn msgid "Connect a display, then try again." msgstr "Připojte displej a zkuste to znovu." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Připojte mikrofon a zkuste to znovu." @@ -9700,6 +9740,21 @@ msgstr "Kopírovat odkaz na pozvánku" msgid "Copy invite URL" msgstr "Kopírovat URL pozvánky" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Kopírovat řádky" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Kopírovat řádky pro hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Kopírovat řádky pro hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Vlastní zdroj" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Vlastní výběr" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Vlastní akce zkratky" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Možnosti vlastních zkratek" @@ -11466,7 +11521,7 @@ msgstr "Výchozí zkratky" msgid "Default translator" msgstr "Výchozí překladač" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Výchozí:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Smazat uložená média" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Smazat zkratku" @@ -12102,13 +12157,13 @@ msgstr "Stav pro vývojáře" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Nástroje pro vývojáře" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Nástroje pro vývojáře jsou dostupné z odznaku pro zaměstnance v záhlaví kanálu." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Vypnout prémiové výhody na backendu" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Vypnout vestavěnou zkratku" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Vypnout samostatná okna obrazu v obraze pro sdílení obrazovky?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Vypnout zkratku" @@ -12888,7 +12943,7 @@ msgstr "Darovat místo toho" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Tlačítko pro stažení" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Stáhnout desktopovou aplikaci" @@ -13204,6 +13259,11 @@ msgstr "Nizozemština" msgid "e.g., YubiKey, iPhone, work computer" msgstr "např. YubiKey, iPhone, pracovní počítač" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Každá klávesa může spouštět jen jednu zkratku pro celý systém." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Upravit odkaz na pozvánku" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Upravit klávesovou zkratku" @@ -13425,7 +13485,7 @@ msgstr "Formulář pro úpravu uloženého média" msgid "Edit search engine" msgstr "Upravit vyhledávač" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Upravit zkratku" @@ -13753,20 +13813,16 @@ msgstr "Prázdná úprava" msgid "Empty favorites" msgstr "Oblíbené položky jsou prázdné" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Zapnout" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Povolit oprávnění {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Zapnout hardwarovou akceleraci a restartovat" msgid "Enable hardware acceleration?" msgstr "Zapnout hardwarovou akceleraci?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Povolte přístup ke vstupním zařízením, abyste mohli používat globální zkratky na Waylandu. Po změně oprávnění aplikaci {productName} úplně ukončete a znovu spusťte, aby zkratky mohly toto oprávnění využít." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Povolit Shift pro rozbalení" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Zapnout zkratku" @@ -14357,7 +14408,7 @@ msgstr "Veškerý zvuk systému" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Zvuk při vstupu" @@ -14467,7 +14518,7 @@ msgstr "Večer" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Vyrovnává hlasitost mikrofonu, abyste nebyli příliš potichu." @@ -15682,6 +15733,26 @@ msgstr "Pouze pro britské oči..." msgid "For me" msgstr "Pro mě" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Pro funkci Stisknutím ztlumit použijte jednu klávesu bez Ctrl, Alt nebo Shift. U kombinace kláves nejprve uvolněte hlavní klávesu, jinak může mikrofon zůstat ztlumený." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Pro funkci Stisknutím mluvit použijte jednu klávesu bez Ctrl, Alt nebo Shift. U kombinace kláves nejprve uvolněte hlavní klávesu, jinak může mikrofon zůstat zapnutý." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Pro tuto zkratku použijte jednu klávesu bez Ctrl, Alt nebo Shift. U kombinace kláves nejprve uvolněte hlavní klávesu, jinak může zkratka zůstat aktivní." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Pro funkci Priorita hlasové aktivity použijte jednu klávesu bez Ctrl, Alt nebo Shift. U kombinace kláves nejprve uvolněte hlavní klávesu, jinak může priorita zůstat zapnutá." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Pro vaši bezpečnost prosím potvrďte, že jste to vy, abyste mohli pokračovat." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Globální profil" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Globální klávesová zkratka" @@ -17434,6 +17505,11 @@ msgstr "Podržte klávesu Shift a stiskněte X pro výběr aplikací. Stisknutí msgid "Hold to record. Drag up to lock, or release to send." msgstr "Podržte pro nahrávání. Táhněte nahoru pro uzamčení, nebo pusťte pro odeslání." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Podržení klávesy Shift, Ctrl nebo Alt tuto zkratku blokuje. Alternativy přidejte v nastavení systému." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Hurá! Oznámení jsou zapnutá" msgid "Hyperlink" msgstr "Hypertextový odkaz" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland nemá dialog pro klávesové zkratky. Přidejte do konfigurace Hyprlandu řádky pro svou verzi Hyprlandu a nahraďte KEY požadovanou klávesou." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland nemá dialog pro klávesové zkratky. Klávesy přiřaďte v konfiguraci Hyprlandu." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (Hyprland před verzí 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 a novější)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Souhlasím se <0>zásadami ochrany osobních údajů" @@ -17576,7 +17672,7 @@ msgstr "Rozmyslel(a) jsem si to" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Rozumím" @@ -18000,7 +18096,7 @@ msgstr "V" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "V režimu „Operační systém“ slouží výše uvedené slovníky pouze jako doporučení. Aplikace {PRODUCT_NAME} předá tyto jazykové značky systémové kontrole pravopisu, pokud to podporuje. Značky, které nerozpozná, ignoruje." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "V prohlížeči funguje funkce Stisknutím mluvit pouze tehdy, když je karta {PRODUCT_NAME} aktivní. Pro použití této funkce v celém systému si nainstalujte desktopovou aplikaci." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Zadávání" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Přístup ke vstupním zařízením se nepodařilo zapnout automaticky. Změňte ho ručně a poté aplikaci {productName} úplně ukončete a znovu spusťte, aby zkratky mohly toto oprávnění využít." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Přístup ke vstupním zařízením se nepodařilo zapnout. Zkuste to znovu v nastavení {settingsMenuName} a poté aplikaci {productName} úplně ukončete a znovu spusťte." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Pro globální klávesové zkratky a zkratky myši na Waylandu je potřeba přístup ke vstupním zařízením. Po změně oprávnění aplikaci {productName} úplně ukončete a znovu spusťte, aby zkratky mohly toto oprávnění využít." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Přístup ke vstupním zařízením byl změněn. Aplikaci {productName} úplně ukončete a znovu spusťte, aby globální zkratky mohly toto oprávnění využít." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Vstup a výstup" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Neplatný požadavek na autorizaci" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Neplatná oprávnění bota." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Kód je neplatný nebo jeho platnost vypršela. Zkuste to znovu." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Neplatný parametr redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Odkaz na pozvánku je skrytý, když sdílíte:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Odkaz na pozvánku je při sdílení maskován" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Přejít na {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Přejít na {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Přejít na aktuální hovor" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Přejít na odkazovaný kanál" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Přejít na odkazovanou zprávu" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Přejít ke zprávě od {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Přejít na zprávu v {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Uchovávejte je v bezpečí. Opětovné vygenerování přeruší stávající integrace." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Udržovat vysílání krátce po uvolnění tlačítka." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Klávesnice" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Přístup ke klávesnici se nepodařilo spustit. Klávesové zkratky fungují jen při aktivním okně aplikace {productName}." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Nechte komunitu, ať se moderuje sama" msgid "Let the member hear others in this community again" msgstr "Členovi bylo znovu umožněno slyšet ostatní v této komunitě" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Umožňuje aplikaci {productName} číst vaši klávesnici přímo. Váš systém to již povoluje." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Po restartování desktopové aplikace umožní pomocí vlastního CSS zobrazit průhledné oblasti okna." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Styl odkazů" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Odkaz není k dispozici" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Vzhled a ovládání" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Zdá se, že používáte {0}. Přidání dalšího zpracování zvuku v aplikaci {PRODUCT_NAME} může způsobit kolísání hlasitosti nebo zkreslení. Přímý vstup propouští zvuk beze změn." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Rozložení zpráv" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "odkaz na zprávu" @@ -21332,7 +21416,7 @@ msgstr "Mikrofon" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Test mikrofonu" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Různé" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Chybí client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Další možnosti" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Další možnosti zkratek" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Chybí oprávnění {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Bez přístupu" @@ -22984,7 +23068,7 @@ msgstr "Nic nebylo nalezeno" msgid "No mentions yet" msgstr "Zatím žádné zmínky" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Mikrofon nenalezen" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Nenalezena žádná nastavení" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Zkratka nenastavena" @@ -23358,6 +23442,11 @@ msgstr "Není to celé číslo" msgid "Not accepted" msgstr "Nepřijato" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Nepřiřazeno" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Otevřete {host} ve svém prohlížeči." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Otevřít {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Otevřít {labelText} v nastavení" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "odkaz na stránku" @@ -25611,11 +25700,11 @@ msgstr "Online stav" msgid "Press {shortcut} to react" msgstr "Stiskněte {shortcut} pro reakci" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Pro přiřazení stiskněte klávesu, tlačítko myši nebo tlačítko gamepadu. Klávesou Escape akci zrušíte." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Stiskněte zkratku…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Klávesa pro režim Stisknutím mluvit" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Stisknutím mluvit" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Stisknutím mluvit (omezená funkčnost)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Režim Stisknutím mluvit zatím nemá klávesu pro celý systém. Nastavíte ji v Nastavení > Aplikace > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Režim Stisknutím mluvit zatím nemá klávesu pro celý systém. Nastavíte ji v Nastavení systému > Klávesnice > Klávesové zkratky > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Režim Stisknutím mluvit zatím nemá klávesu pro celý systém. Nastavíte ji v nastavení klávesových zkratek vašeho desktopového prostředí." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "V režimu Přímý vstup se nastavení Stisknutím mluvit ignoruje. Chcete-li ho použít, přepněte na režim Hlas v popředí nebo Vlastní." @@ -26293,21 +26397,51 @@ msgstr "V režimu Přímý vstup se nastavení Stisknutím mluvit ignoruje. Chce msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Režim Stisknutím mluvit je zapnutý. Chcete-li mluvit, podržte {pushToTalkHint}." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Režim Stisknutím mluvit je zapnutý. Chcete-li mluvit, podržte klávesu pro režim Stisknutím mluvit." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Režim Stisknutím mluvit funguje jen při aktivním okně aplikace {productName}, protože vaše desktopové prostředí nenastavilo zkratky pro celý systém." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Dokud nepovolíte oprávnění {permissionName}, funguje režim Stisknutím mluvit jen při aktivním okně aplikace {productName}. Po změně oprávnění aplikaci {productName} zcela ukončete a znovu spusťte, aby oprávnění mohly využívat systémové klávesové zkratky." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Dokud nenastavíte zkratky pro celý systém, funguje režim Stisknutím mluvit jen při aktivním okně aplikace {productName}." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Režim Stisknutím mluvit funguje pouze při aktivní kartě prohlížeče." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Režim Stisknutím mluvit bude používat klávesu nastavenou v aplikaci {productName}. Nejprve ji nastavte, jinak bude mikrofon používat hlasovou aktivitu." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Režim Stisknutím mluvit nezapne mikrofon, pokud máte vypnutý zvuk. Chcete-li mluvit, nejprve zvuk zapněte." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Režim Stisknutím mluvit funguje mimo aplikaci {productName}, až když mu přiřadíte klávesu v konfiguraci Hyprlandu." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Režim Stisknutím mluvit funguje mimo aplikaci {productName}, až když mu přiřadíte klávesu v konfiguraci Hyprlandu. Přidejte řádek pro svou verzi Hyprlandu a nahraďte KEY požadovanou klávesou." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Nezpracovaný JSON vloženého obsahu" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Znovu zapnout vestavěnou zkratku" @@ -26565,13 +26699,6 @@ msgstr "Nedávné zmínky" msgid "Recent searches" msgstr "Nedávná hledání" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Zkontrolovat znovu" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Doporučeno" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Doporučeno. Upravuje zvuk mikrofonu pro srozumitelnou řeč." @@ -26600,6 +26727,11 @@ msgstr "Znovu připojit" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Opětovné připojení proběhlo úspěšně. Pokud chcete pokračovat v předchozím streamu, vyberte obrazovku znovu." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Znovu se připojujeme k desktopovému prostředí…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Opětovné připojování…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Nahrát zkratku" @@ -26851,7 +26983,7 @@ msgstr "Relativní" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Prodleva uvolnění" @@ -27739,13 +27871,13 @@ msgstr "Znovu odesláno" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Resetovat" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Obnovit \"{label}\" na výchozí" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Obnovit na skutečné hodnoty" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Obnovit výchozí" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Vyberte účet, ke kterému se chcete přihlásit na novém zařízení." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Vyberte režim zpracování hlasu" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Odesílá nezpracovaný zvuk z mikrofonu bez úprav." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Odesílá váš zvuk beze změn. Nejlepší, pokud používáte externí zvukový software." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Nastaveno předvolbou {preset}. Výběr hodnoty zde přepne na Vlastní." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Nastavit vlastní zkratku" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Nastavit vlastní zkratku pro \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "Hlasový region nastaven na {region}" msgid "Set the voice region to automatic" msgstr "Hlasový region nastaven na automatický" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Nastavit" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Nastavení" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "odkaz na nastavení" @@ -30330,8 +30467,8 @@ msgstr "Zkratky" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Přepnout na pohodlné rozvržení" msgid "Switch to dense layout" msgstr "Přepnout na zhuštěné rozvržení" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Přepnout na přímý vstup" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Instalace spravovaná systémem" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Zkratky pro celý systém" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Zkratky pro celý systém se nepodařilo nastavit." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "V celém systému: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "V celém systému: nepřiřazeno" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Stránka, kterou hledáte, neexistuje nebo byla přesunuta." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Zadané redirect_uri není pro tuto aplikaci zaregistrováno." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Zadané redirect_uri musí používat HTTP nebo HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Tato animace je příliš dlouhá nebo příliš velká na oříznutí. Zkuste kratší nebo menší soubor." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Tato aplikace" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Tato příloha vypršela" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Tento bot není veřejný a ostatní uživatelé ho nemohou přidat." @@ -34081,6 +34233,16 @@ msgstr "Tato konverzace již není k dispozici." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Tato verze desktopové aplikace neumožňuje instalovat aktualizace přímo v aplikaci {productName}. Stáhněte si nejnovější instalátor desktopové aplikace." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Toto desktopové prostředí nenabízí zkratky pro celý systém. Režim Stisknutím mluvit funguje jen při aktivním okně aplikace {productName}." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Toto desktopové prostředí nenabízí zkratky pro celý systém. Klávesové zkratky dál fungují při aktivním okně aplikace {productName}." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Toto je váš poslední přístupový klíč a přístupové klíče jso msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Toto je váš poslední přístupový klíč. Jakmile o něj přijdete, nebudete moci používat přístupový klíč jako druhý faktor." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Tato klávesa funguje při aktivním okně aplikace {productName}. Klávesu pro celý systém nastavuje vaše desktopové prostředí v části {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Tento kanál s odkazem je označen jako kanál pro dospělé a může otevřít materiál, který nemusí být vhodný pro některé uživatele." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Tento odkaz není pro váš účet dostupný." @@ -34778,6 +34945,21 @@ msgstr "Lišta s názvem" msgid "Title bar platform" msgstr "Platforma záhlaví" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Chcete-li změnit klávesy, přejděte do Nastavení > Aplikace > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Chcete-li změnit klávesy, přejděte do Nastavení systému > Klávesnice > Klávesové zkratky > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Chcete-li změnit klávesy, otevřete nastavení klávesových zkratek vašeho desktopového prostředí." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Chcete-li vytvořit webhook, otevřete <0>{channelWebhooksSettingsPath}. Zde můžete stále upravovat a organizovat všechny existující webhooky." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Neznámý" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Neznámá aplikace, vypršená relace nebo neplatný požadavek na autorizaci." @@ -36410,6 +36592,11 @@ msgstr "Až 4K/60 sn./s" msgid "Up to 720p at 30 FPS" msgstr "Až 720p při 30 snímcích za sekundu" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Aktualizujte {productName}, abyste mohli na Waylandu používat zkratky pro celý systém." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Použít tmavý motiv v celé aplikaci" msgid "Use default" msgstr "Použít výchozí" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Použít přímý přístup ke vstupním zařízením" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Účastníci hlasového chatu" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Zpracování hlasu" @@ -38264,7 +38456,7 @@ msgstr "Zapnutí zvuku hovoru" msgid "Voice unmute" msgstr "Zapnutí mikrofonu" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Hlas používá výchozí výstupní zařízení vašeho systému v tomto prohlížeči. Změňte jej v nastavení systému." @@ -38278,7 +38470,7 @@ msgstr "Zrušeno" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Čekání na vypršení pomalého režimu v jednom nebo více vybranýc msgid "Waiting for you to..." msgstr "Čeká se, až..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Čekáme na vaše desktopové prostředí…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Které konverzace" msgid "Which rule was broken?" msgstr "Které pravidlo bylo porušeno?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Když je zapnutý režim Stisknutím mluvit, mikrofon ovládá klávesová zkratka, takže tlačítko ztlumení není dostupné." @@ -39094,6 +39291,11 @@ msgstr "Bez tohoto oprávnění je nutné používat režim Stisknutím mluvit." msgid "Wordmark" msgstr "Textové logo" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Funguje při aktivním okně aplikace {productName}" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Změnili jste si uživatelskou značku, když jste měli {PREMIUM_PRODUCT_NAME}. Náhodně se změní, až vaše předplatné {PREMIUM_PRODUCT_NAME} vyprší, pokud se neobnoví." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Nemáte přístup ke kanálu, ve kterém byla tato zpráva odeslána." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Vaše předplatné {premiumProductName} vypršelo. Ztratili jste všechny výhody {premiumProductName}. Znovu aktivujte své předplatné, abyste získali přístup." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Vaše relace {productName} vypršela. Přihlaste se znovu a pokračujte v autorizaci této aplikace." @@ -40144,6 +40346,16 @@ msgstr "Vaše komunita musí mít alespoň {minMemberCount, plural, one {# člen msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Vaše kreditní karta byla zkontrolována. Pokud je platná, obsah pro dospělé se brzy odemkne. Můžete zavřít tuto kartu prohlížeče a vrátit se do aplikace." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Vaše desktopové prostředí nenastavilo zkratky pro celý systém." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Vaše desktopové prostředí spouští tyto zkratky, i když okno aplikace {productName} není aktivní." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/da/messages.po b/fluxer_app/src/features/i18n/locales/da/messages.po index c8a957c1a..47d3c495c 100644 --- a/fluxer_app/src/features/i18n/locales/da/messages.po +++ b/fluxer_app/src/features/i18n/locales/da/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# kanal følger} other {# kanaler følger}} denne k msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# fællesskab fundet} other {# fællesskaber fundet}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# fællesskab er midlertidigt utilgængeligt på grund af en fejl i fluxkondensatoren.} other {# fællesskaber er midlertidigt utilgængelige på grund af en fejl i fluxkondensatoren.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} forhindrer, at der sendes push-notifikationer til dine mob msgid "{productName} Bug Hunter" msgstr "Fejljæger hos {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} kan bede dit skrivebord om at køre sine genveje, mens {productName} ikke er i fokus. Dit skrivebord beder dig bekræfte og lader dig vælge tasterne." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} kan ikke længere læse dit tastatur, så det lytter i stedet efter genveje via X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} kan ikke længere læse dit tastatur, så skrivebordets genveje bruges i stedet." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} kan ikke længere læse dit tastatur. Genveje virker kun, mens {productName} er i fokus." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} kan registrere sine genveje hos Hyprland, så de kører, selvom {productName} ikke er i fokus. Du binder derefter tasterne i din Hyprland-konfiguration." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} skal have adgang til kameraet for at vise dine enheder." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} skal have adgang til mikrofonen for at vise dine enheder." @@ -1837,6 +1862,11 @@ msgstr "{productName} findes nu på {host}. Installer det derfra." msgid "{productName} Partner" msgstr "{productName}-partner" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} har registreret sine genveje hos Hyprland. Bind dem i din Hyprland-konfiguration for at bruge dem, mens {productName} ikke er i fokus." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "En profil, der skiller sig ud" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri er påkrævet, når \"bot\" ikke er det eneste rettighedsområde." @@ -2847,7 +2877,7 @@ msgstr "Kun på tværs af alle åbne DM'er" msgid "Across every community you're in" msgstr "I alle fællesskaber, du er med i" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Handling" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Aktivitetsdeling" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Aktivitetstærskel" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Tilføj et ekstra sikkerhedslag til din konto" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Tilføj endnu en tilpasset genvej" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Adresse" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Juster selv hver indstilling: støjundertrykkelse, ekkoannullering og forstærkning." @@ -3696,11 +3726,11 @@ msgstr "Tillad {everyoneMention} og {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Tillad {PRODUCT_NAME} at få adgang til dit kamera i {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Tillad {PRODUCT_NAME} at få adgang til din mikrofon i {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Giv {PRODUCT_NAME} adgang til din mikrofon. Tjek din browsers adresselinje eller indstillinger." @@ -3803,7 +3833,7 @@ msgstr "Tillad medlemmer uden tilladelsen {readMessageHistoryPermissionLabel} at msgid "Allow mentions for this role" msgstr "Tillad omtaler for denne rolle" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Tillad mikrofon" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Godkendelse mislykkedes" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Autorisation mislykkedes. Prøv igen." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Registrer sprog automatisk" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Indstil aktivitetstærskel automatisk" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Bot-tilladelser" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Bot-tilladelser kan ikke være negative." @@ -6869,6 +6899,11 @@ msgstr "Skift kaldenavn i gruppen" msgid "Change icon" msgstr "Skift ikon" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Ændr i systemindstillinger" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Kanaladgang" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Adgang til kanal nægtet" @@ -7294,7 +7329,7 @@ msgstr "Kanal-id kopieret til udklipsholder" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "kanallink" @@ -7462,6 +7497,11 @@ msgstr "chatinput" msgid "Chat shortcuts" msgstr "Genveje til chat" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Tjek igen" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Vælg et vindue i din systemvælger" msgid "Choose an account" msgstr "Vælg en konto" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Vælg en handling." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Vælg baggrund" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Vælg enten guild_id eller channel_id, ikke begge." @@ -7990,7 +8030,7 @@ msgstr "Renser mikrofonens lyd, så talen bliver tydelig." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Klik for at åbne farvevælgeren og vælge en ny farve." msgid "Click to react" msgstr "Klik for at reagere" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Klik for at optage" @@ -9110,7 +9150,7 @@ msgstr "Bekræftelse" msgid "Confirmation or double-click for community voice joins" msgstr "Bekræftelse eller dobbeltklik ved deltagelse i fællesskabets talekanaler" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Konflikter med {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Tilslut et kamera eller en virtuel optagelsesenhed, og prøv igen." msgid "Connect a display, then try again." msgstr "Tilslut en skærm, og prøv igen." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Tilslut en mikrofon og prøv igen." @@ -9700,6 +9740,21 @@ msgstr "Kopiér invitationslink" msgid "Copy invite URL" msgstr "Kopiér invitationslink" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Kopiér linjer" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Kopiér linjer til hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Kopiér linjer til hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Brugerdefineret" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Brugerdefineret valg" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Handling for tilpasset genvej" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Indstillinger for tilpassede genveje" @@ -11466,7 +11521,7 @@ msgstr "Standardgenveje" msgid "Default translator" msgstr "Standardoversætter" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Standard:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Slet gemte medier" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Slet genvej" @@ -12102,13 +12157,13 @@ msgstr "Udviklerstatus" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Udviklerværktøjer" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Udviklerværktøjerne er tilgængelige via medarbejderbadget i kanalens topbjælke." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Deaktiver backend-premiumfordele" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Deaktiver indbygget genvej" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Vil du deaktivere separate billede-i-billede-vinduer til skærmdeling?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Deaktiver genvej" @@ -12888,7 +12943,7 @@ msgstr "Donér i stedet" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Download-knap" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Download computerappen" @@ -13204,6 +13259,11 @@ msgstr "Nederlandsk" msgid "e.g., YubiKey, iPhone, work computer" msgstr "f.eks. YubiKey, iPhone, arbejdscomputer" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Hver tast kan kun køre én systemdækkende genvej." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Rediger invitationslink" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Rediger tastaturgenvej" @@ -13425,7 +13485,7 @@ msgstr "Formular til redigering af gemte medier" msgid "Edit search engine" msgstr "Rediger søgemaskine" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Rediger genvej" @@ -13753,20 +13813,16 @@ msgstr "Tom redigering" msgid "Empty favorites" msgstr "Tomme favoritter" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Slå til" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Slå tilladelsen {permissionName} til" @@ -13876,11 +13932,6 @@ msgstr "Slå hardwareacceleration til, og genstart" msgid "Enable hardware acceleration?" msgstr "Aktiver hardwareacceleration?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Aktivér inputadgang for at bruge systemdækkende genveje på Wayland. Efter ændring af inputadgang skal du lukke og genstarte {productName} helt, så genveje kan bruge den." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Aktiver Shift for at udvide" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Slå genvej til" @@ -14357,7 +14408,7 @@ msgstr "Hele systemets lyd" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Ankomstlyd" @@ -14467,7 +14518,7 @@ msgstr "Aften" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Udjævner mikrofonens lydstyrke, så din tale ikke er for lav." @@ -15682,6 +15733,26 @@ msgstr "Kun for britiske øjne ..." msgid "For me" msgstr "Til mig" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Brug en enkelt tast uden Ctrl, Alt eller Shift til Tryk for at slå mikrofonen fra. Ved en kombination skal du slippe hovedtasten først, ellers kan mikrofonen forblive slået fra." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Brug en enkelt tast uden Ctrl, Alt eller Shift til Tryk for at tale. Ved en kombination skal du slippe hovedtasten først, ellers kan mikrofonen forblive tændt." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Brug en enkelt tast uden Ctrl, Alt eller Shift til denne genvej. Ved en kombination skal du slippe hovedtasten først, ellers kan genvejen forblive aktiv." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Brug en enkelt tast uden Ctrl, Alt eller Shift til Prioritet for taleaktivitet. Ved en kombination skal du slippe hovedtasten først, ellers kan prioriteten forblive slået til." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Af hensyn til din sikkerhed skal du bekræfte, at det er dig, for at fortsætte." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Global profil" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Global genvej" @@ -17434,6 +17505,11 @@ msgstr "Hold Shift nede og tryk på X for at vælge apps. Tryk på {selectAllSho msgid "Hold to record. Drag up to lock, or release to send." msgstr "Hold nede for at optage. Træk op for at låse, eller slip for at sende." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Denne genvej blokeres, mens du holder Shift, Ctrl eller Alt nede. Tilføj alternativer i dine systemindstillinger." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Hurra! Notifikationer er slået til" msgid "Hyperlink" msgstr "Hyperlink" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland har ingen genvejsdialog. Tilføj linjerne for din Hyprland-version til din Hyprland-konfiguration, og erstat KEY med den tast, du vil bruge." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland har ingen genvejsdialog. Bind tasterne i din Hyprland-konfiguration." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (før Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 og senere)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Jeg accepterer <0>fortrolighedspolitikken" @@ -17576,7 +17672,7 @@ msgstr "Jeg har ombestemt mig" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Jeg forstår" @@ -18000,7 +18096,7 @@ msgstr "I" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "I tilstanden \"Operativsystem\" er ordbøgerne ovenfor vejledende. {PRODUCT_NAME} sender disse sprogkoder til din OS-stavekontrol, når det understøttes. Koder, den ikke genkender, ignoreres." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "I en browser virker Tryk for at tale kun, når fanen {PRODUCT_NAME} er i fokus. Installer computerappen for at bruge Tryk for at tale i hele systemet." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Input" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Inputadgang kunne ikke aktiveres automatisk. Giv adgang manuelt, og luk derefter {productName} helt ned og genstart appen, så genvejene kan bruge den." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Inputadgang kunne ikke aktiveres. Prøv igen under indstillingerne for {settingsMenuName}, og luk derefter {productName} helt ned og genstart appen." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Inputadgang er påkrævet for tastatur- og musegenveje i hele systemet på Wayland. Når du har ændret inputadgangen, skal du lukke {productName} helt ned og genstarte appen, så genvejene kan bruge den." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Inputadgangen er ændret. Luk {productName} helt ned og genstart appen, så genveje i hele systemet kan bruge den." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Input og output" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Ugyldig godkendelsesanmodning" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Ugyldige bot-tilladelser." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Ugyldig eller udløbet kode. Prøv igen." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Ugyldig redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Invitationslinket er skjult under deling:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Invitationslink maskeret under deling" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Gå til {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Gå til {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Gå til det aktuelle opkald" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Gå til den linkede kanal" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Gå til den linkede besked" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Gå til beskeden fra {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Gå til beskeden i {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Opbevar dem sikkert. Hvis du genererer nye, holder eksisterende integrationer op med at virke." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Fortsæt med at sende kort tid efter, at knappen slippes." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Tastatur" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Tastaturadgang kunne ikke startes. Genveje virker kun, mens {productName} er i fokus." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Lad fællesskabet selv moderere" msgid "Let the member hear others in this community again" msgstr "Lod medlemmet høre andre i dette community igen" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Lader {productName} læse dit tastatur direkte. Dit system tillader det allerede." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Lader brugerdefineret CSS vise gennemsigtige områder i vinduet efter genstart af computerappen." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Linktypografi" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Link ikke tilgængeligt" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Udseende og funktionalitet" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Det ser ud til, at du bruger {0}. Ekstra lydbehandling fra {PRODUCT_NAME} kan få lyden til at pumpe eller blive forvrænget. Direkte input sender din lyd igennem uændret." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Beskedlayout" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "beskedlink" @@ -21332,7 +21416,7 @@ msgstr "Mikrofon" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Mikrofontest" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Diverse" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Manglende client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Flere valgmuligheder" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Flere genvejsindstillinger" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Mangler tilladelsen {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Ingen adgang" @@ -22984,7 +23068,7 @@ msgstr "Ingen resultater fundet" msgid "No mentions yet" msgstr "Ingen omtaler endnu" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Mikrofon ikke fundet" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Ingen indstillinger fundet" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Ingen genvej indstillet" @@ -23358,6 +23442,11 @@ msgstr "Ikke et heltal" msgid "Not accepted" msgstr "Ikke accepteret" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Ikke tildelt" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Åbn {host} i din browser." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Åbn {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Åbn {labelText} i indstillingerne" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "side-link" @@ -25611,11 +25700,11 @@ msgstr "Tilstedeværelse" msgid "Press {shortcut} to react" msgstr "Tryk på {shortcut} for at reagere" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Tryk på en tast, museknap eller controllerknap for at tilknytte den som genvej. Tryk på Escape for at annullere." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Tryk på en genvej…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Tast til Tryk for at tale" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Tryk for at tale" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Tryk for at tale (begrænset)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Tryk for at tale har endnu ingen systemdækkende tast. Du kan angive en under Indstillinger > Apps > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Tryk for at tale har endnu ingen systemdækkende tast. Du kan angive en under Systemindstillinger > Tastatur > Genveje > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Tryk for at tale har endnu ingen systemdækkende tast. Du kan angive en i skrivebordets indstillinger for tastaturgenveje." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Tryk for at tale ignoreres ved direkte input. Skift til Fokuseret tale eller Tilpasset for at bruge det." @@ -26293,21 +26397,51 @@ msgstr "Tryk for at tale ignoreres ved direkte input. Skift til Fokuseret tale e msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Tryk for at tale er slået til. Hold {pushToTalkHint} nede for at tale." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Tryk for at tale er slået til. Hold tasten til Tryk for at tale nede for at tale." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Tryk for at tale virker kun, mens {productName} er i fokus, fordi dit skrivebord ikke har opsat systemdækkende genveje." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Tryk for at tale virker kun, når {productName} er i fokus, indtil {permissionName} er aktiveret. Når du har ændret tilladelsen, skal du lukke {productName} helt ned og genstarte appen, så genvejstaster i hele systemet kan bruge den." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Tryk for at tale virker kun, mens {productName} er i fokus, indtil du opsætter systemdækkende genveje." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Tryk for at tale virker kun, når browserfanen er i fokus." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Tryk for at tale bruger den tast, du har angivet i {productName}. Angiv en først, ellers bruger din mikrofon taleaktivitet." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Tryk for at tale slår ikke mikrofonen til, mens du har slået lyden fra. Slå lyden til igen for at tale." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Tryk for at tale virker først uden for {productName}, når du har bundet det i din Hyprland-konfiguration." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Tryk for at tale virker først uden for {productName}, når du har bundet det i din Hyprland-konfiguration. Tilføj linjen for din Hyprland-version, og erstat KEY med den tast, du vil bruge." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Rå JSON til indlejring" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Genaktiver indbygget genvej" @@ -26565,13 +26699,6 @@ msgstr "Seneste omtaler" msgid "Recent searches" msgstr "Seneste søgninger" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Tjek igen" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Anbefalet" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Anbefales. Renser mikrofonlyden, så talen bliver tydelig." @@ -26600,6 +26727,11 @@ msgstr "Opret forbindelse igen" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Forbindelsen er genoprettet. Vælg en skærm igen, hvis du vil genoptage din tidligere stream." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Genopretter forbindelse til skrivebordet…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Opretter forbindelse igen …" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Optag genvej" @@ -26851,7 +26983,7 @@ msgstr "Relativ" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Forsinkelse efter tastslip" @@ -27739,13 +27871,13 @@ msgstr "Sendt igen" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Nulstil" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Nulstil \"{label}\" til den indbyggede lyd" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Nulstil til faktiske værdier" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Nulstil til indbygget" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Vælg den konto, du vil logge ind med på din nye enhed." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Vælg tilstand for talebehandling" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Sender rå mikrofonlyd uden behandling." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Sender din lyd uændret. Bedst, hvis du bruger ekstern lydsoftware." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Indstillet af {preset}-forudindstillingen. Valg af en værdi her skifter til Brugerdefineret." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Angiv tilpasset genvej" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Angiv tilpasset genvej til \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "Angav taleregionen til {region}" msgid "Set the voice region to automatic" msgstr "Angav taleregionen til automatisk" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Opsæt" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Indstillinger" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "indstillingslink" @@ -30330,8 +30467,8 @@ msgstr "Emojikoder" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Skift til rummeligt layout" msgid "Switch to dense layout" msgstr "Skift til kompakt visning" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Skift til direkte input" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Systemadministreret installation" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Genveje i hele systemet" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Systemdækkende genveje kunne ikke opsættes." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Systemdækkende: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Systemdækkende: Ikke tildelt" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Siden du leder efter, findes ikke eller er blevet flyttet." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Den angivne redirect_uri er ikke registreret for denne applikation." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Den angivne redirect_uri skal bruge HTTP eller HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Denne animation er for lang eller for stor til at beskære. Prøv en kortere eller mindre fil." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Denne app" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Denne vedhæftede fil er udløbet" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Denne bot er ikke offentlig og kan ikke tilføjes af andre brugere." @@ -34081,6 +34233,16 @@ msgstr "Denne samtale er ikke længere tilgængelig." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Denne version af computerappen kan ikke installere opdateringer i {productName}. Download den nyeste installationsfil til computerappen i stedet." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Dette skrivebord tilbyder ikke systemdækkende genveje. Tryk for at tale virker kun, mens {productName} er i fokus." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Dette skrivebord tilbyder ikke systemdækkende genveje. Genveje virker stadig, mens {productName} er i fokus." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Dette er din sidste adgangsnøgle, og adgangsnøgler er din anden faktor msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Dette er din sidste adgangsnøgle. Når den er væk, kan du ikke længere bruge en adgangsnøgle som din anden faktor." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Denne tast virker, mens {productName} er i fokus. Den systemdækkende tast styres af dit skrivebord og findes under {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Denne linkkanal er mærket som indhold for voksne og kan åbne materiale, der er upassende for nogle brugere." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Dette link er ikke tilgængeligt for din konto." @@ -34778,6 +34945,21 @@ msgstr "Titellinje" msgid "Title bar platform" msgstr "Titellinjeplatform" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "For at ændre tasterne skal du gå til Indstillinger > Apps > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "For at ændre tasterne skal du gå til Systemindstillinger > Tastatur > Genveje > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "For at ændre tasterne skal du åbne skrivebordets indstillinger for tastaturgenveje." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "For at oprette en webhook skal du åbne <0>{channelWebhooksSettingsPath}. Du kan stadig redigere og organisere alle eksisterende webhooks her." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Ukendt" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Ukendt app, udløbet session eller ugyldig godkendelsesanmodning." @@ -36410,6 +36592,11 @@ msgstr "Op til 4K/60 fps" msgid "Up to 720p at 30 FPS" msgstr "Op til 720p ved 30 billeder/sek" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Opdater {productName} for at bruge systemdækkende genveje på Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Brug mørkt tema i hele appen" msgid "Use default" msgstr "Brug standard" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Brug direkte adgang til inputenheder" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Deltagere i taleopkald" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Talebehandling" @@ -38264,7 +38456,7 @@ msgstr "Slå lyden til i taleopkald" msgid "Voice unmute" msgstr "Slå mikrofonen til" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Stemmen bruger din systems lydenhed i denne browser. Skift den i dine systemindstillinger." @@ -38278,7 +38470,7 @@ msgstr "Annulleret" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Venter på, at ventetiden i langsom tilstand udløber i en eller flere v msgid "Waiting for you to..." msgstr "Venter på, at du ..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Venter på skrivebordet…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Hvilke samtaler" msgid "Which rule was broken?" msgstr "Hvilken regel blev brudt?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Når Tryk for at tale er slået til, styrer genvejen din mikrofon, så knappen til at slå mikrofonen fra er deaktiveret." @@ -39094,6 +39291,11 @@ msgstr "Uden denne tilladelse skal du bruge Tryk for at tale." msgid "Wordmark" msgstr "Tekstlogo" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Virker, mens {productName} er i fokus" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Du ændrede dit tag, mens du havde {PREMIUM_PRODUCT_NAME}. Det vil blive ændret tilfældigt, når dit {PREMIUM_PRODUCT_NAME} udløber, medmindre det fornyes." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Du har ikke adgang til den kanal, hvor denne besked blev sendt." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Dit {premiumProductName}-abonnement er udløbet. Du har mistet alle {premiumProductName}-fordele. Genaktiver dit abonnement for at få adgang igen." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Din {productName}-session er udløbet. Log ind igen for at fortsætte med at godkende denne app." @@ -40144,6 +40346,16 @@ msgstr "Dit fællesskab skal have mindst {minMemberCount, plural, one {# medlem} msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Dit kreditkort er blevet tjekket. Hvis kortet er gyldigt, får du snart adgang til indhold for voksne. Du kan lukke denne fane og vende tilbage til appen." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Dit skrivebord har ikke opsat systemdækkende genveje." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Dit skrivebord kører disse genveje, selv når {productName} ikke er i fokus." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/de/messages.po b/fluxer_app/src/features/i18n/locales/de/messages.po index 4dae8d9ee..0fa9488ce 100644 --- a/fluxer_app/src/features/i18n/locales/de/messages.po +++ b/fluxer_app/src/features/i18n/locales/de/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# Kanal folgt} other {# Kanäle folgen}} diesem Kan msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# Community gefunden} other {# Communitys gefunden}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# Community ist aufgrund einer Fehlfunktion des Fluxkompensators vorübergehend nicht verfügbar.} other {# Communitys sind aufgrund einer Fehlfunktion des Fluxkompensators vorübergehend nicht verfügbar.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} verhindert, dass Push-Benachrichtigungen an deine mobilen msgid "{productName} Bug Hunter" msgstr "{productName}-Fehlerjäger" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} kann deinen Desktop bitten, seine Tastenkombinationen auch dann auszuführen, wenn {productName} nicht im Fokus ist. Dein Desktop bittet dich um Bestätigung und lässt dich die Tasten wählen." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} kann deine Tastatur nicht mehr auslesen und erkennt Tastenkombinationen stattdessen über X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} kann deine Tastatur nicht mehr auslesen, daher werden stattdessen die Tastenkombinationen deines Desktops verwendet." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} kann deine Tastatur nicht mehr auslesen. Tastenkombinationen funktionieren nur, solange {productName} im Fokus ist." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} kann seine Tastenkombinationen bei Hyprland registrieren, damit sie auch funktionieren, wenn {productName} nicht im Fokus ist. Die Tasten legst du dann in deiner Hyprland-Konfiguration fest." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} benötigt Kamerazugriff, um deine Geräte aufzulisten." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} benötigt Mikrofonzugriff, um deine Geräte aufzulisten." @@ -1837,6 +1862,11 @@ msgstr "{productName} ist jetzt unter {host} erreichbar. Installiere es von dort msgid "{productName} Partner" msgstr "{productName}-Partner" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} hat seine Tastenkombinationen bei Hyprland registriert. Lege in deiner Hyprland-Konfiguration Tasten dafür fest, um sie zu nutzen, wenn {productName} nicht im Fokus ist." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Ein Profil, das auffällt" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Eine redirect_uri ist erforderlich, wenn der Bot-Scope nicht der einzige Scope ist." @@ -2847,7 +2877,7 @@ msgstr "Nur in allen offenen DMs" msgid "Across every community you're in" msgstr "In allen Communitys, in denen du bist" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Aktion" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Aktivitätsfreigabe" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Aktivitätsschwelle" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Füge deinem Account eine zusätzliche Sicherheitsebene hinzu" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Weitere benutzerdefinierte Tastenkombination hinzufügen" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Adresse" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Jede Einstellung selbst anpassen: Rauschunterdrückung, Echokompensation und Verstärkung." @@ -3696,11 +3726,11 @@ msgstr "{everyoneMention} und {hereMention} erlauben" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Erlaube {PRODUCT_NAME} unter {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME} den Zugriff auf deine Kamera." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Erlaube {PRODUCT_NAME} den Zugriff auf dein Mikrofon in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Erlaube {PRODUCT_NAME}, auf dein Mikrofon zuzugreifen. Überprüfe die Adressleiste oder die Einstellungen deines Browsers." @@ -3803,7 +3833,7 @@ msgstr "Erlaube Mitgliedern ohne die Berechtigung „{readMessageHistoryPermissi msgid "Allow mentions for this role" msgstr "Erwähnungen für diese Rolle zulassen" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Mikrofonzugriff erlauben" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Autorisierung fehlgeschlagen" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Autorisierung fehlgeschlagen. Bitte versuche es noch einmal." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Sprache automatisch erkennen" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Aktivitätsschwelle automatisch festlegen" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Bot-Berechtigungen" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Bot-Berechtigungen können nicht negativ sein." @@ -6869,6 +6899,11 @@ msgstr "Spitznamen in der Gruppe ändern" msgid "Change icon" msgstr "Symbol ändern" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "In den Systemeinstellungen ändern" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Kanalzugriff" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Kanalzugriff verweigert" @@ -7294,7 +7329,7 @@ msgstr "Kanal-ID in die Zwischenablage kopiert" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "Kanal-Link" @@ -7462,6 +7497,11 @@ msgstr "Chateingabe" msgid "Chat shortcuts" msgstr "Tastenkombinationen für den Chat" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Erneut prüfen" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Wähle ein Fenster in deiner Systemauswahl" msgid "Choose an account" msgstr "Account auswählen" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Eine Aktion auswählen." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Hintergrund auswählen" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Wähle entweder „guild_id“ oder „channel_id“, nicht beides." @@ -7990,7 +8030,7 @@ msgstr "Verbessert das Mikrofonsignal für klare Sprache." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Klicken, um die Farbauswahl zu öffnen und eine neue Farbe auszuwählen. msgid "Click to react" msgstr "Zum Reagieren klicken" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Zum Aufnehmen klicken" @@ -9110,7 +9150,7 @@ msgstr "Bestätigung" msgid "Confirmation or double-click for community voice joins" msgstr "Bestätigung oder Doppelklick zum Beitreten zu einem Sprachkanal in der Community" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "In Konflikt mit {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Schließe eine Kamera oder ein virtuelles Aufnahmegerät an und versuche msgid "Connect a display, then try again." msgstr "Schließe einen Bildschirm an und versuche es erneut." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Verbinde ein Mikrofon und versuche es erneut." @@ -9700,6 +9740,21 @@ msgstr "Einladungslink kopieren" msgid "Copy invite URL" msgstr "Einladungs-URL kopieren" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Zeilen kopieren" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Zeilen für hyprland.conf kopieren" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Zeilen für hyprland.lua kopieren" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Benutzerdefiniert" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Benutzerdefinierte Auswahl" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Benutzerdefinierte Tastenkombinationsaktion" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Optionen für benutzerdefinierte Tastenkombinationen" @@ -11466,7 +11521,7 @@ msgstr "Standard-Tastenkombinationen" msgid "Default translator" msgstr "Standard-Übersetzer" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Standard:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Gespeicherte Medien löschen" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Tastenkombination löschen" @@ -12102,13 +12157,13 @@ msgstr "Entwicklerstatus" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Entwicklertools" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Entwicklertools sind über das Mitarbeiter-Abzeichen in der Kanalüberschrift verfügbar." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Backend-Premiumvorteile deaktivieren" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Integrierte Tastenkombination deaktivieren" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Bild-im-Bild-Pop-outs für Bildschirmfreigaben deaktivieren?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Tastenkombination deaktivieren" @@ -12888,7 +12943,7 @@ msgstr "Stattdessen spenden" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Herunterladen-Button" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Desktop-App herunterladen" @@ -13204,6 +13259,11 @@ msgstr "Niederländisch" msgid "e.g., YubiKey, iPhone, work computer" msgstr "z. B. YubiKey, iPhone, Arbeitscomputer" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Jede Taste kann nur eine systemweite Tastenkombination auslösen." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Einladungslink bearbeiten" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Tastenkombination bearbeiten" @@ -13425,7 +13485,7 @@ msgstr "Formular zum Bearbeiten gespeicherter Medien" msgid "Edit search engine" msgstr "Suchmaschine bearbeiten" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Tastenkombination bearbeiten" @@ -13753,20 +13813,16 @@ msgstr "Leere Bearbeitung" msgid "Empty favorites" msgstr "Favoriten leer" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Aktivieren" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "{permissionName}-Berechtigung aktivieren" @@ -13876,11 +13932,6 @@ msgstr "Hardwarebeschleunigung aktivieren und neu starten" msgid "Enable hardware acceleration?" msgstr "Hardwarebeschleunigung aktivieren?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Aktiviere den Eingabezugriff, um systemweite Tastenkombinationen unter Wayland zu verwenden. Nachdem du den Eingabezugriff geändert hast, beende {productName} vollständig und starte es neu, damit die Tastenkombinationen verwendet werden können." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Umschalttaste zum Erweitern aktivieren" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Tastenkombination aktivieren" @@ -14357,7 +14408,7 @@ msgstr "Gesamtes System-Audio" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Beitrittston" @@ -14467,7 +14518,7 @@ msgstr "Abend" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Gleicht deine Mikrofonlautstärke aus, damit du nicht zu leise bist." @@ -15682,6 +15733,26 @@ msgstr "Nur für britische Augen ..." msgid "For me" msgstr "Für mich" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Verwende für „Zum Stummschalten drücken“ eine einzelne Taste ohne Strg, Alt oder Umschalttaste. Lass bei einer Kombination zuerst die Haupttaste los, sonst kann das Mikrofon stummgeschaltet bleiben." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Verwende für Push-to-Talk eine einzelne Taste ohne Strg, Alt oder Umschalttaste. Lass bei einer Kombination zuerst die Haupttaste los, sonst kann das Mikrofon an bleiben." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Verwende für diese Tastenkombination eine einzelne Taste ohne Strg, Alt oder Umschalttaste. Lass bei einer Kombination zuerst die Haupttaste los, sonst kann die Tastenkombination aktiv bleiben." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Verwende für „Sprachaktivitätspriorität“ eine einzelne Taste ohne Strg, Alt oder Umschalttaste. Lass bei einer Kombination zuerst die Haupttaste los, sonst kann die Priorität aktiv bleiben." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Bestätige bitte zu deiner Sicherheit, dass du es bist, um fortzufahren." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Globales Profil" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Globale Tastenkombination" @@ -17434,6 +17505,11 @@ msgstr "Halte die Umschalttaste gedrückt und drücke X, um Apps auszuwählen. D msgid "Hold to record. Drag up to lock, or release to send." msgstr "Halten zum Aufnehmen. Nach oben ziehen zum Sperren oder loslassen zum Senden." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Wenn du Umschalttaste, Strg oder Alt gedrückt hältst, wird diese Tastenkombination blockiert. Füge in deinen Systemeinstellungen Alternativen hinzu." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Hurra! Benachrichtigungen sind aktiviert" msgid "Hyperlink" msgstr "Hyperlink" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland hat keinen Dialog für Tastenkombinationen. Füge die Zeilen für deine Hyprland-Version zu deiner Hyprland-Konfiguration hinzu und ersetze KEY durch die gewünschte Taste." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland hat keinen Dialog für Tastenkombinationen. Lege die Tasten in deiner Hyprland-Konfiguration fest." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (vor Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 und neuer)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Ich stimme der <0>Datenschutzerklärung zu" @@ -17576,7 +17672,7 @@ msgstr "Ich habe es mir anders überlegt" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Ich verstehe" @@ -18000,7 +18096,7 @@ msgstr "In" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "Im Modus „Betriebssystem“ dienen die oben aufgeführten Wörterbücher als Empfehlungen. {PRODUCT_NAME} übergibt diese Sprach-Tags, sofern unterstützt, an die Rechtschreibprüfung deines Betriebssystems. Nicht erkannte Tags werden ignoriert." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "Im Browser funktioniert Push-to-Talk nur, wenn der Tab \"{PRODUCT_NAME}\" im Fokus ist. Installiere die Desktop-App für systemweites Push-to-Talk." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Eingabe" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Der Eingabezugriff konnte nicht automatisch aktiviert werden. Ändere ihn manuell und beende und starte {productName} dann vollständig neu, damit Tastenkombinationen ihn verwenden können." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Der Eingabezugriff konnte nicht aktiviert werden. Versuche es erneut über die {settingsMenuName}-Einstellungen und beende und starte {productName} dann vollständig neu." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Eingabezugriff ist für systemweite Tastatur- und Maus-Kurzbefehle unter Wayland erforderlich. Nachdem du den Eingabezugriff geändert hast, beende und starte {productName} vollständig neu, damit die Kurzbefehle ihn verwenden können." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Der Eingabezugriff wurde geändert. Beende {productName} vollständig und starte es neu, damit systemweite Tastenkombinationen ihn verwenden können." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Ein- und Ausgabe" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Ungültige Autorisierungsanfrage" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Ungültige Bot-Berechtigungen." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Ungültiger oder abgelaufener Code. Bitte versuche es noch einmal." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Ungültiger Parameter redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Einladungslink ist ausgeblendet, während du teilst:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Einladungslink ausgeblendet, während du teilst" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Zu {displayName} springen" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Springe zu {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Zum aktuellen Anruf springen" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Zum verlinkten Kanal springen" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Zur verlinkten Nachricht springen" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Gehe zu der Nachricht von {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Zur Nachricht in {labelText} springen" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Bitte sicher aufbewahren. Eine Neuerstellung unterbricht bestehende Integrationen." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Auch nach Loslassen des Knopfes noch kurzzeitig übertragen." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Tastatur" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Der Tastaturzugriff konnte nicht gestartet werden. Tastenkombinationen funktionieren nur, solange {productName} im Fokus ist." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Community selbst moderieren lassen" msgid "Let the member hear others in this community again" msgstr "Dem Mitglied wieder erlaubt, andere in dieser Community zu hören" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Lässt {productName} direkt auf deine Tastatur zugreifen. Dein System erlaubt dies bereits." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Ermöglicht es benutzerdefiniertem CSS, nach dem Neustart der Desktop-App transparente Fensterbereiche anzuzeigen." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Link-Stil" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Link nicht verfügbar" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Aussehen und Verhalten" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Es sieht so aus, als würdest du {0} verwenden. Wenn du die Verarbeitung von {PRODUCT_NAME} zusätzlich aktivierst, kann dies zu Pumpen oder Verzerrungen führen. Eine direkte Eingabe leitet dein Audio unverändert weiter." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Nachrichtenlayout" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "Nachrichtenlink" @@ -21332,7 +21416,7 @@ msgstr "Mikrofon" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Mikrofon-Test" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Verschiedenes" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "client_id fehlt" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Weitere Optionen" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Weitere Tastenkombinationsoptionen" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Keine Berechtigung \"{sendMessagesPermission}\"" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Kein Zugriff" @@ -22984,7 +23068,7 @@ msgstr "Keine Treffer gefunden" msgid "No mentions yet" msgstr "Noch keine Erwähnungen" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Kein Mikrofon gefunden" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Keine Einstellungen gefunden" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Keine Tastenkombination festgelegt" @@ -23358,6 +23442,11 @@ msgstr "Keine ganze Zahl" msgid "Not accepted" msgstr "Nicht akzeptiert" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Nicht zugewiesen" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Öffne {host} in deinem Browser." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Öffne {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Öffne {labelText} in den Einstellungen" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Bild ab" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "Seitenlink" @@ -25611,11 +25700,11 @@ msgstr "Online-Status" msgid "Press {shortcut} to react" msgstr "Drücke {shortcut} zum Reagieren" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Drücke eine Taste, Maustaste oder Gamepad-Taste zum Belegen. Escape bricht ab." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Tastenkombination drücken …" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Push-to-Talk-Taste" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Push-to-Talk" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Push-to-Talk (begrenzt)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Push-to-Talk hat noch keine systemweite Taste. Um eine festzulegen, gehe zu Einstellungen > Apps > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Push-to-Talk hat noch keine systemweite Taste. Um eine festzulegen, gehe zu Systemeinstellungen > Tastatur > Kurzbefehle > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Push-to-Talk hat noch keine systemweite Taste. Um eine festzulegen, öffne die Tastenkombinationseinstellungen deines Desktops." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Push-to-Talk wird bei direkter Eingabe ignoriert. Wechsle zu „Fokussierter Sprachchat“ oder „Benutzerdefiniert“, um es zu verwenden." @@ -26293,21 +26397,51 @@ msgstr "Push-to-Talk wird bei direkter Eingabe ignoriert. Wechsle zu „Fokussie msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Push-to-Talk ist aktiviert. Halte {pushToTalkHint} gedrückt, um zu sprechen." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Push-to-Talk ist aktiviert. Halte deine Push-to-Talk-Taste gedrückt, um zu sprechen." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Push-to-Talk funktioniert nur, solange {productName} im Fokus ist, da dein Desktop keine systemweiten Tastenkombinationen eingerichtet hat." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Push-to-Talk funktioniert nur, während {productName} im Fokus ist, bis {permissionName} aktiviert wird. Beende {productName} nach der Berechtigungsänderung vollständig und starte es neu, damit systemweite Tastenkombinationen die Berechtigung nutzen können." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Push-to-Talk funktioniert nur, solange {productName} im Fokus ist, bis du systemweite Tastenkombinationen einrichtest." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Push-to-Talk funktioniert nur, während der Browser-Tab im Fokus ist." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Push-to-Talk verwendet die Taste, die du in {productName} festgelegt hast. Lege zuerst eine fest, sonst nutzt dein Mikrofon Sprachaktivität." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Push-to-Talk aktiviert dein Mikrofon nicht, solange dein Ton ausgeschaltet ist. Schalte deinen Ton ein, um zu sprechen." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Push-to-Talk funktioniert außerhalb von {productName} erst, nachdem du es in deiner Hyprland-Konfiguration festgelegt hast." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Push-to-Talk funktioniert außerhalb von {productName} erst, nachdem du es in deiner Hyprland-Konfiguration festgelegt hast. Füge die Zeile für deine Hyprland-Version hinzu und ersetze KEY durch die gewünschte Taste." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Rohes Embed-JSON" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Integrierte Tastenkombination aktivieren" @@ -26565,13 +26699,6 @@ msgstr "Neue Erwähnungen" msgid "Recent searches" msgstr "Letzte Suchanfragen" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Erneut prüfen" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Empfohlen" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Empfohlen. Filtert dein Mikrofonsignal für klare Sprache." @@ -26600,6 +26727,11 @@ msgstr "Verbindung wiederherstellen" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Wiederherstellung erfolgreich. Wähle erneut einen Bildschirm aus, wenn du deinen vorherigen Stream fortsetzen möchtest." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Verbindung zu deinem Desktop wird wiederhergestellt…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Verbindung wird wiederhergestellt…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Tastenkombination aufnehmen" @@ -26851,7 +26983,7 @@ msgstr "Relativ" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Verzögerung beim Loslassen" @@ -27739,13 +27871,13 @@ msgstr "Erneut gesendet" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Zurücksetzen" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "\"{label}\" auf Standard zurücksetzen" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Auf tatsächliche Werte zurücksetzen" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Auf Standard zurücksetzen" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Wähle das Konto aus, mit dem du dich auf deinem neuen Gerät anmelden möchtest." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Sprachverarbeitungsmodus auswählen" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Sendet rohes Mikrofon-Audio ohne Verarbeitung." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Sendet dein Audio unverändert. Am besten geeignet, wenn du externe Audiosoftware verwendest." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Festgelegt durch die {preset}-Voreinstellung. Die Auswahl eines Wertes hier wechselt zu Benutzerdefiniert." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Eigene Tastenkombination festlegen" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Benutzerdefinierte Tastenkombination für „{label}“ festlegen" @@ -30066,6 +30198,11 @@ msgstr "Die Sprachregion wurde auf {region} gesetzt" msgid "Set the voice region to automatic" msgstr "Die Sprachregion auf automatisch gesetzt" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Einrichten" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Einstellungen" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "Einstellungen-Link" @@ -30330,8 +30467,8 @@ msgstr "Kurzcodes" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Zu \"Komfortabel\" wechseln" msgid "Switch to dense layout" msgstr "Zu kompakter Ansicht wechseln" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Direkte Eingabe nutzen" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Systemverwaltete Installation" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Systemweite Tastenkombinationen" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Systemweite Tastenkombinationen konnten nicht eingerichtet werden." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Systemweit: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Systemweit: Nicht zugewiesen" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Die Seite, die du suchst, existiert nicht oder wurde verschoben." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Die angegebene redirect_uri ist für diese Anwendung nicht registriert." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Die angegebene redirect_uri muss HTTP oder HTTPS verwenden." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Diese Animation ist zu lang oder zu groß zum Zuschneiden. Versuche es mit einer kürzeren oder kleineren Datei." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Diese Anwendung" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Dieser Anhang ist abgelaufen" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Dieser Bot ist nicht öffentlich und kann nicht von anderen Nutzern hinzugefügt werden." @@ -34081,6 +34233,16 @@ msgstr "Diese Unterhaltung ist nicht mehr verfügbar." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Diese Desktop-Version kann keine Updates innerhalb von {productName} installieren. Bitte lade stattdessen die neueste Desktop-Installationsdatei herunter." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Dieser Desktop bietet keine systemweiten Tastenkombinationen. Push-to-Talk funktioniert nur, solange {productName} im Fokus ist." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Dieser Desktop bietet keine systemweiten Tastenkombinationen. Tastenkombinationen funktionieren weiterhin, solange {productName} im Fokus ist." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Dies ist dein letzter Passkey und Passkeys sind dein zweiter Faktor. Wen msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Dies ist dein letzter Passkey. Sobald er weg ist, kannst du keinen Passkey mehr als zweiten Faktor verwenden." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Diese Taste funktioniert, solange {productName} im Fokus ist. Die systemweite Taste legt dein Desktop fest, unter {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Dieser Link-Kanal ist als nicht jugendfrei gekennzeichnet und kann Inhalte enthalten, die für manche Nutzer unangemessen sein könnten." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Dieser Link ist für deinen Account nicht verfügbar." @@ -34778,6 +34945,21 @@ msgstr "Titelleiste" msgid "Title bar platform" msgstr "Titelleisten-Plattform" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Um die Tasten zu ändern, gehe zu Einstellungen > Apps > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Um die Tasten zu ändern, gehe zu Systemeinstellungen > Tastatur > Kurzbefehle > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Um die Tasten zu ändern, öffne die Tastenkombinationseinstellungen deines Desktops." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Um einen Webhook zu erstellen, öffne <0>{channelWebhooksSettingsPath}. Du kannst hier weiterhin alle bestehenden Webhooks bearbeiten und organisieren." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Unbekannt" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Unbekannte Anwendung, abgelaufene Sitzung oder ungültige Autorisierungsanfrage." @@ -36410,6 +36592,11 @@ msgstr "Bis zu 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "Bis zu 720p bei 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Aktualisiere {productName}, um systemweite Tastenkombinationen unter Wayland zu verwenden." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Dunkles Design in der gesamten App verwenden" msgid "Use default" msgstr "Standard verwenden" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Direkten Zugriff auf Eingabegeräte verwenden" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Sprachchat-Teilnehmer" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Sprachverarbeitung" @@ -38264,7 +38456,7 @@ msgstr "Ton im Sprachchat einschalten" msgid "Voice unmute" msgstr "Stummschaltung aufheben" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Die Sprachausgabe verwendet in diesem Browser Ihr Systemausgabegerät. Ändern Sie dies in Ihren Systemeinstellungen." @@ -38278,7 +38470,7 @@ msgstr "Storniert" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Warten, bis die Wartezeit des Slow-Modus in einem oder mehreren ausgewä msgid "Waiting for you to..." msgstr "Warten, bis du …" +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Warten auf deinen Desktop…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Welche Unterhaltungen" msgid "Which rule was broken?" msgstr "Welche Regel wurde gebrochen?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Solange Push-to-Talk aktiviert ist, steuert deine Tastenkombination dein Mikrofon, daher ist die Stummschaltfläche deaktiviert." @@ -39094,6 +39291,11 @@ msgstr "Ohne diese Berechtigung ist Push-to-Talk erforderlich." msgid "Wordmark" msgstr "Wortmarke" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Funktioniert, solange {productName} im Fokus ist" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Du hast deinen Tag geändert, während du {PREMIUM_PRODUCT_NAME} hattest. Er wird zufällig neu generiert, wenn dein {PREMIUM_PRODUCT_NAME} abläuft, es sei denn, es wird verlängert." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Du hast keinen Zugriff auf den Kanal, in dem diese Nachricht gesendet wurde." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Dein {premiumProductName}-Abo ist abgelaufen. Du hast alle {premiumProductName}-Vorteile verloren. Reaktiviere dein Abo, um den Zugriff wiederherzustellen." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Deine {productName}-Sitzung ist abgelaufen. Melde dich erneut an, um die Autorisierung dieser Anwendung fortzusetzen." @@ -40144,6 +40346,16 @@ msgstr "Deine Community braucht mindestens {minMemberCount, plural, one {# Mitgl msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Deine Kreditkarte wurde überprüft. Wenn die Kreditkarte gültig ist, werden Inhalte für Erwachsene in Kürze freigeschaltet. Du kannst diesen Tab schließen und zur App zurückkehren." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Dein Desktop hat keine systemweiten Tastenkombinationen eingerichtet." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Dein Desktop führt diese Tastenkombinationen aus, auch wenn {productName} nicht im Fokus ist." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/el/messages.po b/fluxer_app/src/features/i18n/locales/el/messages.po index a9808c50d..3e850b9e5 100644 --- a/fluxer_app/src/features/i18n/locales/el/messages.po +++ b/fluxer_app/src/features/i18n/locales/el/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# κανάλι ακολουθεί} other {# καν msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# κοινότητα βρέθηκε} other {# κοινότητες βρέθηκαν}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# κοινότητα είναι προσωρινά μη διαθέσιμη λόγω δυσλειτουργίας του πυκνωτή ροής.} other {# κοινότητες είναι προσωρινά μη διαθέσιμες λόγω δυσλειτουργίας του πυκνωτή ροής.}}" @@ -1708,6 +1708,31 @@ msgstr "Το {productName} αποφεύγει την αποστολή ειδοπ msgid "{productName} Bug Hunter" msgstr "Κυνηγός σφαλμάτων {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "Το {productName} μπορεί να ζητήσει από την επιφάνεια εργασίας σας να εκτελεί τις συντομεύσεις του όταν το {productName} δεν έχει την εστίαση. Η επιφάνεια εργασίας σας θα ζητήσει επιβεβαίωση και θα σας αφήσει να επιλέξετε τα πλήκτρα." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "Το {productName} δεν μπορεί πλέον να διαβάσει το πληκτρολόγιό σας, οπότε ανιχνεύει τις συντομεύσεις μέσω του X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "Το {productName} δεν μπορεί πλέον να διαβάσει το πληκτρολόγιό σας, οπότε χρησιμοποιούνται οι συντομεύσεις της επιφάνειας εργασίας σας." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "Το {productName} δεν μπορεί πλέον να διαβάσει το πληκτρολόγιό σας. Οι συντομεύσεις λειτουργούν μόνο όταν το {productName} έχει την εστίαση." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "Το {productName} μπορεί να καταχωρίσει τις συντομεύσεις του στο Hyprland, ώστε να εκτελούνται όταν το {productName} δεν έχει την εστίαση. Στη συνέχεια, αντιστοιχίζετε τα πλήκτρα στο αρχείο διαμόρφωσης του Hyprland." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "Το {productName} χρειάζεται πρόσβαση στην κάμερα για να εμφανίσει τις συσκευές σας." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "Το {productName} χρειάζεται πρόσβαση στο μικρόφωνο για να εμφανίσει τις συσκευές σας." @@ -1837,6 +1862,11 @@ msgstr "Το {productName} βρίσκεται πλέον στο {host}. Εγκα msgid "{productName} Partner" msgstr "Συνεργάτης {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "Το {productName} καταχώρισε τις συντομεύσεις του στο Hyprland. Αντιστοιχίστε τις στο αρχείο διαμόρφωσης του Hyprland για να τις χρησιμοποιείτε όταν το {productName} δεν έχει την εστίαση." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Ένα προφίλ που ξεχωρίζει" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Απαιτείται ένα redirect_uri όταν το πεδίο εφαρμογής bot δεν είναι το μόνο πεδίο εφαρμογής." @@ -2847,7 +2877,7 @@ msgstr "Μόνο σε όλα τα ανοιχτά DM" msgid "Across every community you're in" msgstr "Σε κάθε κοινότητα στην οποία ανήκετε" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Ενέργεια" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Κοινή χρήση δραστηριοτήτων" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Όριο δραστηριότητας" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Πρόσθεσε ένα επιπλέον επίπεδο ασφαλείας στον λογαριασμό σου" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Προσθήκη άλλης προσαρμοσμένης συντόμευσης" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Διεύθυνση" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Προσαρμόστε κάθε ρύθμιση μόνοι σας: καταστολή θορύβου, ακύρωση ηχούς και ενίσχυση." @@ -3696,11 +3726,11 @@ msgstr "Να επιτρέπονται οι αναφορές {everyoneMention} κ msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Επιτρέψτε στο {PRODUCT_NAME} να έχει πρόσβαση στην κάμερά σας στις {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Επιτρέψτε στο {PRODUCT_NAME} να έχει πρόσβαση στο μικρόφωνό σας στις {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Επιτρέψτε στο {PRODUCT_NAME} να έχει πρόσβαση στο μικρόφωνό σας. Ελέγξτε τη γραμμή διευθύνσεων ή τις ρυθμίσεις του προγράμματος περιήγησής σας." @@ -3803,7 +3833,7 @@ msgstr "Να επιτρέπεται στα μέλη χωρίς το δικαίω msgid "Allow mentions for this role" msgstr "Να επιτρέπονται οι αναφορές για αυτόν τον ρόλο" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Να επιτρέπεται το μικρόφωνο" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Η εξουσιοδότηση απέτυχε" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Η εξουσιοδότηση απέτυχε. Δοκιμάστε ξανά." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Αυτόματη ανίχνευση γλώσσας" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Αυτόματη ρύθμιση ορίου δραστηριότητας" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Δικαιώματα bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Τα δικαιώματα bot δεν μπορούν να είναι αρνητικά." @@ -6869,6 +6899,11 @@ msgstr "Αλλαγή ψευδώνυμου στην ομάδα" msgid "Change icon" msgstr "Αλλαγή εικονιδίου" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Αλλαγή στις ρυθμίσεις συστήματος" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Πρόσβαση σε κανάλι" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Δεν επιτρέπεται η πρόσβαση στο κανάλι" @@ -7294,7 +7329,7 @@ msgstr "Το αναγνωριστικό καναλιού αντιγράφηκε #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "σύνδεσμος καναλιού" @@ -7462,6 +7497,11 @@ msgstr "πεδίο συνομιλίας" msgid "Chat shortcuts" msgstr "Συντομεύσεις συνομιλίας" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Νέος έλεγχος" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Επιλέξτε ένα παράθυρο από τον επιλογέα msgid "Choose an account" msgstr "Επιλέξτε λογαριασμό" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Επιλέξτε μια ενέργεια." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Επιλέξτε φόντο" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Επιλέξτε είτε guild_id είτε channel_id, όχι και τα δύο." @@ -7990,7 +8030,7 @@ msgstr "Καθαρίζει το μικρόφωνό σας για καθαρή ο #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Πατήστε για να ανοίξετε την επιλογή χρώ msgid "Click to react" msgstr "Πατήστε για αντίδραση" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Κάντε κλικ για εγγραφή" @@ -9110,7 +9150,7 @@ msgstr "Επιβεβαίωση" msgid "Confirmation or double-click for community voice joins" msgstr "Επιβεβαίωση ή διπλό κλικ για συμμετοχή σε φωνητικά κανάλια της κοινότητας" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Συγκρούεται με {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Συνδέστε μια κάμερα ή μια συσκευή εικον msgid "Connect a display, then try again." msgstr "Συνδέστε μια οθόνη και δοκιμάστε ξανά." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Συνδέστε ένα μικρόφωνο και δοκιμάστε ξανά." @@ -9700,6 +9740,21 @@ msgstr "Αντιγραφή συνδέσμου πρόσκλησης" msgid "Copy invite URL" msgstr "Αντιγραφή URL πρόσκλησης" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Αντιγραφή γραμμών" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Αντιγραφή γραμμών για το hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Αντιγραφή γραμμών για το hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Προσαρμοσμένη πηγή" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Προσαρμοσμένη επιλογή" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Προσαρμοσμένη ενέργεια συντόμευσης" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Επιλογές προσαρμοσμένης συντόμευσης" @@ -11466,7 +11521,7 @@ msgstr "Προεπιλεγμένες συντομεύσεις" msgid "Default translator" msgstr "Προεπιλεγμένος μεταφραστής" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Προεπιλογή:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Διαγραφή αποθηκευμένων πολυμέσων" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Διαγραφή συντόμευσης" @@ -12102,13 +12157,13 @@ msgstr "Κατάσταση προγραμματιστή" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Εργαλεία προγραμματιστή" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Τα εργαλεία προγραμματιστή είναι διαθέσιμα από το σήμα προσωπικού στην κεφαλίδα του καναλιού." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Απενεργοποίηση premium παροχών του backend" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Απενεργοποίηση ενσωματωμένης συντόμευσης" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Απενεργοποίηση αναδυόμενων παραθύρων εικόνας-σε-εικόνα για την κοινή χρήση οθόνης;" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Απενεργοποίηση συντόμευσης" @@ -12888,7 +12943,7 @@ msgstr "Κάνε μια δωρεά" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Κουμπί λήψης" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Λήψη εφαρμογής για υπολογιστή" @@ -13204,6 +13259,11 @@ msgstr "Ολλανδικά" msgid "e.g., YubiKey, iPhone, work computer" msgstr "π.χ. YubiKey, iPhone, υπολογιστής εργασίας" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Κάθε πλήκτρο μπορεί να εκτελεί μόνο μία συντόμευση σε όλο το σύστημα." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Επεξεργασία συνδέσμου πρόσκλησης" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Επεξεργασία συντόμευσης πληκτρολογίου" @@ -13425,7 +13485,7 @@ msgstr "Φόρμα επεξεργασίας αποθηκευμένων πολυ msgid "Edit search engine" msgstr "Επεξεργασία μηχανής αναζήτησης" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Επεξεργασία συντόμευσης" @@ -13753,20 +13813,16 @@ msgstr "Κενή επεξεργασία" msgid "Empty favorites" msgstr "Κενά αγαπημένα" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Ενεργοποίηση" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Ενεργοποίηση άδειας {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Ενεργοποίηση επιτάχυνσης υλικού και επ msgid "Enable hardware acceleration?" msgstr "Ενεργοποίηση επιτάχυνσης υλικού;" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Ενεργοποιήστε την πρόσβαση εισόδου για να χρησιμοποιήσετε συντομεύσεις σε όλο το σύστημα στο Wayland. Αφού αλλάξετε την πρόσβαση εισόδου, κλείστε πλήρως και επανεκκινήστε το {productName} ώστε να μπορούν να χρησιμοποιηθούν οι συντομεύσεις." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Ενεργοποίηση Shift για επέκταση" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Ενεργοποίηση συντόμευσης" @@ -14357,7 +14408,7 @@ msgstr "Ήχος ολόκληρου του συστήματος" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Ήχος εισόδου" @@ -14467,7 +14518,7 @@ msgstr "Βράδυ" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Εξομαλύνει την ένταση του μικροφώνου σας, ώστε να μην ακούγεστε πολύ σιγά." @@ -15682,6 +15733,26 @@ msgstr "Μόνο για βρετανικά μάτια..." msgid "For me" msgstr "Για μένα" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Για τη λειτουργία push to mute, χρησιμοποιήστε ένα μόνο πλήκτρο χωρίς Ctrl, Alt ή Shift. Αν χρησιμοποιείτε συνδυασμό, αφήνετε πρώτα το κύριο πλήκτρο, αλλιώς το μικρόφωνο μπορεί να παραμείνει σε σίγαση." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Για τη λειτουργία push-to-talk, χρησιμοποιήστε ένα μόνο πλήκτρο χωρίς Ctrl, Alt ή Shift. Αν χρησιμοποιείτε συνδυασμό, αφήνετε πρώτα το κύριο πλήκτρο, αλλιώς το μικρόφωνο μπορεί να παραμείνει ανοιχτό." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Για αυτή τη συντόμευση, χρησιμοποιήστε ένα μόνο πλήκτρο χωρίς Ctrl, Alt ή Shift. Αν χρησιμοποιείτε συνδυασμό, αφήνετε πρώτα το κύριο πλήκτρο, αλλιώς η συντόμευση μπορεί να παραμείνει ενεργή." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Για την Προτεραιότητα φωνητικής δραστηριότητας, χρησιμοποιήστε ένα μόνο πλήκτρο χωρίς Ctrl, Alt ή Shift. Αν χρησιμοποιείτε συνδυασμό, αφήνετε πρώτα το κύριο πλήκτρο, αλλιώς η προτεραιότητα μπορεί να παραμείνει ενεργή." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Για την ασφάλειά σας, επιβεβαιώστε ότι είστε εσείς για να συνεχίσετε." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Γενικό προφίλ" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Καθολική συντόμευση" @@ -17434,6 +17505,11 @@ msgstr "Κρατήστε πατημένο το Shift και πατήστε το msgid "Hold to record. Drag up to lock, or release to send." msgstr "Πατήστε παρατεταμένα για εγγραφή. Σύρετε προς τα πάνω για κλείδωμα ή αφήστε για αποστολή." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Όσο κρατάτε πατημένο το Shift, το Ctrl ή το Alt, αυτή η συντόμευση δεν λειτουργεί. Προσθέστε εναλλακτικά πλήκτρα στις ρυθμίσεις του συστήματός σας." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Ζήτω! Οι ειδοποιήσεις είναι ενεργοποιη msgid "Hyperlink" msgstr "Υπερσύνδεσμος" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Το Hyprland δεν έχει παράθυρο συντομεύσεων. Προσθέστε τις γραμμές για την έκδοση του Hyprland σας στο αρχείο διαμόρφωσης του Hyprland και αντικαταστήστε το KEY με το πλήκτρο που θέλετε." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Το Hyprland δεν έχει παράθυρο συντομεύσεων. Αντιστοιχίστε τα πλήκτρα στο αρχείο διαμόρφωσης του Hyprland." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (πριν το Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 και νεότερες εκδόσεις)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Συμφωνώ με την <0>Πολιτική απορρήτου" @@ -17576,7 +17672,7 @@ msgstr "Άλλαξα γνώμη" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Καταλαβαίνω" @@ -18000,7 +18096,7 @@ msgstr "Σε" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "Στη λειτουργία \"Λειτουργικό σύστημα\", τα λεξικά που αναφέρονται παραπάνω είναι ενδεικτικά. Το {PRODUCT_NAME} θα περάσει αυτές τις ετικέτες γλώσσας στον ορθογραφικό έλεγχο του λειτουργικού σας συστήματος, εφόσον υποστηρίζεται. Οι ετικέτες που δεν αναγνωρίζει αγνοούνται." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "Σε ένα πρόγραμμα περιήγησης, η λειτουργία push-to-talk λειτουργεί μόνο όταν η καρτέλα {PRODUCT_NAME} είναι ενεργή. Εγκαταστήστε την εφαρμογή υπολογιστή για push-to-talk σε όλο το σύστημα." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Εισαγωγή" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Δεν ήταν δυνατή η αυτόματη ενεργοποίηση της πρόσβασης εισόδου. Αλλάξτε την χειροκίνητα και, στη συνέχεια, κλείστε εντελώς και επανεκκινήστε το {productName}, ώστε οι συντομεύσεις να μπορούν να τη χρησιμοποιήσουν." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Δεν ήταν δυνατή η ενεργοποίηση της πρόσβασης εισόδου. Δοκιμάστε ξανά από τις ρυθμίσεις {settingsMenuName}, και μετά κλείστε εντελώς και επανεκκινήστε το {productName}." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Απαιτείται πρόσβαση εισόδου για συντομεύσεις πληκτρολογίου και ποντικιού σε όλο το σύστημα στο Wayland. Αφού αλλάξετε την πρόσβαση εισόδου, κλείστε πλήρως και επανεκκινήστε το {productName}, ώστε οι συντομεύσεις να μπορούν να τη χρησιμοποιήσουν." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Η πρόσβαση εισόδου άλλαξε. Κλείστε εντελώς και επανεκκινήστε το {productName} ώστε να μπορούν να χρησιμοποιηθούν οι συντομεύσεις σε όλο το σύστημα." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Είσοδος και έξοδος" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Μη έγκυρο αίτημα εξουσιοδότησης" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Μη έγκυρα δικαιώματα bot." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Μη έγκυρος ή ληγμένος κωδικός. Δοκιμάστε ξανά." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Μη έγκυρο redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Ο σύνδεσμος πρόσκλησης είναι κρυφός κατά την κοινή χρήση:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Ο σύνδεσμος πρόσκλησης αποκρύφτηκε κατά την κοινή χρήση" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Μετάβαση σε {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Μετάβαση σε {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Μετάβαση στην τρέχουσα κλήση" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Μετάβαση στο συνδεδεμένο κανάλι" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Μετάβαση στο μήνυμα του συνδέσμου" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Μετάβαση στο μήνυμα του/της {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Μετάβαση στο μήνυμα στο {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Κράτησέ τα ασφαλή. Η αναδημιουργία θα διακόψει τις υπάρχουσες ενσωματώσεις." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Συνεχίζει τη μετάδοση για λίγο αφού αφήσετε το πλήκτρο." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Πληκτρολόγιο" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Δεν ήταν δυνατή η έναρξη της πρόσβασης στο πληκτρολόγιο. Οι συντομεύσεις λειτουργούν μόνο όταν το {productName} έχει την εστίαση." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Άσε την κοινότητα να συντονίζεται μόνη msgid "Let the member hear others in this community again" msgstr "Επέτρεψε στο μέλος να ακούει ξανά τους άλλους σε αυτήν την κοινότητα" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Επιτρέπει στο {productName} να διαβάζει απευθείας το πληκτρολόγιό σας. Το σύστημά σας το επιτρέπει ήδη." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Επιτρέπει στο προσαρμοσμένο CSS να αποκαλύπτει διαφανείς περιοχές παραθύρων μετά την επανεκκίνηση της εφαρμογής υπολογιστή." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Στιλ συνδέσμου" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Ο σύνδεσμος δεν είναι διαθέσιμος" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Εμφάνιση και αίσθηση" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Φαίνεται ότι χρησιμοποιείτε {0}. Η προσθήκη της επεξεργασίας του {PRODUCT_NAME} από πάνω μπορεί να προκαλέσει διακυμάνσεις έντασης ή παραμόρφωση. Η άμεση είσοδος περνάει τον ήχο σας χωρίς επεξεργασία." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Διάταξη μηνυμάτων" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "σύνδεσμος μηνύματος" @@ -21332,7 +21416,7 @@ msgstr "Μικρόφωνο" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Δοκιμή μικροφώνου" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Διάφορα" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Λείπει το client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Περισσότερες επιλογές" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Περισσότερες επιλογές συντομεύσεων" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Δεν υπάρχει δικαίωμα {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Δεν υπάρχει πρόσβαση" @@ -22984,7 +23068,7 @@ msgstr "Δεν βρέθηκαν αποτελέσματα" msgid "No mentions yet" msgstr "Δεν υπάρχουν αναφορές ακόμα" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Δεν βρέθηκε μικρόφωνο" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Δεν βρέθηκαν ρυθμίσεις" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Δεν έχει οριστεί συντόμευση" @@ -23358,6 +23442,11 @@ msgstr "Δεν είναι ακέραιος αριθμός" msgid "Not accepted" msgstr "Δεν έγινε αποδεκτή" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Δεν έχει οριστεί" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Ανοίξτε το {host} στο πρόγραμμα περιήγησής σας." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Άνοιγμα {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Άνοιγμα {labelText} στις ρυθμίσεις" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "σύνδεσμος σελίδας" @@ -25611,11 +25700,11 @@ msgstr "Παρουσία" msgid "Press {shortcut} to react" msgstr "Πατήστε {shortcut} για αντίδραση" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Πατήστε ένα πλήκτρο, ένα κουμπί του ποντικιού ή ένα κουμπί του χειριστηρίου για αντιστοίχιση. Το Escape ακυρώνει." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Πατήστε μια συντόμευση…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Πλήκτρο «Push to talk»" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Push-to-talk" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Push-to-talk (περιορισμένο)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Η λειτουργία push-to-talk δεν έχει ακόμα πλήκτρο σε όλο το σύστημα. Για να ορίσετε ένα, μεταβείτε στις Ρυθμίσεις > Εφαρμογές > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Η λειτουργία push-to-talk δεν έχει ακόμα πλήκτρο σε όλο το σύστημα. Για να ορίσετε ένα, μεταβείτε στις Ρυθμίσεις συστήματος > Πληκτρολόγιο > Συντομεύσεις > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Η λειτουργία push-to-talk δεν έχει ακόμα πλήκτρο σε όλο το σύστημα. Για να ορίσετε ένα, ανοίξτε τις ρυθμίσεις συντομεύσεων πληκτρολογίου της επιφάνειας εργασίας σας." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Η λειτουργία push-to-talk αγνοείται στην άμεση εισαγωγή. Μεταβείτε σε εστιασμένη φωνή ή προσαρμοσμένη για να τη χρησιμοποιήσετε." @@ -26293,21 +26397,51 @@ msgstr "Η λειτουργία push-to-talk αγνοείται στην άμε msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Η λειτουργία push-to-talk είναι ενεργή. Κρατήστε πατημένο το {pushToTalkHint} για να μιλήσετε." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Η λειτουργία push-to-talk είναι ενεργή. Κρατήστε πατημένο το πλήκτρο push-to-talk για να μιλήσετε." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Η λειτουργία push-to-talk λειτουργεί μόνο όταν το {productName} έχει την εστίαση, επειδή η επιφάνεια εργασίας σας δεν ρύθμισε συντομεύσεις σε όλο το σύστημα." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Η λειτουργία push-to-talk λειτουργεί μόνο όταν το {productName} είναι σε πρώτο πλάνο, μέχρι να ενεργοποιηθεί η άδεια {permissionName}. Αφού αλλάξετε την άδεια, κλείστε εντελώς και επανεκκινήστε το {productName}, ώστε να μπορούν να χρησιμοποιηθούν οι συντομεύσεις πλήκτρων σε όλο το σύστημα." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Η λειτουργία push-to-talk λειτουργεί μόνο όταν το {productName} έχει την εστίαση, μέχρι να ρυθμίσετε συντομεύσεις σε όλο το σύστημα." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Η λειτουργία push-to-talk λειτουργεί μόνο όταν η καρτέλα του προγράμματος περιήγησης είναι εστιασμένη." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Η λειτουργία push-to-talk θα χρησιμοποιεί το πλήκτρο που έχετε ορίσει στο {productName}. Ορίστε πρώτα ένα, αλλιώς το μικρόφωνό σας θα λειτουργεί με φωνητική δραστηριότητα." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Η λειτουργία push-to-talk δεν θα καταργήσει τη σίγαση του μικροφώνου σας όσο έχετε σίγαση ήχου. Καταργήστε τη σίγαση ήχου για να μιλήσετε." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Η λειτουργία push-to-talk λειτουργεί εκτός του {productName} μόνο αφού την αντιστοιχίσετε στο αρχείο διαμόρφωσης του Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Η λειτουργία push-to-talk λειτουργεί εκτός του {productName} μόνο αφού την αντιστοιχίσετε στο αρχείο διαμόρφωσης του Hyprland. Προσθέστε τη γραμμή για την έκδοση του Hyprland σας και αντικαταστήστε το KEY με το πλήκτρο που θέλετε." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Ακατέργαστο JSON ενσωμάτωσης" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Επανενεργοποίηση ενσωματωμένης συντόμευσης" @@ -26565,13 +26699,6 @@ msgstr "Πρόσφατες αναφορές" msgid "Recent searches" msgstr "Πρόσφατες αναζητήσεις" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Επανέλεγχος" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Προτεινόμενο" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Προτείνεται. Καθαρίζει το μικρόφωνό σας για καθαρή ομιλία." @@ -26600,6 +26727,11 @@ msgstr "Επανασύνδεση" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Η επανασύνδεση ήταν επιτυχής. Επιλέξτε ξανά μια οθόνη, αν θέλετε να συνεχίσετε την προηγούμενη ροή σας." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Επανασύνδεση στην επιφάνεια εργασίας σας…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Επανασύνδεση…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Καταγραφή συντόμευσης" @@ -26851,7 +26983,7 @@ msgstr "Σχετική" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Καθυστέρηση απελευθέρωσης" @@ -27739,13 +27871,13 @@ msgstr "Εστάλη ξανά" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Επαναφορά" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Επαναφορά \"{label}\" στην προεπιλογή" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Επαναφορά στις πραγματικές τιμές" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Επαναφορά στην προεπιλογή" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Επιλέξτε τον λογαριασμό με τον οποίο θέλετε να συνδεθείτε στη νέα σας συσκευή." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Επιλέξτε λειτουργία επεξεργασίας φωνής" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Στέλνει ακατέργαστο ήχο μικροφώνου χωρίς επεξεργασία." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Στέλνει τον ήχο σας χωρίς επεξεργασία. Ιδανικό αν χρησιμοποιείτε εξωτερικό λογισμικό ήχου." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Ορίζεται από την προεπιλογή {preset}. Η επιλογή μιας τιμής εδώ θα αλλάξει σε Προσαρμοσμένη." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Ορισμός προσαρμοσμένης συντόμευσης" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Ορισμός προσαρμοσμένης συντόμευσης για «{label}»" @@ -30066,6 +30198,11 @@ msgstr "Ορίστηκε η περιοχή φωνής σε {region}" msgid "Set the voice region to automatic" msgstr "Ορίστηκε η περιοχή φωνής σε αυτόματη" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Ρύθμιση" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Ρυθμίσεις" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "σύνδεσμος ρυθμίσεων" @@ -30330,8 +30467,8 @@ msgstr "Κωδικοί emoji" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Εναλλαγή σε άνετη διάταξη" msgid "Switch to dense layout" msgstr "Εναλλαγή σε συμπαγή διάταξη" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Εναλλαγή σε απευθείας εισαγωγή" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Εγκατάσταση διαχειριζόμενη από το σύστημα" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Συντομεύσεις σε όλο το σύστημα" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Δεν ήταν δυνατή η ρύθμιση των συντομεύσεων σε όλο το σύστημα." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Σε όλο το σύστημα: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Σε όλο το σύστημα: Δεν έχει οριστεί" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Η σελίδα που ψάχνετε δεν υπάρχει ή έχει μετακινηθεί." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Το redirect_uri που δόθηκε δεν είναι καταχωρισμένο για αυτήν την εφαρμογή." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Το redirect_uri που δόθηκε πρέπει να χρησιμοποιεί HTTP ή HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Αυτό το κινούμενο σχέδιο είναι πολύ μεγάλο ή έχει πάρα πολλά καρέ για να γίνει περικοπή. Δοκιμάστε ένα μικρότερο αρχείο." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Αυτή η εφαρμογή" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Αυτό το συνημμένο έχει λήξει" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Αυτό το bot δεν είναι δημόσιο και δεν μπορεί να προστεθεί από άλλους χρήστες." @@ -34081,6 +34233,16 @@ msgstr "Αυτή η συνομιλία δεν είναι πλέον διαθέσ msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Αυτή η έκδοση για υπολογιστές δεν μπορεί να εγκαταστήσει ενημερώσεις μέσα από το {productName}. Κατεβάστε το πιο πρόσφατο πρόγραμμα εγκατάστασης για υπολογιστές." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Αυτή η επιφάνεια εργασίας δεν προσφέρει συντομεύσεις σε όλο το σύστημα. Η λειτουργία push-to-talk λειτουργεί μόνο όταν το {productName} έχει την εστίαση." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Αυτή η επιφάνεια εργασίας δεν προσφέρει συντομεύσεις σε όλο το σύστημα. Οι συντομεύσεις εξακολουθούν να λειτουργούν όσο το {productName} έχει την εστίαση." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Αυτό είναι το τελευταίο σας κλειδί πρόσ msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Αυτό είναι το τελευταίο σας κλειδί πρόσβασης. Μόλις διαγραφεί, δεν θα μπορείτε να χρησιμοποιήσετε κλειδί πρόσβασης ως δεύτερο παράγοντα." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Αυτό το πλήκτρο λειτουργεί όσο το {productName} έχει την εστίαση. Το πλήκτρο για όλο το σύστημα το ορίζει η επιφάνεια εργασίας σας, στην ενότητα {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Αυτό το κανάλι συνδέσμου έχει επισημανθεί για περιεχόμενο ενηλίκων και ενδέχεται να ανοίξει υλικό που μπορεί να είναι ακατάλληλο για ορισμένους χρήστες." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Αυτός ο σύνδεσμος δεν είναι διαθέσιμος για τον λογαριασμό σας." @@ -34778,6 +34945,21 @@ msgstr "Γραμμή τίτλου" msgid "Title bar platform" msgstr "Πλατφόρμα γραμμής τίτλου" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Για να αλλάξετε τα πλήκτρα, μεταβείτε στις Ρυθμίσεις > Εφαρμογές > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Για να αλλάξετε τα πλήκτρα, μεταβείτε στις Ρυθμίσεις συστήματος > Πληκτρολόγιο > Συντομεύσεις > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Για να αλλάξετε τα πλήκτρα, ανοίξτε τις ρυθμίσεις συντομεύσεων πληκτρολογίου της επιφάνειας εργασίας σας." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Για να δημιουργήσετε ένα webhook, ανοίξτε <0>{channelWebhooksSettingsPath}. Μπορείτε ακόμα να επεξεργαστείτε και να οργανώσετε όλα τα υπάρχοντα webhooks εδώ." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Άγνωστο" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Άγνωστη εφαρμογή, ληγμένη συνεδρία ή μη έγκυρο αίτημα εξουσιοδότησης." @@ -36410,6 +36592,11 @@ msgstr "Έως 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "Έως 720p στα 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Ενημερώστε το {productName} για να χρησιμοποιείτε συντομεύσεις σε όλο το σύστημα στο Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Χρήση σκούρου θέματος σε όλη την εφαρμο msgid "Use default" msgstr "Χρήση προεπιλογής" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Χρήση άμεσης πρόσβασης στις συσκευές εισόδου" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Συμμετέχοντες φωνητικής συνομιλίας" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Επεξεργασία φωνής" @@ -38264,7 +38456,7 @@ msgstr "Κατάργηση σίγασης ήχου" msgid "Voice unmute" msgstr "Κατάργηση σίγασης μικροφώνου" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Η φωνή χρησιμοποιεί τη συσκευή εξόδου του συστήματός σας σε αυτό το πρόγραμμα περιήγησης. Αλλάξτε την στις ρυθμίσεις του συστήματός σας." @@ -38278,7 +38470,7 @@ msgstr "Άκυρο" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Αναμονή λήξης της αργής λειτουργίας σε msgid "Waiting for you to..." msgstr "Σε περιμένουμε να..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Αναμονή για την επιφάνεια εργασίας σας…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Ποιες συνομιλίες" msgid "Which rule was broken?" msgstr "Ποιος κανόνας παραβιάστηκε;" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Όσο η λειτουργία push-to-talk είναι ενεργή, η συντόμευσή σας ελέγχει το μικρόφωνό σας, οπότε το κουμπί σίγασης είναι απενεργοποιημένο." @@ -39094,6 +39291,11 @@ msgstr "Χωρίς αυτό το δικαίωμα, απαιτείται η λε msgid "Wordmark" msgstr "Λεκτικό λογότυπο" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Λειτουργεί όταν το {productName} έχει την εστίαση" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Αλλάξατε την ετικέτα σας ενώ είχατε {PREMIUM_PRODUCT_NAME}. Θα αλλάξει τυχαία όταν λήξει το {PREMIUM_PRODUCT_NAME} σας, εκτός αν ανανεωθεί." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Δεν έχετε πρόσβαση στο κανάλι όπου στάλθηκε αυτό το μήνυμα." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Η συνδρομή σας στο {premiumProductName} έληξε. Έχετε χάσει όλα τα προνόμια του {premiumProductName}. Ενεργοποιήστε ξανά τη συνδρομή σας για να αποκτήσετε ξανά πρόσβαση." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Η περίοδος λειτουργίας σας στο {productName} έληξε. Συνδεθείτε ξανά για να συνεχίσετε την εξουσιοδότηση αυτής της εφαρμογής." @@ -40144,6 +40346,16 @@ msgstr "Η κοινότητά σας χρειάζεται τουλάχιστον msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Η πιστωτική σας κάρτα έχει ελεγχθεί. Εάν η κάρτα είναι έγκυρη, το περιεχόμενο ενηλίκων θα ξεκλειδωθεί σύντομα. Μπορείτε να κλείσετε αυτήν την καρτέλα και να επιστρέψετε στην εφαρμογή." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Η επιφάνεια εργασίας σας δεν ρύθμισε συντομεύσεις σε όλο το σύστημα." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Η επιφάνεια εργασίας σας εκτελεί αυτές τις συντομεύσεις ακόμα κι όταν το {productName} δεν έχει την εστίαση." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/en-GB/messages.po b/fluxer_app/src/features/i18n/locales/en-GB/messages.po index 196891af9..2cdf872bb 100644 --- a/fluxer_app/src/features/i18n/locales/en-GB/messages.po +++ b/fluxer_app/src/features/i18n/locales/en-GB/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# channel follows} other {# channels follow}} this msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# community found} other {# communities found}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} avoids sending push notifications to your mobile devices w msgid "{productName} Bug Hunter" msgstr "{productName} Bug Hunter" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} needs camera access to list your devices." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} needs microphone access to list your devices." @@ -1837,6 +1862,11 @@ msgstr "{productName} now lives at {host}. Install it from there." msgid "{productName} Partner" msgstr "{productName} Partner" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "A profile that stands out" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "A redirect_uri is required when the bot scope is not the only scope." @@ -2847,7 +2877,7 @@ msgstr "Across all open DMs only" msgid "Across every community you're in" msgstr "Across every community you're in" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Action" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Activity sharing" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Activity threshold" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Add an extra layer of security to your account" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Add another custom shortcut" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Address" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Adjust each setting yourself: noise suppression, echo cancellation, and gain." @@ -3696,11 +3726,11 @@ msgstr "Allow {everyoneMention} and {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." @@ -3803,7 +3833,7 @@ msgstr "Allow members without the {readMessageHistoryPermissionLabel} permission msgid "Allow mentions for this role" msgstr "Allow mentions for this role" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Allow microphone" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Authorisation failed" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Authorisation failed. Try again." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Auto-detect language" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Auto-set activity threshold" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Bot permissions" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Bot permissions cannot be negative." @@ -6869,6 +6899,11 @@ msgstr "Change group nickname" msgid "Change icon" msgstr "Change icon" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Change in system settings" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Channel access" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Channel access denied" @@ -7294,7 +7329,7 @@ msgstr "Channel ID copied to clipboard" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "channel link" @@ -7462,6 +7497,11 @@ msgstr "chat input" msgid "Chat shortcuts" msgstr "Chat shortcuts" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Check again" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Choose a window in your system picker" msgid "Choose an account" msgstr "Choose an account" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Choose an action." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Choose background" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Choose either guild_id or channel_id, not both." @@ -7763,7 +7803,6 @@ msgstr "Choose what {clientLabel} can do in your community. Uncheck any permissi msgid "Choose what friends see in their active now panel" msgstr "Choose what friends see in their active now panel" -# auto-i18n: reviewed unchanged #: src/features/guild/components/modals/guild_tabs/GuildModerationTab.tsx:291 msgid "Choose what members must have before they can post or DM community members. Members with roles can bypass these checks. For public spaces, we recommend enabling verification." msgstr "Choose what members must have before they can post or DM community members. Members with roles can bypass these checks. For public spaces, we recommend enabling verification." @@ -7991,7 +8030,7 @@ msgstr "Cleans up your mic for clear speech." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8192,7 +8231,7 @@ msgstr "Click to open the colour picker and choose a new colour." msgid "Click to react" msgstr "Click to react" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Click to record" @@ -9111,7 +9150,7 @@ msgstr "Confirmation" msgid "Confirmation or double-click for community voice joins" msgstr "Confirmation or double-click for community voice joins" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Conflicts with {conflictLabel}" @@ -9128,7 +9167,7 @@ msgstr "Connect a camera or virtual capture device, then try again." msgid "Connect a display, then try again." msgstr "Connect a display, then try again." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Connect a microphone and try again." @@ -9701,6 +9740,21 @@ msgstr "Copy invite link" msgid "Copy invite URL" msgstr "Copy invite URL" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Copy lines" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Copy lines for hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Copy lines for hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10846,7 +10900,7 @@ msgstr "Custom" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10949,12 +11003,12 @@ msgid "Custom selection" msgstr "Custom selection" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Custom shortcut action" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Custom shortcut options" @@ -11467,7 +11521,7 @@ msgstr "Default shortcuts" msgid "Default translator" msgstr "Default translator" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Default:" @@ -11716,7 +11770,7 @@ msgid "Delete saved media" msgstr "Delete saved media" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Delete shortcut" @@ -12103,13 +12157,13 @@ msgstr "Developer state" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Developer tools" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Developer tools are available from the staff badge in the channel header." @@ -12314,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Disable backend premium perks" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Disable built-in shortcut" @@ -12378,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Disable screen share picture-in-picture popouts?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Disable shortcut" @@ -12889,7 +12943,7 @@ msgstr "Donate instead" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12952,7 +13006,7 @@ msgid "Download button" msgstr "Download button" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Download desktop app" @@ -13205,6 +13259,11 @@ msgstr "Dutch" msgid "e.g., YubiKey, iPhone, work computer" msgstr "e.g., YubiKey, iPhone, work computer" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Each key can only run one system-wide shortcut." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13351,7 +13410,7 @@ msgid "Edit invite link" msgstr "Edit invite link" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Edit keyboard shortcut" @@ -13426,7 +13485,7 @@ msgstr "Edit saved media form" msgid "Edit search engine" msgstr "Edit search engine" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Edit shortcut" @@ -13754,20 +13813,16 @@ msgstr "Empty edit" msgid "Empty favorites" msgstr "Empty favourites" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Enable" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Enable {permissionName} permission" @@ -13877,11 +13932,6 @@ msgstr "Enable hardware acceleration and restart" msgid "Enable hardware acceleration?" msgstr "Enable hardware acceleration?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13972,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Enable Shift to expand" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Enable shortcut" @@ -14358,7 +14408,7 @@ msgstr "Entire system audio" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Entrance sound" @@ -14468,7 +14518,7 @@ msgstr "Evening time" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Evens out your mic volume so you are not too quiet." @@ -15683,6 +15733,26 @@ msgstr "For British eyes only..." msgid "For me" msgstr "For me" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "For your security, please confirm it's you to continue." @@ -16601,8 +16671,8 @@ msgid "Global profile" msgstr "Global profile" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Global shortcut" @@ -17435,6 +17505,11 @@ msgstr "Hold Shift and press X to select apps. Press {selectAllShortcutLabel} to msgid "Hold to record. Drag up to lock, or release to send." msgstr "Hold to record. Drag up to lock, or release to send." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17538,6 +17613,26 @@ msgstr "Huzzah! Notifications are enabled" msgid "Hyperlink" msgstr "Hyperlink" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (before Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 and later)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "I agree to the <0>Privacy policy" @@ -17577,7 +17672,7 @@ msgstr "I changed my mind" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "I understand" @@ -18001,7 +18096,7 @@ msgstr "In" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognise are ignored." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." @@ -18269,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Input" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Input and output" @@ -18451,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Invalid authorisation request" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Invalid bot permissions." @@ -18494,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Invalid or expired code. Try again." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Invalid redirect_uri" @@ -18598,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Invite link is hidden while sharing:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Invite link masked while sharing" @@ -19012,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Jump to {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Jump to {labelText}" @@ -19069,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Jump to the current call" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Jump to the linked channel" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Jump to the linked message" @@ -19084,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Jump to the message from {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Jump to the message in {labelText}" @@ -19168,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Keep these safe. Regenerating will break existing integrations." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Keep transmitting briefly after release." @@ -19205,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Keyboard" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19578,6 +19656,11 @@ msgstr "Let the community self-moderate" msgid "Let the member hear others in this community again" msgstr "Let the member hear others in this community again" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Lets {productName} read your keyboard directly. Your system already allows this." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Lets custom CSS reveal transparent window regions after restarting the desktop app." @@ -19761,7 +19844,7 @@ msgid "Link styling" msgstr "Link styling" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Link unavailable" @@ -20037,7 +20120,7 @@ msgid "Look and feel" msgstr "Look and feel" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." @@ -21145,7 +21228,7 @@ msgid "Message layout" msgstr "Message layout" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "message link" @@ -21333,7 +21416,7 @@ msgstr "Mic" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Mic test" @@ -21473,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Miscellaneous" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Missing client_id" @@ -21691,7 +21774,7 @@ msgid "More options" msgstr "More options" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "More shortcut options" @@ -22673,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "No {sendMessagesPermission} permission" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "No access" @@ -22985,7 +23068,7 @@ msgstr "No matches found" msgid "No mentions yet" msgstr "No mentions yet" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "No microphone found" @@ -23135,7 +23218,7 @@ msgid "No settings found" msgstr "No settings found" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "No shortcut set" @@ -23359,6 +23442,11 @@ msgstr "Not a whole number" msgid "Not accepted" msgstr "Not accepted" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Not assigned" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23859,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Open {host} in your browser." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Open {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Open {labelText} in settings" @@ -24612,7 +24700,7 @@ msgid "Page down" msgstr "Page down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "page link" @@ -25612,11 +25700,11 @@ msgstr "Presence" msgid "Press {shortcut} to react" msgstr "Press {shortcut} to react" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Press a key, mouse button, or gamepad button to bind. Escape cancels." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Press a shortcut…" @@ -26274,18 +26362,33 @@ msgid "Push to talk key" msgstr "Push to talk key" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Push-to-talk" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Push-to-talk (limited)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." @@ -26294,21 +26397,51 @@ msgstr "Push-to-talk is ignored in direct input. Switch to focused voice or cust msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Push-to-talk is on. Hold {pushToTalkHint} to speak." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Push-to-talk is on. Hold your push-to-talk key to speak." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Push-to-talk only works while the browser tab is focused." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26407,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Raw embed JSON" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Re-enable built-in shortcut" @@ -26566,13 +26699,6 @@ msgstr "Recent mentions" msgid "Recent searches" msgstr "Recent searches" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Recheck" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26587,7 +26713,7 @@ msgid "Recommended" msgstr "Recommended" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Recommended. Cleans up your mic for clear speech." @@ -26601,6 +26727,11 @@ msgstr "Reconnect" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Reconnecting to your desktop…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26612,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Reconnecting…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Record shortcut" @@ -26852,7 +26983,7 @@ msgstr "Relative" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Release delay" @@ -27740,13 +27871,13 @@ msgstr "Resent" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Reset" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Reset \"{label}\" to built-in" @@ -27867,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Reset to actual values" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Reset to built-in" @@ -29510,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Select the account you want to sign in with on your new device." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Select voice processing mode" @@ -29815,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Sends raw mic audio with no processing." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Sends your audio untouched. Best if you're using external audio software." @@ -29914,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Set by the {preset} preset. Picking a value here switches to Custom." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Set custom shortcut" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Set custom shortcut for \"{label}\"" @@ -30067,6 +30198,11 @@ msgstr "Set the voice region to {region}" msgid "Set the voice region to automatic" msgstr "Set the voice region to automatic" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Set up" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30119,7 +30255,7 @@ msgid "Settings" msgstr "Settings" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "settings link" @@ -30331,8 +30467,8 @@ msgstr "Shortcodes" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32760,7 +32896,7 @@ msgstr "Switch to comfy layout" msgid "Switch to dense layout" msgstr "Switch to dense layout" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Switch to direct input" @@ -33028,10 +33164,25 @@ msgid "System-managed install" msgstr "System-managed install" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "System-wide shortcuts" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "System-wide shortcuts could not be set up." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "System-wide: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "System-wide: Not assigned" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33613,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "The page you're looking for doesn't exist or has been moved." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "The provided redirect_uri is not registered for this application." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "The provided redirect_uri must use HTTP or HTTPS." @@ -33851,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "This animation is too long or too large to crop. Try a shorter or smaller file." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "This application" @@ -33861,7 +34012,7 @@ msgid "This attachment has expired" msgstr "This attachment has expired" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "This bot is not public and cannot be added by other users." @@ -34082,6 +34233,16 @@ msgstr "This conversation is no longer available." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34295,6 +34456,11 @@ msgstr "This is your last passkey and passkeys are your second factor. Deleting msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34306,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "This link channel is marked for mature content and may open material that may be inappropriate for some users." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "This link is not available for your account." @@ -34779,6 +34945,21 @@ msgstr "Title bar" msgid "Title bar platform" msgstr "Title bar platform" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "To change the keys, go to Settings > Apps > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "To change the keys, open your desktop's keyboard shortcut settings." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organise all existing webhooks here." @@ -35873,7 +36054,7 @@ msgid "Unknown" msgstr "Unknown" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Unknown application, expired session, or invalid authorisation request." @@ -36411,6 +36592,11 @@ msgstr "Up to 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "Up to 720p at 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Update {productName} to use system-wide shortcuts on Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36852,6 +37038,11 @@ msgstr "Use dark theme across the application" msgid "Use default" msgstr "Use default" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Use direct input device access" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38219,7 +38410,7 @@ msgid "Voice participants" msgstr "Voice participants" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Voice processing" @@ -38265,7 +38456,7 @@ msgstr "Voice undeafen" msgid "Voice unmute" msgstr "Voice unmute" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Voice uses your system output device in this browser. Change it in your system settings." @@ -38279,7 +38470,7 @@ msgstr "Void" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38346,6 +38537,11 @@ msgstr "Waiting for slowmode in one or more selected channels to expire." msgid "Waiting for you to..." msgstr "Waiting for you to..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Waiting for your desktop…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38927,7 +39123,7 @@ msgstr "Which conversations" msgid "Which rule was broken?" msgstr "Which rule was broken?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." @@ -39095,6 +39291,11 @@ msgstr "Without this permission, push-to-talk is required." msgid "Wordmark" msgstr "Wordmark" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Works while {productName} is focused" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39522,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll randomly when your {PREMIUM_PRODUCT_NAME} expires, unless it renews." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "You do not have access to the channel where this message was sent." @@ -40057,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Your {premiumProductName} subscription has expired. You have lost all {premiumProductName} perks. Reactivate your subscription to regain access." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Your {productName} session expired. Sign in again to continue authorising this application." @@ -40145,6 +40346,18 @@ msgstr "Your community needs at least {minMemberCount, plural, one {# member} ot msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." +# auto-i18n: reviewed unchanged +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Your desktop didn't set up system-wide shortcuts." + +# auto-i18n: reviewed unchanged +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Your desktop runs these shortcuts even while {productName} is not focused." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/en-US/messages.po b/fluxer_app/src/features/i18n/locales/en-US/messages.po index a9769e571..18bdae767 100644 --- a/fluxer_app/src/features/i18n/locales/en-US/messages.po +++ b/fluxer_app/src/features/i18n/locales/en-US/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# channel follows} other {# channels follow}} this msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# community found} other {# communities found}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} avoids sending push notifications to your mobile devices w msgid "{productName} Bug Hunter" msgstr "{productName} Bug Hunter" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} needs camera access to list your devices." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} needs microphone access to list your devices." @@ -1837,6 +1862,11 @@ msgstr "{productName} now lives at {host}. Install it from there." msgid "{productName} Partner" msgstr "{productName} Partner" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "A profile that stands out" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "A redirect_uri is required when the bot scope is not the only scope." @@ -2847,7 +2877,7 @@ msgstr "Across all open DMs only" msgid "Across every community you're in" msgstr "Across every community you're in" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Action" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Activity sharing" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Activity threshold" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Add an extra layer of security to your account" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Add another custom shortcut" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Address" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Adjust each setting yourself: noise suppression, echo cancellation, and gain." @@ -3696,11 +3726,11 @@ msgstr "Allow {everyoneMention} and {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." @@ -3803,7 +3833,7 @@ msgstr "Allow members without the {readMessageHistoryPermissionLabel} permission msgid "Allow mentions for this role" msgstr "Allow mentions for this role" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Allow microphone" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Authorization failed" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Authorization failed. Try again." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Auto-detect language" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Auto-set activity threshold" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Bot permissions" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Bot permissions cannot be negative." @@ -6869,6 +6899,11 @@ msgstr "Change group nickname" msgid "Change icon" msgstr "Change icon" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Change in system settings" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Channel access" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Channel access denied" @@ -7294,7 +7329,7 @@ msgstr "Channel ID copied to clipboard" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "channel link" @@ -7462,6 +7497,11 @@ msgstr "chat input" msgid "Chat shortcuts" msgstr "Chat shortcuts" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Check again" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Choose a window in your system picker" msgid "Choose an account" msgstr "Choose an account" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Choose an action." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Choose background" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Choose either guild_id or channel_id, not both." @@ -7990,7 +8030,7 @@ msgstr "Cleans up your mic for clear speech." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Click to open the color picker and choose a new color." msgid "Click to react" msgstr "Click to react" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Click to record" @@ -9110,7 +9150,7 @@ msgstr "Confirmation" msgid "Confirmation or double-click for community voice joins" msgstr "Confirmation or double-click for community voice joins" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Conflicts with {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Connect a camera or virtual capture device, then try again." msgid "Connect a display, then try again." msgstr "Connect a display, then try again." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Connect a microphone and try again." @@ -9700,6 +9740,21 @@ msgstr "Copy invite link" msgid "Copy invite URL" msgstr "Copy invite URL" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Copy lines" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Copy lines for hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Copy lines for hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Custom" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Custom selection" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Custom shortcut action" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Custom shortcut options" @@ -11466,7 +11521,7 @@ msgstr "Default shortcuts" msgid "Default translator" msgstr "Default translator" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Default:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Delete saved media" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Delete shortcut" @@ -12102,13 +12157,13 @@ msgstr "Developer state" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Developer tools" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Developer tools are available from the staff badge in the channel header." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Disable backend premium perks" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Disable built-in shortcut" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Disable screen share picture-in-picture popouts?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Disable shortcut" @@ -12888,7 +12943,7 @@ msgstr "Donate instead" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Download button" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Download desktop app" @@ -13204,6 +13259,11 @@ msgstr "Dutch" msgid "e.g., YubiKey, iPhone, work computer" msgstr "e.g., YubiKey, iPhone, work computer" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Each key can run only one system-wide shortcut." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Edit invite link" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Edit keyboard shortcut" @@ -13425,7 +13485,7 @@ msgstr "Edit saved media form" msgid "Edit search engine" msgstr "Edit search engine" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Edit shortcut" @@ -13753,20 +13813,16 @@ msgstr "Empty edit" msgid "Empty favorites" msgstr "Empty favorites" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Enable" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Enable {permissionName} permission" @@ -13876,11 +13932,6 @@ msgstr "Enable hardware acceleration and restart" msgid "Enable hardware acceleration?" msgstr "Enable hardware acceleration?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Enable Shift to expand" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Enable shortcut" @@ -14357,7 +14408,7 @@ msgstr "Entire system audio" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Entrance sound" @@ -14467,7 +14518,7 @@ msgstr "Evening time" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Evens out your mic volume so you are not too quiet." @@ -15682,6 +15733,26 @@ msgstr "For British eyes only..." msgid "For me" msgstr "For me" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "For your security, please confirm it's you to continue." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Global profile" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Global shortcut" @@ -17434,6 +17505,11 @@ msgstr "Hold Shift and press X to select apps. Press {selectAllShortcutLabel} to msgid "Hold to record. Drag up to lock, or release to send." msgstr "Hold to record. Drag up to lock, or release to send." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Huzzah! Notifications are enabled" msgid "Hyperlink" msgstr "Hyperlink" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (before Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 and later)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "I agree to the <0>Privacy policy" @@ -17576,7 +17672,7 @@ msgstr "I changed my mind" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "I understand" @@ -18000,7 +18096,7 @@ msgstr "In" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Input" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Input and output" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Invalid authorization request" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Invalid bot permissions." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Invalid or expired code. Try again." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Invalid redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Invite link is hidden while sharing:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Invite link masked while sharing" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Jump to {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Jump to {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Jump to the current call" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Jump to the linked channel" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Jump to the linked message" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Jump to the message from {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Jump to the message in {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Keep these safe. Regenerating will break existing integrations." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Keep transmitting briefly after release." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Keyboard" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Let the community self-moderate" msgid "Let the member hear others in this community again" msgstr "Let the member hear others in this community again" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Lets {productName} read your keyboard directly. Your system already allows this." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Lets custom CSS reveal transparent window regions after restarting the desktop app." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Link styling" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Link unavailable" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Look and feel" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Message layout" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "message link" @@ -21332,7 +21416,7 @@ msgstr "Mic" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Mic test" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Miscellaneous" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Missing client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "More options" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "More shortcut options" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "No {sendMessagesPermission} permission" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "No access" @@ -22984,7 +23068,7 @@ msgstr "No matches found" msgid "No mentions yet" msgstr "No mentions yet" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "No microphone found" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "No settings found" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "No shortcut set" @@ -23358,6 +23442,11 @@ msgstr "Not a whole number" msgid "Not accepted" msgstr "Not accepted" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Not assigned" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Open {host} in your browser." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Open {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Open {labelText} in settings" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "page link" @@ -25611,11 +25700,11 @@ msgstr "Presence" msgid "Press {shortcut} to react" msgstr "Press {shortcut} to react" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Press a key, mouse button, or gamepad button to bind. Escape cancels." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Press a shortcut…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Push to talk key" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Push-to-talk" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Push-to-talk (limited)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." @@ -26293,21 +26397,51 @@ msgstr "Push-to-talk is ignored in direct input. Switch to focused voice or cust msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Push-to-talk is on. Hold {pushToTalkHint} to speak." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Push-to-talk is on. Hold your push-to-talk key to speak." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Push-to-talk only works while the browser tab is focused." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Raw embed JSON" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Re-enable built-in shortcut" @@ -26565,13 +26699,6 @@ msgstr "Recent mentions" msgid "Recent searches" msgstr "Recent searches" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Recheck" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Recommended" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Recommended. Cleans up your mic for clear speech." @@ -26600,6 +26727,11 @@ msgstr "Reconnect" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Reconnecting to your desktop…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Reconnecting…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Record shortcut" @@ -26851,7 +26983,7 @@ msgstr "Relative" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Release delay" @@ -27739,13 +27871,13 @@ msgstr "Resent" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Reset" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Reset \"{label}\" to built-in" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Reset to actual values" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Reset to built-in" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Select the account you want to sign in with on your new device." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Select voice processing mode" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Sends raw mic audio with no processing." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Sends your audio untouched. Best if you're using external audio software." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Set by the {preset} preset. Picking a value here switches to Custom." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Set custom shortcut" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Set custom shortcut for \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "Set the voice region to {region}" msgid "Set the voice region to automatic" msgstr "Set the voice region to automatic" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Set up" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Settings" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "settings link" @@ -30330,8 +30467,8 @@ msgstr "Shortcodes" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Switch to comfy layout" msgid "Switch to dense layout" msgstr "Switch to dense layout" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Switch to direct input" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "System-managed install" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "System-wide shortcuts" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "System-wide shortcuts could not be set up." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "System-wide: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "System-wide: Not assigned" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "The page you're looking for doesn't exist or has been moved." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "The provided redirect_uri is not registered for this application." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "The provided redirect_uri must use HTTP or HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "This animation is too long or too large to crop. Try a shorter or smaller file." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "This application" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "This attachment has expired" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "This bot is not public and cannot be added by other users." @@ -34081,6 +34233,16 @@ msgstr "This conversation is no longer available." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "This is your last passkey and passkeys are your second factor. Deleting msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "This link channel is marked for mature content and may open material that may be inappropriate for some users." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "This link is not available for your account." @@ -34778,6 +34945,21 @@ msgstr "Title bar" msgid "Title bar platform" msgstr "Title bar platform" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "To change the keys, go to Settings > Apps > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "To change the keys, open your desktop's keyboard shortcut settings." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Unknown" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Unknown application, expired session, or invalid authorization request." @@ -36410,6 +36592,11 @@ msgstr "Up to 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "Up to 720p at 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Update {productName} to use system-wide shortcuts on Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Use dark theme across the application" msgid "Use default" msgstr "Use default" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Use direct input device access" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Voice participants" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Voice processing" @@ -38264,7 +38456,7 @@ msgstr "Voice undeafen" msgid "Voice unmute" msgstr "Voice unmute" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Voice uses your system output device in this browser. Change it in your system settings." @@ -38278,7 +38470,7 @@ msgstr "Void" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Waiting for slowmode in one or more selected channels to expire." msgid "Waiting for you to..." msgstr "Waiting for you to..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Waiting for your desktop…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Which conversations" msgid "Which rule was broken?" msgstr "Which rule was broken?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." @@ -39094,6 +39291,11 @@ msgstr "Without this permission, push-to-talk is required." msgid "Wordmark" msgstr "Wordmark" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Works while {productName} is focused" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll randomly when your {PREMIUM_PRODUCT_NAME} expires, unless it renews." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "You do not have access to the channel where this message was sent." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Your {premiumProductName} subscription has expired. You have lost all {premiumProductName} perks. Reactivate your subscription to regain access." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Your {productName} session expired. Sign in again to continue authorizing this application." @@ -40144,6 +40346,16 @@ msgstr "Your community needs at least {minMemberCount, plural, one {# member} ot msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Your desktop didn't set up system-wide shortcuts." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Your desktop runs these shortcuts even while {productName} is not focused." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/es-419/messages.po b/fluxer_app/src/features/i18n/locales/es-419/messages.po index 36d1802ca..1468bf575 100644 --- a/fluxer_app/src/features/i18n/locales/es-419/messages.po +++ b/fluxer_app/src/features/i18n/locales/es-419/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# canal lo sigue} other {# canales lo siguen}} en e msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# comunidad encontrada} other {# comunidades encontradas}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# comunidad no está disponible temporalmente debido a una falla en el condensador de flujo.} other {# comunidades no están disponibles temporalmente debido a una falla en el condensador de flujo.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} evita enviar notificaciones push a tus dispositivos móvil msgid "{productName} Bug Hunter" msgstr "Cazador de bugs de {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} puede pedirle a tu escritorio que ejecute sus atajos cuando {productName} no tiene el foco. Tu escritorio te pedirá que confirmes y te dejará elegir las teclas." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} ya no puede leer tu teclado, así que ahora escucha los atajos a través de X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} ya no puede leer tu teclado, así que se usan los atajos de tu escritorio." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} ya no puede leer tu teclado. Los atajos solo funcionan mientras {productName} tiene el foco." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} puede registrar sus atajos en Hyprland para que funcionen cuando {productName} no tiene el foco. Luego asigna las teclas en tu configuración de Hyprland." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} necesita acceso a la cámara para mostrar tus dispositivos." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} necesita acceso al micrófono para mostrar tus dispositivos." @@ -1837,6 +1862,11 @@ msgstr "{productName} ahora está en {host}. Instálalo desde allí." msgid "{productName} Partner" msgstr "Socio de {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} registró sus atajos en Hyprland. Asígnalos en tu configuración de Hyprland para usarlos cuando {productName} no tenga el foco." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Un perfil que destaque" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Se requiere redirect_uri cuando \"bot\" no es el único ámbito." @@ -2847,7 +2877,7 @@ msgstr "Solo en todos los MD abiertos" msgid "Across every community you're in" msgstr "En todas las comunidades en las que estás" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Acción" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Compartir actividad" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Umbral de actividad" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Agrega una capa adicional de seguridad a tu cuenta" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Agregar otro atajo personalizado" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Dirección" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Ajusta cada opción tú mismo: supresión de ruido, cancelación de eco y ganancia." @@ -3696,11 +3726,11 @@ msgstr "Permitir {everyoneMention} y {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Permite que {PRODUCT_NAME} acceda a tu cámara en {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Permite que {PRODUCT_NAME} acceda a tu micrófono en {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Permite que {PRODUCT_NAME} acceda a tu micrófono. Revisa la barra de direcciones o la configuración de tu navegador." @@ -3803,7 +3833,7 @@ msgstr "Permitir que los miembros sin el permiso {readMessageHistoryPermissionLa msgid "Allow mentions for this role" msgstr "Permitir menciones para este rol" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Permitir micrófono" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Falló la autorización" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Falló la autorización. Intenta de nuevo." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Detectar idioma automáticamente" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Establecer umbral de actividad automáticamente" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Permisos del bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Los permisos del bot no pueden ser negativos." @@ -6869,6 +6899,11 @@ msgstr "Cambiar apodo en el grupo" msgid "Change icon" msgstr "Cambiar ícono" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Cambiar en la configuración del sistema" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Acceso al canal" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Acceso al canal denegado" @@ -7294,7 +7329,7 @@ msgstr "ID del canal copiado al portapapeles" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "enlace al canal" @@ -7462,6 +7497,11 @@ msgstr "entrada de chat" msgid "Chat shortcuts" msgstr "Atajos de chat" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Volver a comprobar" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Elige una ventana en el selector de tu sistema" msgid "Choose an account" msgstr "Elige una cuenta" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Elige una acción." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Elige un fondo" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Elige guild_id o channel_id, no ambos." @@ -7990,7 +8030,7 @@ msgstr "Mejora tu micrófono para que tu voz se escuche con claridad." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Haz clic para abrir el selector de color y elegir uno nuevo." msgid "Click to react" msgstr "Haz clic para reaccionar" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Haz clic para grabar" @@ -9110,7 +9150,7 @@ msgstr "Confirmación" msgid "Confirmation or double-click for community voice joins" msgstr "Confirmación o doble clic para unirse a canales de voz de la comunidad" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Conflicto con {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Conecta una cámara o un dispositivo de captura virtual y vuelve a inten msgid "Connect a display, then try again." msgstr "Conecta una pantalla y vuelve a intentarlo." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Conecta un micrófono e inténtalo de nuevo." @@ -9700,6 +9740,21 @@ msgstr "Copiar enlace de invitación" msgid "Copy invite URL" msgstr "Copiar enlace de invitación" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Copiar líneas" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Copiar líneas para hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Copiar líneas para hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Personalizado" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Selección personalizada" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Acción de atajo personalizada" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Opciones de atajo personalizadas" @@ -11466,7 +11521,7 @@ msgstr "Atajos predeterminados" msgid "Default translator" msgstr "Traductor predeterminado" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Predeterminado:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Eliminar contenido multimedia guardado" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Eliminar atajo" @@ -12102,13 +12157,13 @@ msgstr "Estado de desarrollador" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Herramientas de desarrollador" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Las herramientas de desarrollador están disponibles desde la insignia de personal en el encabezado del canal." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Desactivar beneficios premium del backend" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Desactivar atajo integrado" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "¿Desactivar las ventanas flotantes de imagen en imagen al compartir pantalla?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Desactivar atajo" @@ -12888,7 +12943,7 @@ msgstr "Donar en su lugar" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Botón de descarga" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Descargar la aplicación de escritorio" @@ -13204,6 +13259,11 @@ msgstr "Neerlandés" msgid "e.g., YubiKey, iPhone, work computer" msgstr "ej., YubiKey, iPhone, computadora del trabajo" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Cada tecla puede ejecutar solo un atajo de sistema." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Editar enlace de invitación" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Editar atajo de teclado" @@ -13425,7 +13485,7 @@ msgstr "Formulario para editar el contenido multimedia guardado" msgid "Edit search engine" msgstr "Editar motor de búsqueda" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Editar atajo" @@ -13753,20 +13813,16 @@ msgstr "Edición sin texto" msgid "Empty favorites" msgstr "Favoritos vacíos" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Habilitar" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Habilitar el permiso de {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Activar la aceleración de hardware y reiniciar" msgid "Enable hardware acceleration?" msgstr "¿Habilitar la aceleración de hardware?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Habilita el acceso de entrada para usar atajos de todo el sistema en Wayland. Después de cambiar el acceso de entrada, cierra y reinicia {productName} por completo para que los atajos puedan usarlo." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Habilitar Mayús para expandir" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Habilitar atajo" @@ -14357,7 +14408,7 @@ msgstr "Audio de todo el sistema" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Sonido de entrada" @@ -14467,7 +14518,7 @@ msgstr "Noche" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Ajusta el volumen de tu micrófono para que no suenes demasiado bajo." @@ -15682,6 +15733,26 @@ msgstr "Solo para ojos británicos..." msgid "For me" msgstr "Para mí" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Para \"Pulsar para silenciar\", usa una sola tecla sin Ctrl, Alt ni Shift. Con una combinación, suelta primero la tecla principal o el micrófono podría quedarse silenciado." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Para \"Pulsar para hablar\", usa una sola tecla sin Ctrl, Alt ni Shift. Con una combinación, suelta primero la tecla principal o el micrófono podría quedarse encendido." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Para este atajo, usa una sola tecla sin Ctrl, Alt ni Shift. Con una combinación, suelta primero la tecla principal o el atajo podría quedarse activo." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Para \"Prioridad de actividad de voz\", usa una sola tecla sin Ctrl, Alt ni Shift. Con una combinación, suelta primero la tecla principal o la prioridad podría quedarse activada." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Para tu seguridad, confirma que eres tú para continuar." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Perfil global" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Atajo global" @@ -17434,6 +17505,11 @@ msgstr "Mantén presionada la tecla Shift y presiona X para seleccionar aplicaci msgid "Hold to record. Drag up to lock, or release to send." msgstr "Mantén presionado para grabar. Desliza hacia arriba para bloquear o suelta para enviar." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Mantener presionadas las teclas Shift, Ctrl o Alt bloquea este atajo. Agrega alternativas en la configuración del sistema." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "¡Hurra! Las notificaciones están activadas" msgid "Hyperlink" msgstr "Hipervínculo" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland no tiene un diálogo de atajos. Agrega las líneas de tu versión de Hyprland a tu configuración de Hyprland y reemplaza KEY por la tecla que quieras." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland no tiene un diálogo de atajos. Asigna las teclas en tu configuración de Hyprland." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (antes de Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 y posteriores)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Acepto la <0>Política de privacidad" @@ -17576,7 +17672,7 @@ msgstr "Cambié de opinión" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Entiendo" @@ -18000,7 +18096,7 @@ msgstr "En" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "En el modo \"Sistema operativo\", los diccionarios que se muestran arriba son orientativos. {PRODUCT_NAME} pasará estas etiquetas de idioma al corrector ortográfico de tu sistema operativo cuando sea compatible. Las etiquetas que no reconozca serán ignoradas." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "En un navegador, la función pulsar para hablar solo funciona mientras la pestaña de {PRODUCT_NAME} esté activa. Instala la aplicación de escritorio para usar pulsar para hablar en todo el sistema." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Entrada" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "No se pudo habilitar el acceso de entrada automáticamente. Cámbialo manualmente y luego cierra {productName} por completo y vuelve a abrirlo para que los atajos puedan usarlo." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "No se pudo habilitar el acceso de entrada. Intenta de nuevo desde la configuración de {settingsMenuName} y luego cierra {productName} por completo y vuelve a abrirlo." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Se requiere acceso de entrada para los atajos de teclado y mouse de todo el sistema en Wayland. Después de cambiar el acceso de entrada, cierra y reinicia {productName} por completo para que los atajos puedan usarlo." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Se cambió el acceso de entrada. Cierra {productName} por completo y vuelve a abrirlo para que los atajos de todo el sistema puedan usarlo." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Entrada y salida" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Solicitud de autorización no válida" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Permisos de bot no válidos." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Código no válido o caducado. Intenta de nuevo." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Valor de redirect_uri no válido" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "El enlace de invitación está oculto mientras compartes:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Enlace de invitación oculto mientras compartes pantalla" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Ir a {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Ir a {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Ir a la llamada actual" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Ir al canal enlazado" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Ir al mensaje enlazado" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Ir al mensaje de {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Ir al mensaje en {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Guarda estas credenciales en un lugar seguro. Si las regeneras, las integraciones existentes dejarán de funcionar." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Continúa transmitiendo brevemente después de soltar el botón." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Teclado" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "No se pudo iniciar el acceso al teclado. Los atajos solo funcionan mientras {productName} tiene el foco." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Permitir que la comunidad se automodere" msgid "Let the member hear others in this community again" msgstr "Se permitió que el miembro volviera a escuchar a otros en esta comunidad" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Permite que {productName} lea tu teclado directamente. Tu sistema ya lo permite." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Permite que el CSS personalizado revele regiones transparentes de la ventana después de reiniciar la aplicación de escritorio." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Estilo de enlaces" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Enlace no disponible" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Apariencia y estilo" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Parece que estás usando {0}. Si agregas el procesamiento de {PRODUCT_NAME} encima, puede causar bombeo o distorsión. La entrada directa pasa tu audio sin modificaciones." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Diseño de mensajes" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "enlace del mensaje" @@ -21332,7 +21416,7 @@ msgstr "Micrófono" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Prueba de micrófono" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Varios" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Falta client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Más opciones" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Más opciones de atajos" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Sin permiso de {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Sin acceso" @@ -22984,7 +23068,7 @@ msgstr "No se encontraron resultados" msgid "No mentions yet" msgstr "Aún no hay menciones" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "No se encontró ningún micrófono" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "No se encontraron configuraciones" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Ningún atajo configurado" @@ -23358,6 +23442,11 @@ msgstr "No es un número entero" msgid "Not accepted" msgstr "No aceptada" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Sin asignar" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Abre {host} en tu navegador." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Abrir {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Abrir {labelText} en la configuración" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Av Pág" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "enlace de página" @@ -25611,11 +25700,11 @@ msgstr "Presencia" msgid "Press {shortcut} to react" msgstr "Presiona {shortcut} para reaccionar" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Presiona una tecla, un botón del mouse o un botón del gamepad para asignar. Esc para cancelar." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Presiona un atajo…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Tecla para pulsar y hablar" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Pulsar para hablar" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Pulsar para hablar (limitado)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "\"Pulsar para hablar\" aún no tiene una tecla en todo el sistema. Para asignar una, ve a Configuración > Aplicaciones > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "\"Pulsar para hablar\" aún no tiene una tecla en todo el sistema. Para asignar una, ve a Preferencias del sistema > Teclado > Accesos rápidos > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "\"Pulsar para hablar\" aún no tiene una tecla en todo el sistema. Para asignar una, abre la configuración de atajos de teclado de tu escritorio." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "La función pulsar para hablar se ignora en la entrada directa. Cambia a voz enfocada o personalizada para usarla." @@ -26293,21 +26397,51 @@ msgstr "La función pulsar para hablar se ignora en la entrada directa. Cambia a msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Pulsar para hablar está activado. Mantén presionado {pushToTalkHint} para hablar." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "\"Pulsar para hablar\" está activado. Mantén presionada tu tecla para hablar." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "\"Pulsar para hablar\" solo funciona mientras {productName} tiene el foco porque tu escritorio no configuró los atajos de todo el sistema." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Pulsar para hablar solo funciona mientras la ventana de {productName} está activa hasta que se active {permissionName}. Después de cambiar el permiso, cierra por completo y reinicia {productName} para que los atajos de todo el sistema puedan usarlo." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "\"Pulsar para hablar\" solo funciona mientras {productName} tiene el foco hasta que configures los atajos de todo el sistema." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Pulsar para hablar solo funciona cuando la pestaña del navegador está activa." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "\"Pulsar para hablar\" usará la tecla que configures en {productName}. Configura una primero o tu micrófono usará la actividad de voz." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "La función \"pulsar para hablar\" no activará el micrófono mientras tengas el sonido desactivado. Activa el sonido para poder hablar." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "\"Pulsar para hablar\" solo funciona fuera de {productName} después de asignarlo en tu configuración de Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "\"Pulsar para hablar\" solo funciona fuera de {productName} después de asignarlo en tu configuración de Hyprland. Agrega la línea de tu versión de Hyprland y reemplaza KEY por la tecla que quieras." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "JSON sin procesar del contenido incrustado" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Volver a activar atajo integrado" @@ -26565,13 +26699,6 @@ msgstr "Menciones recientes" msgid "Recent searches" msgstr "Búsquedas recientes" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Volver a verificar" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Recomendado" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Recomendado. Reduce el ruido del micrófono para que la voz se escuche con claridad." @@ -26600,6 +26727,11 @@ msgstr "Reconectar" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Reconexión exitosa. Elige una pantalla de nuevo si quieres reanudar tu transmisión anterior." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Reconectando con tu escritorio…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Reconectando…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Grabar atajo" @@ -26851,7 +26983,7 @@ msgstr "Relativo" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Retraso de liberación" @@ -27739,13 +27871,13 @@ msgstr "Reenviado" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Restablecer" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Restablecer \"{label}\" a predeterminado" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Restablecer a valores reales" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Restablecer a los valores predeterminados" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Selecciona la cuenta con la que quieres iniciar sesión en tu nuevo dispositivo." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Seleccionar modo de procesamiento de voz" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Envía el audio puro del micrófono sin procesar." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Envía tu audio sin modificar. Ideal si usas software de audio externo." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Establecido por el preset {preset}. Al elegir un valor aquí, se cambiará a Personalizado." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Establecer atajo personalizado" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Establecer atajo personalizado para \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "Se estableció la región de voz en {region}" msgid "Set the voice region to automatic" msgstr "Se estableció la región de voz en automática" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Configurar" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Configuración" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "enlace de configuración" @@ -30330,8 +30467,8 @@ msgstr "Códigos cortos" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Cambiar a diseño cómodo" msgid "Switch to dense layout" msgstr "Cambiar a diseño compacto" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Cambiar a entrada directa" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Instalación gestionada por el sistema" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Atajos de todo el sistema" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "No se pudieron configurar los atajos de todo el sistema." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Sistema: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Sistema: sin asignar" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "La página que buscas no existe o se movió." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "La redirect_uri proporcionada no está registrada para esta aplicación." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "La redirect_uri proporcionada debe usar HTTP o HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Esta animación es demasiado larga o grande para recortarla. Intenta con un archivo más corto o pequeño." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Esta aplicación" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Este archivo adjunto ha caducado" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Este bot no es público y no puede ser añadido por otros usuarios." @@ -34081,6 +34233,16 @@ msgstr "Esta conversación ya no está disponible." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Esta versión de escritorio no puede instalar actualizaciones dentro de {productName}. Descarga el instalador de escritorio más reciente en su lugar." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Este escritorio no ofrece atajos de todo el sistema. \"Pulsar para hablar\" solo funciona mientras {productName} tiene el foco." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Este escritorio no ofrece atajos de todo el sistema. Los atajos siguen funcionando mientras {productName} tiene el foco." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Esta es tu última llave de acceso y las llaves de acceso son tu segundo msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Esta es tu última llave de acceso. Una vez que la elimines, no podrás usar una llave de acceso como segundo factor." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Esta tecla funciona mientras {productName} tiene el foco. Tu escritorio asigna la tecla de todo el sistema en {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Este canal de enlaces está marcado para contenido para adultos y puede mostrar material que no es apropiado para algunos usuarios." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Este enlace no está disponible para tu cuenta." @@ -34778,6 +34945,21 @@ msgstr "Barra de título" msgid "Title bar platform" msgstr "Plataforma de la barra de título" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Para cambiar las teclas, ve a Configuración > Aplicaciones > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Para cambiar las teclas, ve a Preferencias del sistema > Teclado > Accesos rápidos > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Para cambiar las teclas, abre la configuración de atajos de teclado de tu escritorio." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Para crear un webhook, abre <0>{channelWebhooksSettingsPath}. Todavía puedes editar y organizar todos los webhooks existentes aquí." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Desconocido" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Aplicación desconocida, sesión caducada o solicitud de autorización no válida." @@ -36410,6 +36592,11 @@ msgstr "Hasta 4K/60 fps" msgid "Up to 720p at 30 FPS" msgstr "Hasta 720p a 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Actualiza {productName} para usar atajos de todo el sistema en Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Usar tema oscuro en toda la aplicación" msgid "Use default" msgstr "Usar predeterminado" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Usar acceso directo a los dispositivos de entrada" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Participantes en la llamada" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Procesamiento de voz" @@ -38264,7 +38456,7 @@ msgstr "Activar sonido" msgid "Voice unmute" msgstr "Activar micrófono" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "La voz usa el dispositivo de salida de tu sistema en este navegador. Cámbialo en la configuración de tu sistema." @@ -38278,7 +38470,7 @@ msgstr "Anulada" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Esperando que el modo lento en uno o más canales seleccionados expire." msgid "Waiting for you to..." msgstr "Esperando que..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Esperando a tu escritorio…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Qué conversaciones" msgid "Which rule was broken?" msgstr "¿Qué regla se rompió?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Mientras la función pulsar para hablar esté activada, tu atajo controlará el micrófono, por lo que el botón de silenciar estará deshabilitado." @@ -39094,6 +39291,11 @@ msgstr "Sin este permiso, se requiere pulsar para hablar." msgid "Wordmark" msgstr "Logotipo tipográfico" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Funciona mientras {productName} tiene el foco" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Cambiaste tu etiqueta mientras usabas {PREMIUM_PRODUCT_NAME}. Se volverá a generar aleatoriamente cuando venza tu {PREMIUM_PRODUCT_NAME}, a menos que se renueve." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "No tienes acceso al canal donde se envió este mensaje." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Tu suscripción a {premiumProductName} ha caducado. Has perdido todos los beneficios de {premiumProductName}. Reactiva tu suscripción para recuperar el acceso." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Tu sesión de {productName} caducó. Inicia sesión de nuevo para seguir autorizando esta aplicación." @@ -40144,6 +40346,16 @@ msgstr "Tu comunidad necesita al menos {minMemberCount, plural, one {# miembro} msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Se ha comprobado tu tarjeta de crédito. Si la tarjeta es válida, el contenido para adultos se desbloqueará en breve. Puedes cerrar esta pestaña y volver a la aplicación." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Tu escritorio no configuró los atajos de todo el sistema." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Tu escritorio ejecuta estos atajos incluso cuando {productName} no tiene el foco." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/es-ES/messages.po b/fluxer_app/src/features/i18n/locales/es-ES/messages.po index 71c608ecf..223722b98 100644 --- a/fluxer_app/src/features/i18n/locales/es-ES/messages.po +++ b/fluxer_app/src/features/i18n/locales/es-ES/messages.po @@ -993,7 +993,7 @@ msgstr "Sigue este canal {count, plural, one {# canal} other {# canales}}. Conve msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# comunidad encontrada} other {# comunidades encontradas}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# comunidad no está disponible temporalmente debido a un fallo en el condensador de flujo.} other {# comunidades no están disponibles temporalmente debido a un fallo en el condensador de flujo.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} evita enviar notificaciones push a tus dispositivos móvil msgid "{productName} Bug Hunter" msgstr "Cazador de errores de {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} puede pedirle a tu escritorio que ejecute sus atajos cuando {productName} no tiene el foco. Tu escritorio te pedirá confirmación y te dejará elegir las teclas." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} ya no puede leer tu teclado, así que ahora escucha los atajos a través de X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} ya no puede leer tu teclado, así que se usan los atajos de tu escritorio." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} ya no puede leer tu teclado. Los atajos solo funcionan mientras {productName} tiene el foco." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} puede registrar sus atajos en Hyprland para que funcionen cuando {productName} no tiene el foco. Luego asigna las teclas en tu configuración de Hyprland." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} necesita acceso a la cámara para mostrar tus dispositivos." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} necesita acceso al micrófono para mostrar tus dispositivos." @@ -1837,6 +1862,11 @@ msgstr "{productName} ahora está en {host}. Instálalo desde allí." msgid "{productName} Partner" msgstr "Socio de {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} ha registrado sus atajos en Hyprland. Asígnalos en tu configuración de Hyprland para usarlos cuando {productName} no tenga el foco." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Un perfil que destaque" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Se requiere redirect_uri cuando \"bot\" no es el único ámbito." @@ -2847,7 +2877,7 @@ msgstr "Solo en todos los MD abiertos" msgid "Across every community you're in" msgstr "En todas las comunidades en las que estás" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Acción" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Compartir actividad" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Umbral de actividad" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Añade una capa extra de seguridad a tu cuenta" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Añadir otro atajo personalizado" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Dirección" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Ajusta cada opción tú mismo: supresión de ruido, cancelación de eco y ganancia." @@ -3696,11 +3726,11 @@ msgstr "Permitir {everyoneMention} y {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Permite que {PRODUCT_NAME} acceda a tu cámara en {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Permite que {PRODUCT_NAME} acceda a tu micrófono en {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Permite que {PRODUCT_NAME} acceda a tu micrófono. Comprueba la barra de direcciones o los ajustes de tu navegador." @@ -3803,7 +3833,7 @@ msgstr "Permitir que los miembros sin el permiso {readMessageHistoryPermissionLa msgid "Allow mentions for this role" msgstr "Permitir menciones para este rol" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Permitir micrófono" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Error de autorización" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Error de autorización. Inténtalo de nuevo." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Detectar idioma automáticamente" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Establecer umbral de actividad automáticamente" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Permisos del bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Los permisos del bot no pueden ser negativos." @@ -6869,6 +6899,11 @@ msgstr "Cambiar apodo en el grupo" msgid "Change icon" msgstr "Cambiar icono" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Cambiar en la configuración del sistema" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Acceso al canal" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Acceso al canal denegado" @@ -7294,7 +7329,7 @@ msgstr "ID del canal copiado al portapapeles" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "enlace del canal" @@ -7462,6 +7497,11 @@ msgstr "entrada de chat" msgid "Chat shortcuts" msgstr "Atajos de chat" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Volver a comprobar" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Elige una ventana en el selector de tu sistema" msgid "Choose an account" msgstr "Elige una cuenta" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Elige una acción." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Elige un fondo" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Elige guild_id o channel_id, no ambos." @@ -7990,7 +8030,7 @@ msgstr "Mejora el micrófono para que la voz se escuche con claridad." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Haz clic para abrir el selector de color y elegir uno nuevo." msgid "Click to react" msgstr "Haz clic para reaccionar" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Haz clic para grabar" @@ -9110,7 +9150,7 @@ msgstr "Confirmación" msgid "Confirmation or double-click for community voice joins" msgstr "Confirmación o doble clic para unirse a los canales de voz de la comunidad." -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Entra en conflicto con {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Conecta una cámara o un dispositivo de captura virtual y vuelve a inten msgid "Connect a display, then try again." msgstr "Conecta una pantalla y vuelve a intentarlo." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Conecta un micrófono y vuelve a intentarlo." @@ -9700,6 +9740,21 @@ msgstr "Copiar enlace de invitación" msgid "Copy invite URL" msgstr "Copiar enlace de invitación" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Copiar líneas" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Copiar líneas para hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Copiar líneas para hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Fuente personalizada" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Selección personalizada" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Acción de atajo personalizada" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Opciones de atajo personalizadas" @@ -11466,7 +11521,7 @@ msgstr "Atajos predeterminados" msgid "Default translator" msgstr "Traductor predeterminado" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Predeterminado:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Eliminar contenido multimedia guardado" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Eliminar atajo" @@ -12102,13 +12157,13 @@ msgstr "Estado de desarrollador" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Herramientas de desarrollador" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Las herramientas de desarrollador están disponibles desde la insignia de personal en el encabezado del canal." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Desactivar ventajas prémium del backend" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Desactivar atajo integrado" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "¿Desactivar las ventanas flotantes de imagen en imagen al compartir pantalla?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Desactivar atajo" @@ -12888,7 +12943,7 @@ msgstr "Donar en su lugar" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Botón de descarga" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Descargar aplicación de escritorio" @@ -13204,6 +13259,11 @@ msgstr "Neerlandés" msgid "e.g., YubiKey, iPhone, work computer" msgstr "p. ej., YubiKey, iPhone, ordenador del trabajo" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Cada tecla solo puede ejecutar un atajo del sistema." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Editar enlace de invitación" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Editar atajo de teclado" @@ -13425,7 +13485,7 @@ msgstr "Formulario para editar el contenido multimedia guardado" msgid "Edit search engine" msgstr "Editar motor de búsqueda" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Editar atajo" @@ -13753,20 +13813,16 @@ msgstr "Edición sin texto" msgid "Empty favorites" msgstr "Favoritos vacíos" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Habilitar" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Habilitar el permiso {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Habilitar aceleración de hardware y reiniciar" msgid "Enable hardware acceleration?" msgstr "¿Habilitar la aceleración de hardware?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Habilita el acceso de entrada para usar atajos de teclado en todo el sistema en Wayland. Después de cambiar el acceso de entrada, cierra y reinicia {productName} por completo para que los atajos puedan usarlo." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Habilitar Mayús para expandir" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Activar atajo" @@ -14357,7 +14408,7 @@ msgstr "Audio de todo el sistema" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Sonido de entrada" @@ -14467,7 +14518,7 @@ msgstr "Tarde" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Iguala el volumen de tu micrófono para que no suenes demasiado bajo." @@ -15682,6 +15733,26 @@ msgstr "Solo para ojos británicos..." msgid "For me" msgstr "Para mí" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Para \"Pulsar para silenciar\", usa una sola tecla sin Ctrl, Alt ni Mayús. Con una combinación, suelta primero la tecla principal o el micrófono podría quedarse silenciado." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Para \"Pulsar para hablar\", usa una sola tecla sin Ctrl, Alt ni Mayús. Con una combinación, suelta primero la tecla principal o el micrófono podría quedarse activado." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Para este atajo, usa una sola tecla sin Ctrl, Alt ni Mayús. Con una combinación, suelta primero la tecla principal o el atajo podría quedarse activo." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Para \"Prioridad de actividad de voz\", usa una sola tecla sin Ctrl, Alt ni Mayús. Con una combinación, suelta primero la tecla principal o la prioridad podría quedarse activada." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Para tu seguridad, confirma que eres tú para continuar." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Perfil global" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Atajo global" @@ -17434,6 +17505,11 @@ msgstr "Mantén pulsada Mayús y pulsa X para seleccionar aplicaciones. Pulsa {s msgid "Hold to record. Drag up to lock, or release to send." msgstr "Mantén pulsado para grabar. Desliza hacia arriba para bloquear o suelta para enviar." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Mantener pulsadas las teclas Mayús, Ctrl o Alt bloquea este atajo. Añade alternativas en los ajustes del sistema." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "¡Hurra! Las notificaciones están activadas" msgid "Hyperlink" msgstr "Hipervínculo" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland no tiene un diálogo de atajos. Añade las líneas de tu versión de Hyprland a tu configuración de Hyprland y sustituye KEY por la tecla que quieras." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland no tiene un diálogo de atajos. Asigna las teclas en tu configuración de Hyprland." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (antes de Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 y posteriores)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Acepto la <0>Política de privacidad" @@ -17576,7 +17672,7 @@ msgstr "He cambiado de opinión" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Entendido" @@ -18000,7 +18096,7 @@ msgstr "En" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "En el modo \"Sistema operativo\", los diccionarios indicados anteriormente son orientativos. {PRODUCT_NAME} pasará estas etiquetas de idioma al corrector ortográfico de tu sistema operativo cuando sea compatible. Las etiquetas que no reconozca se ignorarán." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "En un navegador, la función pulsar para hablar solo funciona si la pestaña de {PRODUCT_NAME} está activa. Instala la aplicación de escritorio para usar la función pulsar para hablar en todo el sistema." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Entrada" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "No se pudo habilitar el acceso de entrada automáticamente. Cámbialo manualmente y luego cierra {productName} por completo y vuelve a abrirlo para que los atajos puedan usarlo." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "No se pudo habilitar el acceso de entrada. Inténtalo de nuevo desde los ajustes de {settingsMenuName} y luego cierra {productName} por completo y vuelve a abrirlo." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Se requiere acceso de entrada para los atajos de teclado y ratón de todo el sistema en Wayland. Después de cambiar el acceso de entrada, cierra y reinicia {productName} por completo para que los atajos puedan usarlo." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Se ha cambiado el acceso de entrada. Cierra {productName} por completo y vuelve a abrirlo para que los atajos de todo el sistema puedan usarlo." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Entrada y salida" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Solicitud de autorización no válida" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Permisos de bot no válidos." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Código no válido o caducado. Inténtalo de nuevo." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Valor de redirect_uri no válido" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "El enlace de invitación está oculto mientras compartes:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Enlace de invitación oculto mientras compartes pantalla" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Ir a {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Ir a {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Ir a la llamada actual" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Ir al canal enlazado" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Ir al mensaje enlazado" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Ir al mensaje de {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Ir al mensaje en {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Guarda estas credenciales en un lugar seguro. Si las regeneras, las integraciones existentes dejarán de funcionar." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Sigue transmitiendo brevemente después de soltar el botón." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Teclado" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "No se pudo iniciar el acceso al teclado. Los atajos solo funcionan mientras {productName} tiene el foco." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Permitir que la comunidad se automodere" msgid "Let the member hear others in this community again" msgstr "Se ha permitido que el miembro vuelva a escuchar a los demás en esta comunidad" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Permite que {productName} lea tu teclado directamente. Tu sistema ya lo permite." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Permite que el CSS personalizado revele regiones transparentes de la ventana después de reiniciar la aplicación de escritorio." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Estilo de los enlaces" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Enlace no disponible" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Apariencia y estilo" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Parece que estás usando {0}. Añadir el procesamiento de {PRODUCT_NAME} puede causar bombeo o distorsión. La entrada directa transmite el audio sin modificar." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Diseño de los mensajes" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "enlace del mensaje" @@ -21332,7 +21416,7 @@ msgstr "Micrófono" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Prueba de micrófono" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Varios" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Falta client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Más opciones" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Más opciones de atajos" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Sin permiso de {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Sin acceso" @@ -22984,7 +23068,7 @@ msgstr "No se encontraron coincidencias" msgid "No mentions yet" msgstr "Aún no hay menciones" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "No se ha encontrado ningún micrófono" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "No se encontraron ajustes" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Ningún atajo configurado" @@ -23358,6 +23442,11 @@ msgstr "No es un número entero" msgid "Not accepted" msgstr "No aceptado" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Sin asignar" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Abre {host} en tu navegador." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Abrir {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Abrir {labelText} en los ajustes" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Av Pág" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "enlace a la página" @@ -25611,11 +25700,11 @@ msgstr "Presencia" msgid "Press {shortcut} to react" msgstr "Pulsa {shortcut} para reaccionar" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Pulsa una tecla, un botón del ratón o un botón del mando para asignar. Esc para cancelar." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Pulsa un atajo…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Tecla para pulsar y hablar" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Pulsar para hablar" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Pulsar para hablar (limitado)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "\"Pulsar para hablar\" aún no tiene una tecla en todo el sistema. Para asignar una, ve a Configuración > Aplicaciones > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "\"Pulsar para hablar\" aún no tiene una tecla en todo el sistema. Para asignar una, ve a Preferencias del sistema > Teclado > Accesos rápidos > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "\"Pulsar para hablar\" aún no tiene una tecla en todo el sistema. Para asignar una, abre los ajustes de atajos de teclado de tu escritorio." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "La función \"Pulsar para hablar\" se ignora en la entrada directa. Cambia a voz enfocada o personalizada para usarla." @@ -26293,21 +26397,51 @@ msgstr "La función \"Pulsar para hablar\" se ignora en la entrada directa. Camb msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Pulsar para hablar está activado. Mantén pulsado {pushToTalkHint} para hablar." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "\"Pulsar para hablar\" está activado. Mantén pulsada tu tecla para hablar." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "\"Pulsar para hablar\" solo funciona mientras {productName} tiene el foco porque tu escritorio no ha configurado los atajos de todo el sistema." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Pulsar para hablar solo funciona mientras la ventana de {productName} está activa hasta que se active {permissionName}. Después de cambiar el permiso, cierra por completo y reinicia {productName} para que los atajos de todo el sistema puedan usarlo." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "\"Pulsar para hablar\" solo funciona mientras {productName} tiene el foco hasta que configures los atajos de todo el sistema." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Pulsar para hablar solo funciona si la pestaña del navegador está activa." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "\"Pulsar para hablar\" usará la tecla que configures en {productName}. Configura una primero o tu micrófono usará la actividad de voz." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "La función \"pulsar para hablar\" no activará el micrófono mientras tengas el sonido desactivado. Activa el sonido para poder hablar." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "\"Pulsar para hablar\" solo funciona fuera de {productName} después de asignarlo en tu configuración de Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "\"Pulsar para hablar\" solo funciona fuera de {productName} después de asignarlo en tu configuración de Hyprland. Añade la línea de tu versión de Hyprland y sustituye KEY por la tecla que quieras." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "JSON sin procesar del contenido incrustado" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Volver a activar atajo integrado" @@ -26565,13 +26699,6 @@ msgstr "Menciones recientes" msgid "Recent searches" msgstr "Búsquedas recientes" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Volver a comprobar" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Recomendado" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Recomendado. Reduce el ruido del micrófono para que la voz se escuche con claridad." @@ -26600,6 +26727,11 @@ msgstr "Reconectar" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Reconexión correcta. Elige una pantalla de nuevo si quieres reanudar tu transmisión anterior." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Reconectando con tu escritorio…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Reconectando…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Grabar atajo" @@ -26851,7 +26983,7 @@ msgstr "Relativo" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Retraso de la liberación" @@ -27739,13 +27871,13 @@ msgstr "Reenviado" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Restablecer" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Restablecer \"{label}\" a predeterminado" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Restablecer a los valores reales" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Restablecer a los valores predeterminados" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Selecciona la cuenta con la que quieres iniciar sesión en tu nuevo dispositivo." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Selecciona el modo de procesamiento de voz" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Envía el audio del micrófono sin procesar." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Envía tu audio sin modificar. Es la mejor opción si usas software de audio externo." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Establecido por el ajuste preestablecido {preset}. Seleccionar un valor aquí cambiará a Personalizado." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Establecer atajo personalizado" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Establecer atajo personalizado para \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "Se estableció la región de voz en {region}" msgid "Set the voice region to automatic" msgstr "Se estableció la región de voz en automático" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Configurar" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Ajustes" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "enlace de ajustes" @@ -30330,8 +30467,8 @@ msgstr "Códigos cortos" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Cambiar a diseño cómodo" msgid "Switch to dense layout" msgstr "Cambiar a diseño compacto" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Cambiar a entrada directa" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Instalación gestionada por el sistema" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Atajos de todo el sistema" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "No se han podido configurar los atajos de todo el sistema." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Sistema: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Sistema: sin asignar" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "La página que buscas no existe o se ha movido." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "La redirect_uri proporcionada no está registrada para esta aplicación." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "La redirect_uri proporcionada debe usar HTTP o HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Esta animación es demasiado larga o grande para recortarla. Prueba con un archivo más corto o pequeño." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Esta aplicación" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Este archivo adjunto ha caducado" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Este bot no es público y no puede ser añadido por otros usuarios." @@ -34081,6 +34233,16 @@ msgstr "Esta conversación ya no está disponible." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Esta versión de escritorio no puede instalar actualizaciones dentro de {productName}. Descarga el instalador de escritorio más reciente en su lugar." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Este escritorio no ofrece atajos de todo el sistema. \"Pulsar para hablar\" solo funciona mientras {productName} tiene el foco." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Este escritorio no ofrece atajos de todo el sistema. Los atajos siguen funcionando mientras {productName} tiene el foco." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Esta es tu última clave de acceso y las claves de acceso son tu segundo msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Esta es tu última clave de acceso. Una vez que la elimines, no podrás usar una clave de acceso como segundo factor." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Esta tecla funciona mientras {productName} tiene el foco. Tu escritorio asigna la tecla de todo el sistema en {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Este canal de enlaces está marcado para contenido para adultos y puede abrir material que podría ser inapropiado para algunos usuarios." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Este enlace no está disponible para tu cuenta." @@ -34778,6 +34945,21 @@ msgstr "Barra de título" msgid "Title bar platform" msgstr "Plataforma de la barra de título" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Para cambiar las teclas, ve a Configuración > Aplicaciones > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Para cambiar las teclas, ve a Preferencias del sistema > Teclado > Accesos rápidos > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Para cambiar las teclas, abre los ajustes de atajos de teclado de tu escritorio." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Para crear un webhook, abre <0>{channelWebhooksSettingsPath}. Puedes seguir editando y organizando todos los webhooks existentes aquí." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Desconocido" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Aplicación desconocida, sesión caducada o solicitud de autorización no válida." @@ -36410,6 +36592,11 @@ msgstr "Hasta 4K/60 fps" msgid "Up to 720p at 30 FPS" msgstr "Hasta 720p a 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Actualiza {productName} para usar atajos de todo el sistema en Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Usar tema oscuro en toda la aplicación" msgid "Use default" msgstr "Usar predeterminado" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Usar acceso directo a los dispositivos de entrada" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Participantes en el chat de voz" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Procesamiento de voz" @@ -38264,7 +38456,7 @@ msgstr "Activar sonido" msgid "Voice unmute" msgstr "Activar micrófono" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "La voz utiliza el dispositivo de salida de tu sistema en este navegador. Cámbialo en la configuración de tu sistema." @@ -38278,7 +38470,7 @@ msgstr "Anulada" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Esperando a que el modo lento caduque en uno o más canales seleccionado msgid "Waiting for you to..." msgstr "Esperando a que..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Esperando a tu escritorio…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Qué conversaciones" msgid "Which rule was broken?" msgstr "¿Qué regla se ha infringido?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Mientras la función pulsar para hablar esté activada, tu atajo controla el micrófono, por lo que el botón de silenciar está deshabilitado." @@ -39094,6 +39291,11 @@ msgstr "Sin este permiso, se requiere pulsar para hablar." msgid "Wordmark" msgstr "Logotipo tipográfico" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Funciona mientras {productName} tiene el foco" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Cambiaste tu etiqueta mientras usabas {PREMIUM_PRODUCT_NAME}. Se volverá a generar aleatoriamente cuando caduque tu {PREMIUM_PRODUCT_NAME}, a menos que se renueve." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "No tienes acceso al canal donde se envió este mensaje." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Tu suscripción a {premiumProductName} ha caducado. Has perdido todas las ventajas de {premiumProductName}. Reactiva tu suscripción para recuperar el acceso." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Tu sesión de {productName} ha caducado. Inicia sesión de nuevo para seguir autorizando esta aplicación." @@ -40144,6 +40346,16 @@ msgstr "Tu comunidad necesita al menos {minMemberCount, plural, one {# miembro} msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Se ha comprobado tu tarjeta de crédito. Si la tarjeta es válida, el contenido para adultos se desbloqueará en breve. Puedes cerrar esta pestaña y volver a la aplicación." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Tu escritorio no ha configurado los atajos de todo el sistema." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Tu escritorio ejecuta estos atajos incluso cuando {productName} no tiene el foco." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/fi/messages.po b/fluxer_app/src/features/i18n/locales/fi/messages.po index dd57f1796..abf03b630 100644 --- a/fluxer_app/src/features/i18n/locales/fi/messages.po +++ b/fluxer_app/src/features/i18n/locales/fi/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# kanava seuraa} other {# kanavaa seuraa}} tämän msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# yhteisö löytyi} other {# yhteisöä löytyi}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# yhteisö on tilapäisesti pois käytöstä toimintahäiriön vuoksi.} other {# yhteisöä on tilapäisesti pois käytöstä toimintahäiriön vuoksi.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} ei lähetä push-ilmoituksia mobiililaitteisiisi, kun olet msgid "{productName} Bug Hunter" msgstr "{productName}-buginmetsästäjä" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} voi pyytää työpöytääsi suorittamaan sen pikanäppäimiä, kun {productName} ei ole aktiivisena. Työpöytäsi pyytää sinua vahvistamaan ja antaa sinun valita näppäimet." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} ei voi enää lukea näppäimistöäsi, joten se kuuntelee pikanäppäimiä sen sijaan X11:n kautta." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} ei voi enää lukea näppäimistöäsi, joten sen sijaan käytetään työpöytäsi pikanäppäimiä." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} ei voi enää lukea näppäimistöäsi. Pikanäppäimet toimivat vain, kun {productName} on aktiivisena." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} voi rekisteröidä pikanäppäimensä Hyprlandiin, jotta ne toimivat, kun {productName} ei ole aktiivisena. Sido sitten näppäimet Hyprland-määrityksissäsi." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} tarvitsee kameran käyttöoikeuden, jotta se voi näyttää laitteesi." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} tarvitsee mikrofonin käyttöoikeuden, jotta se voi näyttää laitteesi." @@ -1837,6 +1862,11 @@ msgstr "{productName} löytyy nyt osoitteesta {host}. Asenna se sieltä." msgid "{productName} Partner" msgstr "{productName}-kumppani" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} rekisteröi pikanäppäimensä Hyprlandiin. Sido ne Hyprland-määrityksissäsi, jotta voit käyttää niitä, kun {productName} ei ole aktiivisena." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Profiili, joka erottuu joukosta" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri vaaditaan, kun bot-käyttöoikeus ei ole ainoa käyttöoikeus." @@ -2847,7 +2877,7 @@ msgstr "Vain kaikissa avoimissa yksityisviesteissä" msgid "Across every community you're in" msgstr "Kaikissa yhteisöissä, joissa olet mukana" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Toiminto" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Aktiviteetin jakaminen" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Aktiivisuuskynnys" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Lisää tilillesi ylimääräinen suojauskerros" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Lisää toinen mukautettu pikanäppäin" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Osoite" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Säädä kaikki asetukset itse: melunvaimennus, kaiunpoisto ja vahvistus." @@ -3696,11 +3726,11 @@ msgstr "Salli {everyoneMention} ja {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Salli {PRODUCT_NAME}-sovelluksen käyttää kameraasi kohdassa {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Salli {PRODUCT_NAME}-sovelluksen käyttää mikrofonia kohdassa {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Salli {PRODUCT_NAME}-sovelluksen käyttää mikrofonia. Tarkista selaimesi osoitepalkki tai asetukset." @@ -3803,7 +3833,7 @@ msgstr "Salli jäsenten, joilla ei ole {readMessageHistoryPermissionLabel} -käy msgid "Allow mentions for this role" msgstr "Salli maininnat tälle roolille" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Salli mikrofonin käyttö" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Valtuutus epäonnistui" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Valtuutus epäonnistui. Yritä uudelleen." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Tunnista kieli automaattisesti" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Aseta aktiivisuuskynnys automaattisesti" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Botin käyttöoikeudet" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Botin käyttöoikeudet eivät voi olla negatiivisia." @@ -6869,6 +6899,11 @@ msgstr "Vaihda ryhmän lempinimi" msgid "Change icon" msgstr "Vaihda kuvake" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Muuta järjestelmäasetuksissa" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Kanavan käyttöoikeus" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Kanavan käyttö estetty" @@ -7294,7 +7329,7 @@ msgstr "Kanavan tunnus kopioitu leikepöydälle" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "kanavalinkki" @@ -7462,6 +7497,11 @@ msgstr "viestikenttä" msgid "Chat shortcuts" msgstr "Keskustelun pikanäppäimet" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Tarkista uudelleen" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Valitse ikkuna järjestelmän valitsimesta" msgid "Choose an account" msgstr "Valitse tili" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Valitse toiminto." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Valitse tausta" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Valitse joko guild_id tai channel_id, älä molempia." @@ -7990,7 +8030,7 @@ msgstr "Siistii mikrofonin ääntä, jotta puhe kuuluu selkeästi." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Avaa värivalitsin ja valitse uusi väri napsauttamalla." msgid "Click to react" msgstr "Reagoi napsauttamalla" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Tallenna napsauttamalla" @@ -9110,7 +9150,7 @@ msgstr "Vahvistus" msgid "Confirmation or double-click for community voice joins" msgstr "Vahvistus tai kaksoisnapsautus yhteisön äänikanaviin liittyessä" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Ristiriidassa kohteen {conflictLabel} kanssa" @@ -9127,7 +9167,7 @@ msgstr "Liitä kamera tai virtuaalinen tallennuslaite ja yritä sitten uudelleen msgid "Connect a display, then try again." msgstr "Liitä näyttö ja yritä uudelleen." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Liitä mikrofoni ja yritä uudelleen." @@ -9700,6 +9740,21 @@ msgstr "Kopioi kutsulinkki" msgid "Copy invite URL" msgstr "Kopioi kutsulinkki" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Kopioi rivit" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Kopioi rivit tiedostoa hyprland.conf varten" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Kopioi rivit tiedostoa hyprland.lua varten" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Mukautettu" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Mukautettu valinta" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Mukautettu pikanäppäintoiminto" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Mukautetut pikanäppäinasetukset" @@ -11466,7 +11521,7 @@ msgstr "Oletuspikanäppäimet" msgid "Default translator" msgstr "Oletuskääntäjä" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Oletus:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Poista tallennettu media" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Poista pikanäppäin" @@ -12102,13 +12157,13 @@ msgstr "Kehittäjän tila" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Kehittäjän työkalut" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Kehittäjän työkalut ovat käytettävissä kanavan otsikon henkilöstömerkistä." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Poista taustaohjelman premium-edut käytöstä" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Poista sisäänrakennettu pikanäppäin käytöstä" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Haluatko poistaa näytönjakamisen ponnahdusikkunat käytöstä?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Poista pikanäppäin käytöstä" @@ -12888,7 +12943,7 @@ msgstr "Lahjoita sen sijaan" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Lataa-painike" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Lataa työpöytäsovellus" @@ -13204,6 +13259,11 @@ msgstr "hollanti" msgid "e.g., YubiKey, iPhone, work computer" msgstr "esim. YubiKey, iPhone, työkone" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Yhteen näppäimeen voi liittää vain yhden järjestelmänlaajuisen pikanäppäimen." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Muokkaa kutsulinkkiä" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Muokkaa pikanäppäintä" @@ -13425,7 +13485,7 @@ msgstr "Tallennetun median muokkauslomake" msgid "Edit search engine" msgstr "Muokkaa hakukonetta" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Muokkaa pikanäppäintä" @@ -13753,20 +13813,16 @@ msgstr "Tyhjä muokkaus" msgid "Empty favorites" msgstr "Ei suosikkeja" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Ota käyttöön" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Ota käyttöön {permissionName}-käyttöoikeus" @@ -13876,11 +13932,6 @@ msgstr "Ota laitteistokiihdytys käyttöön ja käynnistä uudelleen" msgid "Enable hardware acceleration?" msgstr "Käytetäänkö laitteistokiihdytystä?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Ota syötteen käyttöoikeus käyttöön, jotta voit käyttää järjestelmänlaajuisia pikanäppäimiä Waylandissa. Kun olet muuttanut syötteen käyttöoikeutta, sulje ja käynnistä {productName} uudelleen, jotta pikanäppäimet voivat käyttää sitä." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Laajenna painamalla vaihtonäppäintä" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Ota pikanäppäin käyttöön" @@ -14357,7 +14408,7 @@ msgstr "Koko järjestelmän ääni" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Sisääntuloääni" @@ -14467,7 +14518,7 @@ msgstr "Ilta-aika" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Tasoittaa mikrofonisi äänenvoimakkuutta, jotta et ole liian hiljainen." @@ -15682,6 +15733,26 @@ msgstr "Vain brittiläisille silmille..." msgid "For me" msgstr "Minulle" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Käytä Mykistä painamalla -toiminnolle yhtä näppäintä ilman Ctrl-, Alt- tai Shift-näppäintä. Jos käytät yhdistelmää, vapauta ensin päänäppäin, muuten mikrofoni voi jäädä mykistetyksi." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Käytä Push-to-talkille yhtä näppäintä ilman Ctrl-, Alt- tai Shift-näppäintä. Jos käytät yhdistelmää, vapauta ensin päänäppäin, muuten mikrofoni voi jäädä päälle." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Käytä tälle pikanäppäimelle yhtä näppäintä ilman Ctrl-, Alt- tai Shift-näppäintä. Jos käytät yhdistelmää, vapauta ensin päänäppäin, muuten pikanäppäin voi jäädä aktiiviseksi." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Käytä ääniaktiivisuuden prioriteetille yhtä näppäintä ilman Ctrl-, Alt- tai Shift-näppäintä. Jos käytät yhdistelmää, vapauta ensin päänäppäin, muuten prioriteetti voi jäädä päälle." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Vahvista turvallisuussyistä henkilöllisyytesi, jotta voit jatkaa." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Globaali profiili" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Globaali pikanäppäin" @@ -17434,6 +17505,11 @@ msgstr "Pidä Shift-näppäintä pohjassa ja paina X valitaksesi sovelluksia. Pa msgid "Hold to record. Drag up to lock, or release to send." msgstr "Pidä painettuna nauhoittaaksesi. Lukitse vetämällä ylös tai lähetä vapauttamalla." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Shift-, Ctrl- tai Alt-näppäimen pitäminen pohjassa estää tämän pikanäppäimen. Lisää vaihtoehtoisia näppäimiä järjestelmäasetuksissa." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Hienoa! Ilmoitukset ovat käytössä" msgid "Hyperlink" msgstr "Hyperlinkki" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprlandissa ei ole pikanäppäinikkunaa. Lisää Hyprland-versiotasi vastaavat rivit Hyprland-määrityksiisi ja korvaa KEY haluamallasi näppäimellä." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprlandissa ei ole pikanäppäinikkunaa. Sido näppäimet Hyprland-määrityksissäsi." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (ennen Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 ja uudemmat)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Hyväksyn <0>tietosuojakäytännön" @@ -17576,7 +17672,7 @@ msgstr "Muutin mieleni" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Ymmärrän" @@ -18000,7 +18096,7 @@ msgstr "Kanavalla" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "Käyttöjärjestelmä-tilassa yllä luetellut sanakirjat ovat ohjeellisia. {PRODUCT_NAME} välittää nämä kielitunnisteet käyttöjärjestelmän oikolukijalle, jos se tukee niitä. Tunnisteet, joita se ei tunnista, ohitetaan." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "Selaimessa push-to-talk toimii vain, kun {PRODUCT_NAME}-välilehti on aktiivinen. Asenna työpöytäsovellus, jos haluat käyttää push-to-talkia koko järjestelmässä." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Syöttö" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Syötteen käyttöoikeutta ei voitu ottaa käyttöön automaattisesti. Muuta asetus manuaalisesti, sulje sitten {productName} kokonaan ja käynnistä se uudelleen, jotta pikanäppäimet voivat käyttää sitä." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Syötteen käyttöoikeutta ei voitu ottaa käyttöön. Yritä uudelleen {settingsMenuName}-asetuksista, sulje sitten {productName} kokonaan ja käynnistä se uudelleen." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Syötteen käyttöoikeus tarvitaan järjestelmänlaajuisiin näppäimistön ja hiiren pikanäppäimiin Waylandissa. Kun olet muuttanut syötteen käyttöoikeutta, sulje {productName} kokonaan ja käynnistä se uudelleen, jotta pikanäppäimet voivat käyttää sitä." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Syötteen käyttöoikeutta muutettiin. Sulje {productName} kokonaan ja käynnistä se uudelleen, jotta järjestelmänlaajuiset pikanäppäimet voivat käyttää sitä." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Tulo ja lähtö" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Virheellinen valtuutuspyyntö" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Virheelliset botin käyttöoikeudet." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Virheellinen tai vanhentunut koodi. Yritä uudelleen." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Virheellinen redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Kutsulinkki on piilotettu jakamisen aikana:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Kutsulinkki piilotettu jakamisen aikana" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Siirry kohtaan {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Siirry kohtaan {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Siirry puheluun" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Siirry linkitettyyn kanavaan" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Siirry linkitettyyn viestiin" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Siirry käyttäjän {authorNickname} viestiin" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Siirry viestiin kohteessa {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Pidä nämä turvassa. Uuden luominen rikkoo olemassa olevat integraatiot." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Jatka lähettämistä hetken näppäimen vapauttamisen jälkeen." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Näppäimistö" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Näppäimistön käyttöä ei voitu käynnistää. Pikanäppäimet toimivat vain, kun {productName} on aktiivisena." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Anna yhteisön valvoa itse itseään" msgid "Let the member hear others in this community again" msgstr "Antoi jäsenen taas kuulla muita tässä yhteisössä" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Sallii sovelluksen {productName} lukea näppäimistöäsi suoraan. Järjestelmäsi sallii tämän jo." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Mukautetun CSS:n avulla työpöytäsovelluksen ikkunan alueet voivat olla läpinäkyviä uudelleenkäynnistyksen jälkeen." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Linkkien tyyli" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Linkki ei ole saatavilla" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Ulkoasu ja tuntuma" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Näyttää siltä, että käytät ohjelmaa {0}. Jos lisäät {PRODUCT_NAME}-käsittelyn sen päälle, ääni voi pumpata tai vääristyä. Suora syöte välittää äänen muokkaamattomana." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Viestin asettelu" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "viestilinkki" @@ -21332,7 +21416,7 @@ msgstr "Mikrofoni" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Mikrofonitesti" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Muut" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "client_id puuttuu" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Lisää vaihtoehtoja" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Lisää pikanäppäinvaihtoehtoja" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Ei {sendMessagesPermission}-käyttöoikeutta" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Ei käyttöoikeutta" @@ -22984,7 +23068,7 @@ msgstr "Ei tuloksia" msgid "No mentions yet" msgstr "Ei vielä mainintoja" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Mikrofonia ei löydy" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Asetuksia ei löytynyt" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Pikanäppäintä ei ole asetettu" @@ -23358,6 +23442,11 @@ msgstr "Ei ole kokonaisluku" msgid "Not accepted" msgstr "Ei hyväksytty" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Ei määritetty" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Avaa {host} selaimessasi." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Avaa {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Avaa {labelText} asetuksissa" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "sivulinkki" @@ -25611,11 +25700,11 @@ msgstr "Läsnäolo" msgid "Press {shortcut} to react" msgstr "Paina {shortcut} reagoidaksesi" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Paina näppäintä, hiiren painiketta tai peliohjaimen painiketta, jonka haluat sitoa. Esc peruuttaa." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Paina pikanäppäintä…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Puhu painamalla -näppäin" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Push-to-talk" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Puhu painamalla (rajoitettu)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Push-to-talkille ei ole vielä määritetty järjestelmänlaajuista näppäintä. Voit määrittää sen kohdassa Asetukset > Sovellukset > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Push-to-talkille ei ole vielä määritetty järjestelmänlaajuista näppäintä. Voit määrittää sen kohdassa Järjestelmäasetukset > Näppäimistö > Pikanäppäimet > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Push-to-talkille ei ole vielä määritetty järjestelmänlaajuista näppäintä. Voit määrittää sen työpöytäsi pikanäppäinasetuksissa." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Push-to-talk ohitetaan suorassa syötteessä. Käytä sitä vaihtamalla kohdennettuun ääneen tai mukautettuun tilaan." @@ -26293,21 +26397,51 @@ msgstr "Push-to-talk ohitetaan suorassa syötteessä. Käytä sitä vaihtamalla msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Push-to-talk on käytössä. Pidä {pushToTalkHint} pohjassa puhuaksesi." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Push-to-talk on käytössä. Pidä Push-to-talk-näppäintä pohjassa puhuaksesi." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Push-to-talk toimii vain, kun {productName} on aktiivisena, koska työpöytäsi ei määrittänyt järjestelmänlaajuisia pikanäppäimiä." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Push-to-talk toimii vain, kun {productName} on aktiivisena, kunnes {permissionName} on otettu käyttöön. Kun olet muuttanut lupaa, sulje ja käynnistä {productName} uudelleen, jotta järjestelmänlaajuiset pikanäppäimet voivat käyttää sitä." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Push-to-talk toimii vain, kun {productName} on aktiivisena, kunnes määrität järjestelmänlaajuiset pikanäppäimet." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Push-to-talk toimii vain, kun selainvälilehti on aktiivinen." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Push-to-talk käyttää näppäintä, jonka olet määrittänyt sovelluksessa {productName}. Määritä se ensin, muuten mikrofonisi käyttää puheaktiivisuutta." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Push-to-talk ei poista mikrofonisi mykistystä, kun äänet ovat mykistettyinä. Poista äänten mykistys, niin voit puhua." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Push-to-talk toimii sovelluksen {productName} ulkopuolella vasta, kun sidot sen Hyprland-määrityksissäsi." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Push-to-talk toimii sovelluksen {productName} ulkopuolella vasta, kun sidot sen Hyprland-määrityksissäsi. Lisää Hyprland-versiotasi vastaava rivi ja korvaa KEY haluamallasi näppäimellä." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Upotuksen raaka JSON" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Ota sisäänrakennettu pikanäppäin uudelleen käyttöön" @@ -26565,13 +26699,6 @@ msgstr "Viimeisimmät maininnat" msgid "Recent searches" msgstr "Viimeisimmät haut" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Tarkista uudelleen" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Suositeltu" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Suositeltu. Puhdistaa mikrofonin selkeää puhetta varten." @@ -26600,6 +26727,11 @@ msgstr "Yhdistä uudelleen" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Yhteys palautettu. Valitse näyttö uudelleen, jos haluat jatkaa edellistä lähetystäsi." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Yhdistetään uudelleen työpöytään…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Yhdistetään uudelleen…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Määritä pikanäppäin" @@ -26851,7 +26983,7 @@ msgstr "Suhteellinen" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Vapautusviive" @@ -27739,13 +27871,13 @@ msgstr "Lähetetty uudelleen" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Palauta" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Palauta \"{label}\" oletusarvoon" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Palauta todelliset arvot" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Palauta oletus" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Valitse tili, jolla haluat kirjautua sisään uudella laitteellasi." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Valitse äänenkäsittelytila" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Lähettää mikrofonin raa'an äänen ilman käsittelyä." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Lähettää äänesi muokkaamattomana. Paras, jos käytät ulkoista ääniohjelmistoa." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Asetettu käyttämällä {preset}-esiasetusta. Arvon valitseminen tässä vaihtaa mukautettuun." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Aseta mukautettu pikanäppäin" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Aseta mukautettu pikanäppäin kohteelle \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "Puhealueeksi asetettiin {region}" msgid "Set the voice region to automatic" msgstr "Puhealueeksi asetettiin automaattinen" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Määritä" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Asetukset" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "asetuslinkki" @@ -30330,8 +30467,8 @@ msgstr "Pikakoodit" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Vaihda väljään asetteluun" msgid "Switch to dense layout" msgstr "Vaihda tiiviiseen asetteluun" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Vaihda suoraan syöttöön" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Järjestelmän hallinnoima asennus" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Järjestelmänlaajuiset pikanäppäimet" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Järjestelmänlaajuisia pikanäppäimiä ei voitu määrittää." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Järjestelmänlaajuinen: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Järjestelmänlaajuinen: Ei määritetty" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Etsimääsi sivua ei löydy tai se on siirretty." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Annettua redirect_uri-osoitetta ei ole rekisteröity tälle sovellukselle." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "redirect_uri-osoitteen on käytettävä HTTP- tai HTTPS-protokollaa." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Animaatio on liian pitkä tai liian suuri rajattavaksi. Kokeile lyhyempää tai pienempää tiedostoa." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Tämä sovellus" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Tämä liite on vanhentunut" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Tämä botti ei ole julkinen, eivätkä muut käyttäjät voi lisätä sitä." @@ -34081,6 +34233,16 @@ msgstr "Tämä keskustelu ei ole enää saatavilla." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Tätä työpöytäversiota ei voi päivittää suoraan {productName}-sovelluksessa. Lataa sen sijaan uusin työpöytäasennusohjelma." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Tämä työpöytä ei tue järjestelmänlaajuisia pikanäppäimiä. Push-to-talk toimii vain, kun {productName} on aktiivisena." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Tämä työpöytä ei tue järjestelmänlaajuisia pikanäppäimiä. Pikanäppäimet toimivat silti, kun {productName} on aktiivisena." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Tämä on viimeinen pääsyavaimesi, ja pääsyavaimet ovat tilisi toine msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Tämä on viimeinen pääsyavaimesi. Kun se on poistettu, et voi enää käyttää pääsyavainta toisena vaiheena." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Tämä näppäin toimii, kun {productName} on aktiivisena. Työpöytäsi määrittää järjestelmänlaajuisen näppäimen, jonka näet kohdassa {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Tämä linkkikanava on merkitty aikuisille tarkoitetuksi sisällöksi, ja se voi avata materiaalia, joka voi olla sopimatonta joillekin käyttäjille." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Tämä linkki ei ole käytettävissä tililläsi." @@ -34778,6 +34945,21 @@ msgstr "Otsikkorivi" msgid "Title bar platform" msgstr "Otsikkorivin alusta" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Voit muuttaa näppäimiä kohdassa Asetukset > Sovellukset > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Voit muuttaa näppäimiä kohdassa Järjestelmäasetukset > Näppäimistö > Pikanäppäimet > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Voit muuttaa näppäimiä työpöytäsi pikanäppäinasetuksissa." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Voit luoda webhookin avaamalla <0>{channelWebhooksSettingsPath}. Voit edelleen muokata ja järjestellä kaikkia olemassa olevia webhookeja täällä." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Tuntematon" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Tuntematon sovellus, vanhentunut istunto tai virheellinen valtuutuspyyntö." @@ -36410,6 +36592,11 @@ msgstr "Jopa 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "Jopa 720p 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Päivitä {productName}, jotta voit käyttää järjestelmänlaajuisia pikanäppäimiä Waylandissa." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Käytä tummaa teemaa koko sovelluksessa" msgid "Use default" msgstr "Käytä oletusta" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Käytä suoraa pääsyä syöttölaitteisiin" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Äänipuhelun osallistujat" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Äänenkäsittely" @@ -38264,7 +38456,7 @@ msgstr "Äänten mykistyksen poisto" msgid "Voice unmute" msgstr "Mikrofonin mykistyksen poisto" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Ääni käyttää tässä selaimessa järjestelmäsi äänilähtölaitetta. Muuta sitä järjestelmäasetuksissasi." @@ -38278,7 +38470,7 @@ msgstr "Mitätöity" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Odotetaan hitaan tilan päättymistä yhdessä tai useammassa valitussa msgid "Waiting for you to..." msgstr "Odotan, että..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Odotetaan työpöytääsi…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Mitkä keskustelut" msgid "Which rule was broken?" msgstr "Mitä sääntöä rikottiin?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Kun paina ja puhu -toiminto on käytössä, pikanäppäin ohjaa mikrofonia, joten mykistyspainike on poissa käytöstä." @@ -39094,6 +39291,11 @@ msgstr "Ilman tätä käyttöoikeutta on käytettävä paina ja puhu -toimintoa. msgid "Wordmark" msgstr "Sanamerkki" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Toimii, kun {productName} on aktiivisena" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Vaihdoit tunnisteesi {PREMIUM_PRODUCT_NAME}-tilauksen aikana. Tunniste arvotaan uudelleen satunnaisesti, kun {PREMIUM_PRODUCT_NAME}-tilauksesi päättyy, ellei sitä uusita." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Sinulla ei ole pääsyä kanavalle, jolle tämä viesti lähetettiin." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "{premiumProductName}-tilauksesi on vanhentunut. Olet menettänyt kaikki {premiumProductName}-edut. Aktivoi tilauksesi uudelleen saadaksesi edut takaisin." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "{productName}-istuntosi vanheni. Kirjaudu sisään uudelleen jatkaaksesi tämän sovelluksen valtuuttamista." @@ -40144,6 +40346,16 @@ msgstr "Yhteisössäsi on oltava vähintään {minMemberCount, plural, one {# j msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Luottokorttisi on tarkistettu. Jos kortti on kelvollinen, aikuisille tarkoitettu sisältö avautuu pian. Voit sulkea tämän välilehden ja palata sovellukseen." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Työpöytäsi ei määrittänyt järjestelmänlaajuisia pikanäppäimiä." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Työpöytäsi suorittaa nämä pikanäppäimet myös silloin, kun {productName} ei ole aktiivisena." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/fr/messages.po b/fluxer_app/src/features/i18n/locales/fr/messages.po index 666361234..9c8cfa87e 100644 --- a/fluxer_app/src/features/i18n/locales/fr/messages.po +++ b/fluxer_app/src/features/i18n/locales/fr/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# canal suit} other {# canaux suivent}} ce canal. msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# communauté trouvée} other {# communautés trouvées}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# communauté est temporairement indisponible en raison d'un dysfonctionnement du condensateur de flux.} other {# communautés sont temporairement indisponibles en raison d'un dysfonctionnement du condensateur de flux.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} évite d’envoyer des notifications push à vos appareils msgid "{productName} Bug Hunter" msgstr "Chasseur de bugs {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} peut demander à votre bureau d'exécuter ses raccourcis lorsque {productName} n'est pas au premier plan. Votre bureau vous demandera de confirmer et vous laissera choisir les touches." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} ne peut plus lire votre clavier. Il écoute donc les raccourcis via X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} ne peut plus lire votre clavier. Les raccourcis de votre bureau sont donc utilisés à la place." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} ne peut plus lire votre clavier. Les raccourcis ne fonctionnent que lorsque {productName} est au premier plan." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} peut enregistrer ses raccourcis auprès de Hyprland pour qu'ils fonctionnent lorsque {productName} n'est pas au premier plan. Vous associez ensuite les touches dans votre configuration Hyprland." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} a besoin d'accéder à votre caméra pour afficher vos appareils." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} a besoin d'accéder à votre microphone pour afficher vos appareils." @@ -1837,6 +1862,11 @@ msgstr "{productName} est maintenant disponible sur {host}. Installez-le à part msgid "{productName} Partner" msgstr "Partenaire {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} a enregistré ses raccourcis auprès de Hyprland. Associez-les dans votre configuration Hyprland pour les utiliser lorsque {productName} n'est pas au premier plan." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Un profil qui sort du lot" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Le paramètre redirect_uri est requis lorsque bot n’est pas la seule portée demandée." @@ -2847,7 +2877,7 @@ msgstr "Uniquement dans les conversations privées ouvertes" msgid "Across every community you're in" msgstr "Dans toutes les communautés dont vous faites partie" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Action" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Partage d’activité" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Seuil d'activité" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Ajoutez une couche de sécurité supplémentaire à votre compte" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Ajouter un autre raccourci personnalisé" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Adresse" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Ajustez chaque paramètre vous-même : suppression du bruit, annulation de l'écho et gain." @@ -3696,11 +3726,11 @@ msgstr "Autoriser {everyoneMention} et {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Autorisez {PRODUCT_NAME} à accéder à votre caméra dans {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Autorisez {PRODUCT_NAME} à accéder à votre micro dans {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Autorisez {PRODUCT_NAME} à accéder à votre microphone. Vérifiez la barre d'adresse ou les paramètres de votre navigateur." @@ -3803,7 +3833,7 @@ msgstr "Autoriser les membres sans la permission {readMessageHistoryPermissionLa msgid "Allow mentions for this role" msgstr "Autoriser les mentions pour ce rôle" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Autoriser l’accès au micro" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Échec de l'autorisation" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Échec de l'autorisation. Réessayez." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Détection automatique de la langue" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Définir automatiquement le seuil d'activité" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Permissions du bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "La valeur des permissions du bot ne peut pas être négative." @@ -6869,6 +6899,11 @@ msgstr "Modifier le surnom du groupe" msgid "Change icon" msgstr "Changer l'icône" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Modifier dans les paramètres système" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Accès aux salons" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Accès au salon refusé" @@ -7294,7 +7329,7 @@ msgstr "ID du salon copié dans le presse-papiers" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "lien de salon" @@ -7462,6 +7497,11 @@ msgstr "champ de saisie du chat" msgid "Chat shortcuts" msgstr "Raccourcis de discussion" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Vérifier à nouveau" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Choisissez une fenêtre dans le sélecteur de votre système" msgid "Choose an account" msgstr "Choisir un compte" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Choisissez une action." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Choisir un arrière-plan" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Choisissez guild_id ou channel_id, pas les deux." @@ -7990,7 +8030,7 @@ msgstr "Améliore le son du micro pour une voix claire." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Cliquez pour ouvrir le sélecteur de couleurs et choisir une nouvelle co msgid "Click to react" msgstr "Cliquer pour réagir" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Cliquer pour enregistrer" @@ -9110,7 +9150,7 @@ msgstr "Confirmation" msgid "Confirmation or double-click for community voice joins" msgstr "Confirmation ou double-clic pour rejoindre les salons vocaux des communautés" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Conflit avec {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Connectez une caméra ou un appareil de capture virtuel, puis réessayez msgid "Connect a display, then try again." msgstr "Connectez un écran, puis réessayez." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Connectez un microphone et réessayez." @@ -9700,6 +9740,21 @@ msgstr "Copier le lien d'invitation" msgid "Copy invite URL" msgstr "Copier le lien d'invitation" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Copier les lignes" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Copier les lignes pour hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Copier les lignes pour hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Personnalisé" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Sélection personnalisée" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Action de raccourci personnalisée" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Options de raccourcis personnalisés" @@ -11466,7 +11521,7 @@ msgstr "Raccourcis par défaut" msgid "Default translator" msgstr "Traducteur par défaut" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Par défaut :" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Supprimer le média enregistré" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Supprimer le raccourci" @@ -12102,13 +12157,13 @@ msgstr "État du développeur" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Outils de développement" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Les outils de développement sont accessibles depuis le badge du personnel dans l'en-tête du salon." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Désactiver les avantages premium du backend" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Désactiver le raccourci intégré" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Désactiver les fenêtres d’incrustation des partages d’écran ?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Désactiver le raccourci" @@ -12888,7 +12943,7 @@ msgstr "Faire un don plutôt" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Bouton de téléchargement" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Télécharger l’application de bureau" @@ -13204,6 +13259,11 @@ msgstr "Néerlandais" msgid "e.g., YubiKey, iPhone, work computer" msgstr "ex. : YubiKey, iPhone, ordinateur professionnel" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Chaque touche ne peut exécuter qu'un seul raccourci système." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Modifier le lien d'invitation" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Modifier le raccourci clavier" @@ -13425,7 +13485,7 @@ msgstr "Formulaire de modification du média enregistré" msgid "Edit search engine" msgstr "Modifier le moteur de recherche" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Modifier le raccourci" @@ -13753,20 +13813,16 @@ msgstr "Modification vide" msgid "Empty favorites" msgstr "Favoris vides" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Activer" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Activer la permission {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Activer l'accélération matérielle et redémarrer" msgid "Enable hardware acceleration?" msgstr "Activer l'accélération matérielle ?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Activez l'accès aux entrées pour utiliser les raccourcis système sur Wayland. Après avoir modifié l'accès aux entrées, quittez complètement et redémarrez {productName} pour que les raccourcis puissent l'utiliser." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Activer Maj pour développer" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Activer le raccourci" @@ -14357,7 +14408,7 @@ msgstr "Audio du système entier" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Son d’entrée" @@ -14467,7 +14518,7 @@ msgstr "Soir" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Équilibre le volume de votre micro pour que votre voix reste bien audible." @@ -15682,6 +15733,26 @@ msgstr "Pour les yeux britanniques seulement..." msgid "For me" msgstr "Pour moi" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Pour « Appuyer pour couper le micro », utilisez une seule touche sans Ctrl, Alt ou Maj. Avec une combinaison, relâchez d'abord la touche principale, sinon le micro peut rester coupé." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Pour « Appuyer pour parler », utilisez une seule touche sans Ctrl, Alt ou Maj. Avec une combinaison, relâchez d'abord la touche principale, sinon le micro peut rester activé." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Pour ce raccourci, utilisez une seule touche sans Ctrl, Alt ou Maj. Avec une combinaison, relâchez d'abord la touche principale, sinon le raccourci peut rester actif." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Pour « Priorité à l'activité vocale », utilisez une seule touche sans Ctrl, Alt ou Maj. Avec une combinaison, relâchez d'abord la touche principale, sinon la priorité peut rester activée." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Pour votre sécurité, veuillez confirmer votre identité pour continuer." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Profil global" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Raccourci global" @@ -17434,6 +17505,11 @@ msgstr "Maintenez la touche Maj enfoncée et appuyez sur X pour sélectionner de msgid "Hold to record. Drag up to lock, or release to send." msgstr "Maintenez pour enregistrer. Faites glisser vers le haut pour verrouiller, ou relâchez pour envoyer." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Maintenir Maj, Ctrl ou Alt enfoncée bloque ce raccourci. Ajoutez des alternatives dans les paramètres système." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Hourra ! Les notifications sont activées" msgid "Hyperlink" msgstr "Lien hypertexte" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland n'a pas de boîte de dialogue de raccourcis. Ajoutez les lignes correspondant à votre version de Hyprland à votre configuration Hyprland et remplacez KEY par la touche de votre choix." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland n'a pas de boîte de dialogue de raccourcis. Associez les touches dans votre configuration Hyprland." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (avant Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 et versions ultérieures)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "J'accepte la <0>politique de confidentialité" @@ -17576,7 +17672,7 @@ msgstr "J'ai changé d'avis" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "J'ai compris" @@ -18000,7 +18096,7 @@ msgstr "Dans" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "En mode « Système d’exploitation », les dictionnaires ci-dessus sont indicatifs. {PRODUCT_NAME} transmet ces codes de langue au correcteur orthographique de votre système lorsque celui-ci le permet. Les codes non reconnus sont ignorés." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "Dans un navigateur, la fonction \" Appuyer pour parler \" ne fonctionne que lorsque l'onglet {PRODUCT_NAME} est actif. Installez l'application de bureau pour utiliser cette fonction à l'échelle du système." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Saisie" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "L'accès aux entrées n'a pas pu être activé automatiquement. Modifiez-le manuellement, puis quittez et redémarrez complètement {productName} pour que les raccourcis puissent l'utiliser." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "L'accès aux entrées n'a pas pu être activé. Réessayez depuis les paramètres {settingsMenuName}, puis quittez et redémarrez complètement {productName}." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "L'accès aux entrées est requis pour les raccourcis clavier et souris à l'échelle du système sur Wayland. Après avoir modifié l'accès aux entrées, quittez et redémarrez complètement {productName} pour que les raccourcis puissent l'utiliser." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "L’accès aux entrées a été modifié. Quittez complètement puis redémarrez {productName} pour que les raccourcis système puissent l’utiliser." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Entrée et sortie" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Requête d'autorisation non valide" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Permissions du bot non valides." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Code invalide ou expiré. Veuillez réessayer." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Paramètre redirect_uri non valide" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Le lien d'invitation est masqué pendant le partage :" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Lien d’invitation masqué pendant le partage d’écran" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Aller à {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Aller à {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Afficher l’appel en cours" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Accéder au salon lié" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Aller au message lié" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Aller au message de {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Aller au message dans {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Gardez-les en sécurité. La régénération interrompra les intégrations existantes." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Continuer à transmettre brièvement après avoir relâché le bouton." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Clavier" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "L'accès au clavier n'a pas pu démarrer. Les raccourcis ne fonctionnent que lorsque {productName} est au premier plan." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Laisser la communauté s'auto-modérer" msgid "Let the member hear others in this community again" msgstr "A de nouveau autorisé le membre à entendre les autres dans cette communauté" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Permet à {productName} de lire directement votre clavier. Votre système l'autorise déjà." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Permet au CSS personnalisé de révéler les zones de fenêtre transparentes après le redémarrage de l'application de bureau." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Style des liens" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Lien indisponible" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Apparence" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Il semble que vous utilisiez {0}. Empiler le traitement de {PRODUCT_NAME} par-dessus peut provoquer des pompages ou des distorsions. L'entrée directe transmet votre audio sans modification." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Disposition des messages" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "lien du message" @@ -21332,7 +21416,7 @@ msgstr "Micro" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Test micro" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Divers" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "client_id manquant" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Plus d'options" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Plus d'options de raccourcis" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Permission {sendMessagesPermission} manquante" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Accès refusé" @@ -22984,7 +23068,7 @@ msgstr "Aucun résultat" msgid "No mentions yet" msgstr "Aucune mention pour l'instant" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Aucun microphone détecté" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Aucun paramètre trouvé" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Aucun raccourci défini" @@ -23358,6 +23442,11 @@ msgstr "Ce n’est pas un nombre entier" msgid "Not accepted" msgstr "Non accepté" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Non attribué" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Ouvrez {host} dans votre navigateur." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Ouvrir {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Ouvrir {labelText} dans les paramètres" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page suiv." #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "lien de page" @@ -25611,11 +25700,11 @@ msgstr "Présence" msgid "Press {shortcut} to react" msgstr "Appuyez sur {shortcut} pour réagir" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Appuyez sur une touche, un bouton de souris ou de manette pour l'assigner. Échap annule." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Appuyez sur un raccourci…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Touche \" Appuyer pour parler \"" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Appuyer pour parler" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Appuyer pour parler (limité)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "« Appuyer pour parler » n'a pas encore de touche système. Pour en définir une, allez dans Paramètres > Applications > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "« Appuyer pour parler » n'a pas encore de touche système. Pour en définir une, allez dans Configuration du système > Clavier > Raccourcis > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "« Appuyer pour parler » n'a pas encore de touche système. Pour en définir une, ouvrez les paramètres de raccourcis clavier de votre bureau." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "« Appuyer pour parler » est ignoré en mode « Entrée directe ». Passez en mode « Optimisé pour la voix » ou « Personnalisé » pour l’utiliser." @@ -26293,21 +26397,51 @@ msgstr "« Appuyer pour parler » est ignoré en mode « Entrée directe ». Pas msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "« Appuyer pour parler » est activé. Maintenez {pushToTalkHint} enfoncé pour parler." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "« Appuyer pour parler » est activé. Maintenez votre touche enfoncée pour parler." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "« Appuyer pour parler » ne fonctionne que lorsque {productName} est au premier plan, car votre bureau n'a pas configuré les raccourcis système." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Le mode « Appuyer pour parler » ne fonctionne que lorsque {productName} est au premier plan, jusqu'à ce que l'autorisation {permissionName} soit activée. Après avoir modifié l'autorisation, quittez et redémarrez complètement {productName} pour que les raccourcis clavier système puissent l'utiliser." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "« Appuyer pour parler » ne fonctionne que lorsque {productName} est au premier plan, tant que vous n'avez pas configuré les raccourcis système." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "« Appuyer pour parler » fonctionne uniquement lorsque l’onglet du navigateur est actif." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "« Appuyer pour parler » utilisera la touche définie dans {productName}. Définissez-en une d'abord, sinon votre micro utilisera l'activité vocale." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "« Appuyer pour parler » ne réactive pas votre micro lorsque le son est désactivé. Réactivez le son pour parler." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "« Appuyer pour parler » ne fonctionne en dehors de {productName} qu'après l'avoir associé dans votre configuration Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "« Appuyer pour parler » ne fonctionne en dehors de {productName} qu'après l'avoir associé dans votre configuration Hyprland. Ajoutez la ligne correspondant à votre version de Hyprland et remplacez KEY par la touche de votre choix." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "JSON brut du contenu intégré" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Réactiver le raccourci intégré" @@ -26565,13 +26699,6 @@ msgstr "Mentions récentes" msgid "Recent searches" msgstr "Recherches récentes" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Vérifier à nouveau" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Recommandé" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Recommandé. Améliore le son du micro pour une voix claire." @@ -26600,6 +26727,11 @@ msgstr "Se reconnecter" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Reconnexion réussie. Choisissez à nouveau un écran pour reprendre votre diffusion précédente." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Reconnexion à votre bureau…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Reconnexion…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Enregistrer le raccourci" @@ -26851,7 +26983,7 @@ msgstr "Relatif" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Délai de relâchement" @@ -27739,13 +27871,13 @@ msgstr "Renvoyé" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Réinitialiser" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Rétablir le réglage intégré de « {label} »" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Rétablir les valeurs réelles" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Réinitialiser les raccourcis par défaut" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Sélectionnez le compte avec lequel vous souhaitez vous connecter sur votre nouvel appareil." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Sélectionner le mode de traitement vocal" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Envoie l'audio brut du micro sans traitement." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Envoie votre audio tel quel. Idéal si vous utilisez un logiciel audio externe." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Défini par le préréglage {preset}. Choisir une valeur ici passe en mode Personnalisé." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Définir un raccourci personnalisé" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Définir un raccourci personnalisé pour « {label} »" @@ -30066,6 +30198,11 @@ msgstr "A défini la région vocale sur {region}" msgid "Set the voice region to automatic" msgstr "A défini la région vocale sur automatique" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Configurer" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Réglages" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "lien vers les paramètres" @@ -30330,8 +30467,8 @@ msgstr "Codes courts" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Passer à la mise en page confortable" msgid "Switch to dense layout" msgstr "Passer à la mise en page dense" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Passer en mode Entrée directe" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Installation gérée par le système" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Raccourcis système" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Les raccourcis système n'ont pas pu être configurés." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Système : {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Système : non attribué" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "La page que vous recherchez n'existe pas ou a été déplacée." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "L'URI de redirection fournie n'est pas enregistrée pour cette application." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "L'URI de redirection fournie doit utiliser HTTP ou HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Cette animation est trop longue ou trop volumineuse pour être rognée. Essayez un fichier plus court ou plus petit." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Cette application" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Cette pièce jointe a expiré" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Ce bot n'est pas public et ne peut pas être ajouté par d'autres utilisateurs." @@ -34081,6 +34233,16 @@ msgstr "Cette conversation n'est plus disponible." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Cette version de bureau ne peut pas installer les mises à jour directement dans {productName}. Téléchargez plutôt la dernière version du programme d’installation pour ordinateur." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Ce bureau ne propose pas de raccourcis système. « Appuyer pour parler » ne fonctionne que lorsque {productName} est au premier plan." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Ce bureau ne propose pas de raccourcis système. Les raccourcis fonctionnent quand même lorsque {productName} est au premier plan." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "C'est votre dernière clé d'accès, et les clés d'accès sont votre de msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "C'est votre dernière clé d'accès. Une fois qu'elle sera supprimée, vous ne pourrez plus utiliser de clé d'accès comme deuxième facteur." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Cette touche fonctionne lorsque {productName} est au premier plan. Votre bureau définit la touche système dans {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Ce salon de lien est réservé aux adultes et peut ouvrir du contenu inapproprié pour certains utilisateurs." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Ce lien n'est pas disponible pour votre compte." @@ -34778,6 +34945,21 @@ msgstr "Barre de titre" msgid "Title bar platform" msgstr "Plateforme de la barre de titre" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Pour modifier les touches, allez dans Paramètres > Applications > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Pour modifier les touches, allez dans Configuration du système > Clavier > Raccourcis > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Pour modifier les touches, ouvrez les paramètres de raccourcis clavier de votre bureau." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Pour créer un webhook, ouvrez <0>{channelWebhooksSettingsPath}. Vous pouvez toujours modifier et organiser tous les webhooks existants ici." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Inconnu" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Application inconnue, session expirée ou requête d'autorisation invalide." @@ -36410,6 +36592,11 @@ msgstr "Jusqu’à 4K à 60 FPS" msgid "Up to 720p at 30 FPS" msgstr "Jusqu'à 720p à 30 IPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Mettez à jour {productName} pour utiliser les raccourcis système sur Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Utiliser le thème sombre dans toute l'application" msgid "Use default" msgstr "Utiliser par défaut" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Utiliser l'accès direct aux périphériques d'entrée" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Participants à l'appel" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Traitement de la voix" @@ -38264,7 +38456,7 @@ msgstr "Son réactivé" msgid "Voice unmute" msgstr "Micro réactivé" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "La voix utilise votre périphérique de sortie système dans ce navigateur. Modifiez-le dans vos paramètres système." @@ -38278,7 +38470,7 @@ msgstr "Annulée" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "En attente de l'expiration du mode lent dans un ou plusieurs salons sél msgid "Waiting for you to..." msgstr "En attente d’une action de votre part…" +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "En attente de votre bureau…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Quelles conversations" msgid "Which rule was broken?" msgstr "Quelle règle a été enfreinte ?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Lorsque « Appuyer pour parler » est activé, votre raccourci contrôle le micro. Le bouton « Couper le micro » est donc désactivé." @@ -39094,6 +39291,11 @@ msgstr "Sans cette permission, « Appuyer pour parler » est requis." msgid "Wordmark" msgstr "Logo textuel" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Fonctionne lorsque {productName} est au premier plan" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Vous avez changé votre tag alors que vous étiez abonné à {PREMIUM_PRODUCT_NAME}. Il sera modifié aléatoirement à l'expiration de votre abonnement {PREMIUM_PRODUCT_NAME}, sauf s'il est renouvelé." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Vous n'avez pas accès au salon où ce message a été envoyé." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Votre abonnement {premiumProductName} a expiré. Vous avez perdu tous les avantages {premiumProductName}. Réactivez votre abonnement pour retrouver l'accès." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Votre session {productName} a expiré. Connectez-vous à nouveau pour continuer à autoriser cette application." @@ -40144,6 +40346,16 @@ msgstr "Votre communauté doit compter au moins {minMemberCount, plural, one {# msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Votre carte de crédit a été vérifiée. Si elle est valide, les contenus pour adultes seront bientôt débloqués. Vous pouvez fermer cet onglet et revenir dans l’application." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Votre bureau n'a pas configuré les raccourcis système." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Votre bureau exécute ces raccourcis même lorsque {productName} n'est pas actif." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/he/messages.po b/fluxer_app/src/features/i18n/locales/he/messages.po index 4de8f0fef..b616966bd 100644 --- a/fluxer_app/src/features/i18n/locales/he/messages.po +++ b/fluxer_app/src/features/i18n/locales/he/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# ערוץ עוקב} other {# ערוצים עוקב msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# קהילה נמצאה} other {# קהילות נמצאו}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# קהילה אינה זמינה באופן זמני עקב תקלה בקבל השטף.} other {# קהילות אינן זמינות באופן זמני עקב תקלה בקבל השטף.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} נמנע משליחת התראות דחיפה למכשיר msgid "{productName} Bug Hunter" msgstr "צייד באגים של {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} יכול לבקש משולחן העבודה שלך להפעיל את הקיצורים שלו כש-{productName} אינו ממוקד. שולחן העבודה יבקש ממך אישור ויאפשר לך לבחור את המקשים." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} כבר לא יכול לקרוא את המקלדת שלך, ולכן הוא מאזין לקיצורי דרך באמצעות X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} כבר לא יכול לקרוא את המקלדת שלך, ולכן נעשה שימוש בקיצורי הדרך של שולחן העבודה שלך." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} כבר לא יכול לקרוא את המקלדת שלך. קיצורי הדרך פועלים רק כאשר {productName} ממוקד." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} יכול לרשום את הקיצורים שלו ב-Hyprland כדי שיפעלו גם כש-{productName} אינו ממוקד. לאחר מכן תקצה את המקשים בקובץ התצורה של Hyprland." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} זקוק לגישה למצלמה כדי להציג את המכשירים שלך." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} זקוק לגישה למיקרופון כדי להציג את המכשירים שלך." @@ -1837,6 +1862,11 @@ msgstr "{productName} נמצא כעת בכתובת {host}. התקן אותו מ msgid "{productName} Partner" msgstr "שותף של {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} רשם את הקיצורים שלו ב-Hyprland. הקצה להם מקשים בקובץ התצורה של Hyprland כדי להשתמש בהם כש-{productName} אינו ממוקד." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "פרופיל שיבלוט" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "נדרש redirect_uri כאשר ההרשאה bot אינה ההרשאה היחידה." @@ -2847,7 +2877,7 @@ msgstr "רק בכל ההודעות הפרטיות הפתוחות" msgid "Across every community you're in" msgstr "בכל הקהילות שבהן אתה חבר" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "פעולה" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "שיתוף פעילות" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "סף פעילות" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "הוסף שכבת אבטחה נוספת לחשבונך" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "הוספת קיצור דרך מותאם אישית נוסף" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "כתובת" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "התאם כל הגדרה בעצמך: דיכוי רעשים, ביטול הד והגברה." @@ -3696,11 +3726,11 @@ msgstr "אפשר {everyoneMention} ו-{hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "אפשר ל-{PRODUCT_NAME} לגשת למצלמה שלך ב-{MACOS_SYSTEM_SETTINGS_NAME} ← {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} ← {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "אפשר ל-{PRODUCT_NAME} לגשת למיקרופון שלך ב-{MACOS_SYSTEM_SETTINGS_NAME} ← {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} ← {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "אפשר ל-{PRODUCT_NAME} לגשת למיקרופון שלך. בדוק את שורת הכתובת או את ההגדרות של הדפדפן." @@ -3803,7 +3833,7 @@ msgstr "אפשר לחברים ללא הרשאת {readMessageHistoryPermissionLab msgid "Allow mentions for this role" msgstr "אפשר אזכורים לתפקיד זה" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "אפשר מיקרופון" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "ההרשאה נכשלה" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "ההרשאה נכשלה. יש לנסות שוב." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "זיהוי שפה אוטומטי" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "קביעה אוטומטית של סף הפעילות" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "הרשאות בוט" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "הרשאות בוט לא יכולות להיות שליליות." @@ -6869,6 +6899,11 @@ msgstr "שינוי הכינוי בקבוצה" msgid "Change icon" msgstr "שינוי אייקון" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "שינוי בהגדרות המערכת" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "גישה לערוץ" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "הגישה לערוץ נדחתה" @@ -7294,7 +7329,7 @@ msgstr "מזהה הערוץ הועתק ללוח הגזירים" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "קישור לערוץ" @@ -7462,6 +7497,11 @@ msgstr "שדה הקלדה" msgid "Chat shortcuts" msgstr "קיצורי דרך לצ'אט" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "בדיקה חוזרת" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "בחר חלון מבורר המערכת שלך" msgid "Choose an account" msgstr "בחר חשבון" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "בחר פעולה." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "בחר רקע" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "יש לבחור guild_id או channel_id, לא את שניהם." @@ -7990,7 +8030,7 @@ msgstr "מנקה את המיקרופון שלך לדיבור ברור." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "יש ללחוץ כדי לפתוח את בורר הצבעים ולבחו msgid "Click to react" msgstr "יש ללחוץ כדי להגיב" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "יש ללחוץ כדי להקליט" @@ -9110,7 +9150,7 @@ msgstr "אישור" msgid "Confirmation or double-click for community voice joins" msgstr "אישור או לחיצה כפולה להצטרפות לערוץ קולי בקהילה" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "מתנגש עם {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "חבר מצלמה או התקן לכידה וירטואלי, ואז נס msgid "Connect a display, then try again." msgstr "חבר צג, ואז נסה שוב." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "חבר מיקרופון ונסה שוב." @@ -9700,6 +9740,21 @@ msgstr "העתקת קישור להזמנה" msgid "Copy invite URL" msgstr "העתקת כתובת ה-URL של ההזמנה" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "העתקת שורות" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "העתקת שורות עבור hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "העתקת שורות עבור hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "מותאם אישית" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "בחירה מותאמת אישית" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "פעולה מותאמת אישית לקיצור דרך" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "אפשרויות קיצור דרך מותאמות אישית" @@ -11466,7 +11521,7 @@ msgstr "קיצורי דרך ברירת מחדל" msgid "Default translator" msgstr "מתרגם ברירת מחדל" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "ברירת מחדל:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "מחיקת מדיה שמורה" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "מחיקת קיצור דרך" @@ -12102,13 +12157,13 @@ msgstr "מצב מפתח" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "כלי מפתחים" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "כלי מפתחים זמינים דרך תג הצוות בכותרת הערוץ." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "השבתת הטבות פרימיום בשרת" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "השבת קיצור דרך מובנה" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "האם להשבית חלונות קופצים של שיתוף מסך?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "השבת קיצור דרך" @@ -12888,7 +12943,7 @@ msgstr "לתרום במקום זאת" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "כפתור הורדה" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "הורדת אפליקציית המחשב" @@ -13204,6 +13259,11 @@ msgstr "הולנדית" msgid "e.g., YubiKey, iPhone, work computer" msgstr "לדוגמה: YubiKey, iPhone, מחשב עבודה" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "כל מקש יכול להפעיל רק קיצור דרך כלל-מערכתי אחד." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "עריכת קישור הזמנה" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "עריכת קיצור מקלדת" @@ -13425,7 +13485,7 @@ msgstr "טופס עריכת מדיה שמורה" msgid "Edit search engine" msgstr "עריכת מנוע חיפוש" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "עריכת קיצור דרך" @@ -13753,20 +13813,16 @@ msgstr "עריכה ריקה" msgid "Empty favorites" msgstr "אין מועדפים" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "הפעלה" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "הפעלת הרשאת {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "הפעלת האצת חומרה והפעלה מחדש" msgid "Enable hardware acceleration?" msgstr "להפעיל האצת חומרה?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "כדי להשתמש בקיצורי דרך כלל-מערכתיים ב-Wayland, יש לאפשר גישת קלט. לאחר שינוי גישת הקלט, יש לצאת מ-{productName} לחלוטין ולהפעיל אותה מחדש כדי שקיצורי הדרך יוכלו להשתמש בה." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "הפעל Shift כדי להרחיב" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "הפעל קיצור דרך" @@ -14357,7 +14408,7 @@ msgstr "כל צלילי המערכת" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "צליל כניסה" @@ -14467,7 +14518,7 @@ msgstr "ערב" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "מאזן את עוצמת המיקרופון שלך כדי שלא תישמע שקט מדי." @@ -15682,6 +15733,26 @@ msgstr "לעיניים בריטיות בלבד..." msgid "For me" msgstr "בשבילי" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "עבור השתקה בלחיצה, השתמש במקש יחיד ללא Ctrl, Alt או Shift. אם אתה משתמש בשילוב מקשים, שחרר קודם את המקש הראשי, אחרת המיקרופון עלול להישאר מושתק." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "עבור דיבור בלחיצה, השתמש במקש יחיד ללא Ctrl, Alt או Shift. אם אתה משתמש בשילוב מקשים, שחרר קודם את המקש הראשי, אחרת המיקרופון עלול להישאר פתוח." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "עבור קיצור דרך זה, השתמש במקש יחיד ללא Ctrl, Alt או Shift. אם אתה משתמש בשילוב מקשים, שחרר קודם את המקש הראשי, אחרת הקיצור עלול להישאר פעיל." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "עבור עדיפות לפעילות קולית, השתמש במקש יחיד ללא Ctrl, Alt או Shift. אם אתה משתמש בשילוב מקשים, שחרר קודם את המקש הראשי, אחרת העדיפות עלולה להישאר פעילה." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "למען אבטחתך, יש לאמת את זהותך כדי להמשיך." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "פרופיל גלובלי" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "קיצור דרך גלובלי" @@ -17434,6 +17505,11 @@ msgstr "החזק את Shift ולחץ על X כדי לבחור אפליקציות msgid "Hold to record. Drag up to lock, or release to send." msgstr "החזק כדי להקליט. גרור למעלה כדי לנעול, או שחרר כדי לשלוח." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "החזקת Shift, Ctrl או Alt חוסמת את קיצור הדרך הזה. הוסף חלופות בהגדרות המערכת שלך." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "הידד! ההתראות מופעלות" msgid "Hyperlink" msgstr "קישור" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "ל-Hyprland אין חלון קיצורי דרך. הוסף את השורות עבור גרסת ה-Hyprland שלך לקובץ התצורה של Hyprland והחלף את KEY במקש הרצוי." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "ל-Hyprland אין חלון קיצורי דרך. הקצה את המקשים בקובץ התצורה של Hyprland." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (לפני Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 ואילך)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "אני מסכים/ה ל<0>מדיניות הפרטיות" @@ -17576,7 +17672,7 @@ msgstr "שיניתי את דעתי" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "אני מבין/ה" @@ -18000,7 +18096,7 @@ msgstr "בערוץ" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "במצב \"מערכת הפעלה\", המילונים המפורטים לעיל הם בגדר המלצה. {PRODUCT_NAME} יעביר את תגי השפה האלה לבודק האיות של מערכת ההפעלה שלך כאשר הדבר נתמך. הוא יתעלם מתגים שאינו מזהה." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "בדפדפן, דיבור בלחיצה (push-to-talk) פועל רק כשהכרטיסייה של {PRODUCT_NAME} ממוקדת. התקן את אפליקציית המחשב כדי להשתמש בדיבור בלחיצה בכל המערכת." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "קלט" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "לא ניתן היה להפעיל גישת קלט באופן אוטומטי. שנה זאת ידנית, ולאחר מכן צא לגמרי מ-{productName} והפעל מחדש כדי שקיצורי הדרך יוכלו להשתמש בה." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "לא ניתן היה להפעיל גישת קלט. נסה שוב מהגדרות {settingsMenuName}, ולאחר מכן צא לגמרי מ-{productName} והפעל מחדש." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "נדרשת גישה לקלט עבור קיצורי מקלדת ועכבר כלל-מערכתיים ב-Wayland. לאחר שינוי גישת הקלט, צא לגמרי מ-{productName} והפעל מחדש כדי שקיצורי הדרך יוכלו להשתמש בה." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "גישת הקלט שונתה. צא לגמרי מ-{productName} והפעל מחדש כדי שקיצורי דרך כלל-מערכתיים יוכלו להשתמש בה." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "קלט ופלט" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "בקשת הרשאה לא תקינה" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "הרשאות בוט לא תקינות." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "קוד שגוי או שפג תוקפו. נסה שוב." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "כתובת הניתוב מחדש (redirect_uri) אינה תקינה" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "קישור ההזמנה מוסתר בזמן שיתוף:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "קישור ההזמנה מוסתר בזמן שיתוף" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "קפוץ אל {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "קפוץ אל {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "קפיצה לשיחה הנוכחית" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "קפוץ לערוץ המקושר" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "קפוץ להודעה המקושרת" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "קפוץ להודעה של {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "קפוץ להודעה ב-{labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "שמור עליהם במקום בטוח. יצירה מחדש תשבור אינטגרציות קיימות." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "המשך לשדר לזמן קצר לאחר השחרור." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "מקלדת" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "לא ניתן היה להפעיל את הגישה למקלדת. קיצורי הדרך פועלים רק כאשר {productName} ממוקד." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "אפשר לקהילה לנהל את עצמה" msgid "Let the member hear others in this community again" msgstr "מתן אפשרות לחבר לשמוע שוב אחרים בקהילה זו" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "מאפשר ל-{productName} לקרוא את המקלדת שלך ישירות. המערכת שלך כבר מאפשרת זאת." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "מאפשר ל-CSS מותאם אישית לחשוף אזורים שקופים בחלון לאחר הפעלה מחדש של אפליקציית המחשב." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "עיצוב קישורים" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "הקישור אינו זמין" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "מראה ותחושה" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "נראה שאתה משתמש ב-{0}. הוספת עיבוד של {PRODUCT_NAME} עלולה לגרום לעיוות או להגברת יתר. קלט ישיר מעביר את השמע שלך ללא שינוי." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "פריסת הודעות" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "קישור להודעה" @@ -21332,7 +21416,7 @@ msgstr "מיקרופון" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "בדיקת מיקרופון" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "שונות" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "חסר client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "אפשרויות נוספות" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "אפשרויות נוספות לקיצורי דרך" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "אין הרשאת {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "אין גישה" @@ -22984,7 +23068,7 @@ msgstr "לא נמצאו התאמות" msgid "No mentions yet" msgstr "אין אזכורים עדיין" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "לא נמצא מיקרופון" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "לא נמצאו הגדרות" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "לא הוגדר קיצור דרך" @@ -23358,6 +23442,11 @@ msgstr "לא מספר שלם" msgid "Not accepted" msgstr "לא התקבלה" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "לא הוקצה" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "פתח את {host} בדפדפן שלך." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "פתח את {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "פתח את {labelText} בהגדרות" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "קישור לדף" @@ -25611,11 +25700,11 @@ msgstr "נוכחות" msgid "Press {shortcut} to react" msgstr "הקש {shortcut} כדי להגיב" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "לחץ על מקש, לחצן עכבר או לחצן בקר משחק כדי להגדיר את הקיצור. מקש Escape מבטל." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "יש ללחוץ על קיצור דרך…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "מקש 'דיבור בלחיצה'" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "דיבור בלחיצה" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "דיבור בלחיצה (מוגבל)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "לדיבור בלחיצה עדיין אין מקש כלל-מערכתי. כדי להגדיר מקש, עבור אל הגדרות > יישומים > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "לדיבור בלחיצה עדיין אין מקש כלל-מערכתי. כדי להגדיר מקש, עבור אל הגדרות מערכת > מקלדת > קיצורי דרך > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "לדיבור בלחיצה עדיין אין מקש כלל-מערכתי. כדי להגדיר מקש, פתח את הגדרות קיצורי המקלדת של שולחן העבודה שלך." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "דיבור בלחיצה אינו פעיל בקלט ישיר. עבור לקול ממוקד או למצב מותאם אישית כדי להשתמש בו." @@ -26293,21 +26397,51 @@ msgstr "דיבור בלחיצה אינו פעיל בקלט ישיר. עבור ל msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "הדיבור בלחיצה מופעל. החזק את {pushToTalkHint} כדי לדבר." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "הדיבור בלחיצה מופעל. החזק את מקש הדיבור בלחיצה כדי לדבר." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "דיבור בלחיצה פועל רק כאשר {productName} ממוקד, כי שולחן העבודה שלך לא הגדיר קיצורי דרך כלל-מערכתיים." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "דיבור בלחיצה פועל רק כש-{productName} ממוקד, עד שההרשאה {permissionName} מופעלת. לאחר שינוי ההרשאה, יש לצאת מ-{productName} לחלוטין ולהפעיל אותו מחדש כדי שקיצורי מקשים כלל-מערכתיים יוכלו להשתמש בה." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "עד שתגדיר קיצורי דרך כלל-מערכתיים, דיבור בלחיצה פועל רק כאשר {productName} ממוקד." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "דיבור בלחיצה עובד רק כשהכרטיסייה בדפדפן ממוקדת." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "דיבור בלחיצה ישתמש במקש שהגדרת ב-{productName}. הגדר מקש קודם, אחרת המיקרופון שלך יפעל לפי פעילות קולית." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "דיבור בלחיצה לא יבטל את השתקת המיקרופון כל עוד השמע שלך מושתק. בטל את השתקת השמע כדי לדבר." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "דיבור בלחיצה פועל מחוץ ל-{productName} רק אחרי שתקצה לו מקש בקובץ התצורה של Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "דיבור בלחיצה פועל מחוץ ל-{productName} רק אחרי שתקצה לו מקש בקובץ התצורה של Hyprland. הוסף את השורה עבור גרסת Hyprland שלך והחלף את KEY במקש הרצוי." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "JSON גולמי של ההטמעה" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "הפעלה מחדש של קיצור דרך מובנה" @@ -26565,13 +26699,6 @@ msgstr "אזכורים אחרונים" msgid "Recent searches" msgstr "חיפושים אחרונים" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "בדוק שוב" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "מומלץ" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "מומלץ. מנקה את המיקרופון שלך לדיבור ברור." @@ -26600,6 +26727,11 @@ msgstr "התחבר מחדש" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "ההתחברות מחדש הצליחה. בחר מסך שוב אם ברצונך להמשיך את השידור הקודם." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "מתחבר מחדש לשולחן העבודה שלך…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "מתחבר מחדש…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "הקלט קיצור דרך" @@ -26851,7 +26983,7 @@ msgstr "יחסי" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "השהיית שחרור" @@ -27739,13 +27871,13 @@ msgstr "נשלח שוב" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "איפוס" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "איפוס \"{label}\" לברירת מחדל" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "איפוס לערכים בפועל" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "איפוס לברירת מחדל" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "בחר את החשבון שברצונך להתחבר איתו במכשיר החדש שלך." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "בחר מצב עיבוד קולי" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "שולח אודיו גולמי מהמיקרופון ללא עיבוד." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "שולח את האודיו שלך ללא שינוי. מומלץ אם אתה משתמש בתוכנת אודיו חיצונית." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "מוגדר על ידי קביעת התצורה של {preset}. בחירת ערך כאן תעבור ל'מותאם אישית'." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "הגדר קיצור דרך מותאם אישית" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "הגדר קיצור דרך מותאם אישית עבור \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "הגדרת אזור הקול ל-{region}" msgid "Set the voice region to automatic" msgstr "הגדרת אזור הקול לאוטומטי" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "הגדרה" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "הגדרות" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "קישור להגדרות" @@ -30330,8 +30467,8 @@ msgstr "קודי קיצור" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "החלף לפריסה נוחה" msgid "Switch to dense layout" msgstr "החלף לפריסה דחוסה" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "החלף לקלט ישיר" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "התקנה מנוהלת על ידי המערכת" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "קיצורי דרך כלל-מערכתיים" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "לא ניתן היה להגדיר קיצורי דרך כלל-מערכתיים." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "כלל-מערכתי: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "כלל-מערכתי: לא הוקצה" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "הדף שחיפשת אינו קיים או הועבר." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "כתובת ה-redirect_uri שצוינה אינה רשומה עבור האפליקציה הזו." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "ה-redirect_uri שסופק חייב להשתמש ב-HTTP או ב-HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "האנימציה הזו ארוכה מדי או גדולה מדי לחיתוך. נסה קובץ קצר או קטן יותר." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "האפליקציה הזו" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "פג תוקפו של הקובץ המצורף" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "הבוט הזה אינו ציבורי ולא ניתן להוסיף אותו על ידי משתמשים אחרים." @@ -34081,6 +34233,16 @@ msgstr "שיחה זו אינה זמינה עוד." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "גרסת שולחן העבודה הזו לא יכולה להתקין עדכונים בתוך {productName}. במקום זאת, הורד את הגרסה האחרונה של תוכנת ההתקנה לשולחן העבודה." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "שולחן עבודה זה אינו תומך בקיצורי דרך כלל-מערכתיים. דיבור בלחיצה פועל רק כאשר {productName} ממוקד." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "שולחן עבודה זה אינו תומך בקיצורי דרך כלל-מערכתיים. קיצורי הדרך עדיין פועלים כאשר {productName} ממוקד." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "זהו מפתח הגישה האחרון שלך, ומפתחות גישה msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "זהו מפתח הגישה האחרון שלך. לאחר שיימחק, לא תוכל להשתמש במפתח גישה כשלב האימות השני שלך." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "המקש הזה פועל כאשר {productName} ממוקד. את המקש הכלל-מערכתי מגדיר שולחן העבודה שלך, תחת {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "ערוץ קישור זה מסומן ככולל תוכן למבוגרים ועשוי להכיל חומר שאינו הולם עבור חלק מהמשתמשים." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "הקישור הזה אינו זמין לחשבון שלך." @@ -34778,6 +34945,21 @@ msgstr "שורת כותרת" msgid "Title bar platform" msgstr "פלטפורמת שורת הכותרת" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "כדי לשנות את המקשים, עבור אל הגדרות > יישומים > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "כדי לשנות את המקשים, עבור אל הגדרות מערכת > מקלדת > קיצורי דרך > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "כדי לשנות את המקשים, פתח את הגדרות קיצורי המקלדת של שולחן העבודה שלך." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "כדי ליצור webhook, יש לפתוח את <0>{channelWebhooksSettingsPath}. עדיין ניתן לערוך ולארגן כאן את כל ה-webhooks הקיימים." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "לא ידוע" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "אפליקציה לא ידועה, סשן שפג תוקפו או בקשת הרשאה שגויה." @@ -36410,6 +36592,11 @@ msgstr "עד 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "עד 720p ב-30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "עדכן את {productName} כדי להשתמש בקיצורי דרך כלל-מערכתיים ב-Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "השתמש בערכת נושא כהה בכל האפליקציה" msgid "Use default" msgstr "השתמש בברירת מחדל" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "השתמש בגישה ישירה להתקן הקלט" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "משתתפים בשיחה הקולית" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "עיבוד קולי" @@ -38264,7 +38456,7 @@ msgstr "ביטול השתקת שמע" msgid "Voice unmute" msgstr "ביטול השתקת מיקרופון" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "הקול משתמש בהתקן הפלט של המערכת שלך בדפדפן זה. שנה אותו בהגדרות המערכת שלך." @@ -38278,7 +38470,7 @@ msgstr "בוטל" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "ממתין שתוקף מצב איטי באחד או יותר מהערו msgid "Waiting for you to..." msgstr "מחכה לך כדי..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "ממתין לשולחן העבודה שלך…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "אילו שיחות" msgid "Which rule was broken?" msgstr "איזה כלל הופר?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "כאשר דיבור בלחיצה מופעל, קיצור הדרך שולט במיקרופון שלך, ולכן כפתור ההשתקה מושבת." @@ -39094,6 +39291,11 @@ msgstr "ללא הרשאה זו, נדרש דיבור בלחיצה." msgid "Wordmark" msgstr "סימן מילולי" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "פועל כאשר {productName} ממוקד" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "שינית את התג שלך בזמן שהיית ב-{PREMIUM_PRODUCT_NAME}. הוא ישתנה באופן אקראי כשתוקף ה-{PREMIUM_PRODUCT_NAME} שלך יפוג, אלא אם כן הוא יתחדש." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "אין לך גישה לערוץ שבו נשלחה ההודעה הזו." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "תוקף המנוי שלך ל-{premiumProductName} פג. איבדת את כל ההטבות של {premiumProductName}. הפעל מחדש את המנוי שלך כדי לקבל גישה שוב." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "הסשן שלך ב-{productName} פג. היכנס שוב כדי להמשיך ולאשר את האפליקציה הזו." @@ -40144,6 +40346,16 @@ msgstr "הקהילה שלך צריכה לפחות {minMemberCount, plural, one { msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "כרטיס האשראי שלך נבדק. אם הכרטיס תקף, תוכן למבוגרים ייפתח בקרוב. באפשרותך לסגור את הכרטיסייה הזו ולחזור לאפליקציה." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "שולחן העבודה שלך לא הגדיר קיצורי דרך כלל-מערכתיים." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "שולחן העבודה שלך מפעיל את הקיצורים האלה גם כש-{productName} אינו ממוקד." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/hi/messages.po b/fluxer_app/src/features/i18n/locales/hi/messages.po index 3bc3f2f19..8eef5340a 100644 --- a/fluxer_app/src/features/i18n/locales/hi/messages.po +++ b/fluxer_app/src/features/i18n/locales/hi/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# चैनल इसे फ़ॉलो करत msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# कम्युनिटी मिली} other {# कम्युनिटी मिलीं}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# कम्युनिटी फ्लक्स कैपेसिटर में खराबी के कारण अस्थायी रूप से अनुपलब्ध है।} other {# कम्युनिटी फ्लक्स कैपेसिटर में खराबी के कारण अस्थायी रूप से अनुपलब्ध हैं।}}" @@ -1708,6 +1708,31 @@ msgstr "जब आप अपने कंप्यूटर पर होते msgid "{productName} Bug Hunter" msgstr "{productName} बग हंटर" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} आपके डेस्कटॉप से कह सकता है कि {productName} के फ़ोकस में न होने पर भी उसके शॉर्टकट चलाए। आपका डेस्कटॉप आपसे पुष्टि करने को कहेगा और आपको कुंजियाँ चुनने देगा।" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} अब आपका कीबोर्ड नहीं पढ़ सकता है, इसलिए यह इसके बजाय X11 के माध्यम से शॉर्टकट सुनता है।" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} अब आपका कीबोर्ड नहीं पढ़ सकता है, इसलिए इसके बजाय आपके डेस्कटॉप के शॉर्टकट का उपयोग किया जा रहा है।" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} अब आपका कीबोर्ड नहीं पढ़ सकता है। शॉर्टकट केवल तभी काम करते हैं जब {productName} फ़ोकस में हो।" + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} अपने शॉर्टकट Hyprland के साथ रजिस्टर कर सकता है, ताकि वे {productName} के फ़ोकस में न होने पर भी चलें। फिर आप अपने Hyprland कॉन्फ़िगरेशन में कुंजियाँ बाइंड करते हैं।" + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} को आपके डिवाइस दिखाने के लिए कैमरे का एक्सेस चाहिए।" #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} को आपके डिवाइस दिखाने के लिए माइक्रोफ़ोन का एक्सेस चाहिए।" @@ -1837,6 +1862,11 @@ msgstr "{productName} अब {host} पर उपलब्ध है। वह msgid "{productName} Partner" msgstr "{productName} पार्टनर" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} ने अपने शॉर्टकट Hyprland के साथ रजिस्टर कर दिए हैं। {productName} के फ़ोकस में न होने पर इनका उपयोग करने के लिए इन्हें अपने Hyprland कॉन्फ़िगरेशन में बाइंड करें।" + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "एक प्रोफ़ाइल जो सबसे अलग दिखे" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "जब बॉट स्कोप ही एकमात्र स्कोप न हो, तो redirect_uri ज़रूरी है।" @@ -2847,7 +2877,7 @@ msgstr "सिर्फ़ सभी खुले DM में" msgid "Across every community you're in" msgstr "आपकी हर कम्युनिटी में" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "कार्रवाई" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "गतिविधि शेयरिंग" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "गतिविधि थ्रेशोल्ड" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "अपने अकाउंट में सुरक्षा की एक और लेयर जोड़ें" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "एक और कस्टम शॉर्टकट जोड़ें" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "पता" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "हर सेटिंग खुद एडजस्ट करें: शोर दबाना, इको कैंसलेशन और गेन।" @@ -3696,11 +3726,11 @@ msgstr "{everyoneMention} और {hereMention} की अनुमति दे msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "{PRODUCT_NAME} को {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME} में अपने कैमरे का उपयोग करने की अनुमति दें।" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "{PRODUCT_NAME} को {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME} में अपने माइक्रोफ़ोन का उपयोग करने की अनुमति दें।" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "{PRODUCT_NAME} को अपने माइक्रोफ़ोन का इस्तेमाल करने की अनुमति दें। अपने ब्राउज़र के एड्रेस बार या सेटिंग्स में जाँच करें।" @@ -3803,7 +3833,7 @@ msgstr "जिन सदस्यों के पास {readMessageHistoryPerm msgid "Allow mentions for this role" msgstr "इस रोल के लिए मेंशन की अनुमति दें" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "माइक्रोफ़ोन का एक्सेस दें" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "अधिकृत नहीं किया जा सका" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "अनुमति नहीं दी जा सकी। फिर से कोशिश करें।" @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "भाषा अपने आप पहचानें" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "गतिविधि थ्रेशोल्ड अपने आप सेट करें" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "बॉट अनुमतियाँ" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "बॉट अनुमतियाँ ऋणात्मक नहीं हो सकतीं।" @@ -6869,6 +6899,11 @@ msgstr "ग्रुप का उपनाम बदलें" msgid "Change icon" msgstr "आइकॉन बदलें" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "सिस्टम सेटिंग में बदलें" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "चैनल एक्सेस" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "चैनल का एक्सेस नहीं है" @@ -7294,7 +7329,7 @@ msgstr "चैनल आईडी क्लिपबोर्ड पर कॉ #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "चैनल लिंक" @@ -7462,6 +7497,11 @@ msgstr "चैट इनपुट" msgid "Chat shortcuts" msgstr "चैट शॉर्टकट" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "फिर से जाँचें" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "अपने सिस्टम पिकर में एक विं msgid "Choose an account" msgstr "कोई अकाउंट चुनें" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "एक क्रिया चुनें।" @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "बैकग्राउंड चुनें" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "guild_id या channel_id में से कोई एक चुनें, दोनों नहीं।" @@ -7990,7 +8030,7 @@ msgstr "स्पष्ट बातचीत के लिए आपके म #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "कलर पिकर खोलने और नया रंग चु msgid "Click to react" msgstr "रिएक्ट करने के लिए क्लिक करें" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "रिकॉर्ड करने के लिए क्लिक करें" @@ -9110,7 +9150,7 @@ msgstr "पुष्टि" msgid "Confirmation or double-click for community voice joins" msgstr "कम्युनिटी वॉइस जॉइन के लिए पुष्टि या डबल-क्लिक" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "{conflictLabel} से टकराता है" @@ -9127,7 +9167,7 @@ msgstr "कैमरा या वर्चुअल कैप्चर डि msgid "Connect a display, then try again." msgstr "एक डिस्प्ले कनेक्ट करें, फिर दोबारा प्रयास करें।" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "माइक्रोफ़ोन कनेक्ट करें और फिर से प्रयास करें।" @@ -9700,6 +9740,21 @@ msgstr "इनवाइट लिंक कॉपी करें" msgid "Copy invite URL" msgstr "इनवाइट यूआरएल कॉपी करें" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "लाइनें कॉपी करें" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "hyprland.conf के लिए लाइनें कॉपी करें" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "hyprland.lua के लिए लाइनें कॉपी करें" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "कस्टम" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "कस्टम चयन" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "कस्टम शॉर्टकट कार्रवाई" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "कस्टम शॉर्टकट विकल्प" @@ -11466,7 +11521,7 @@ msgstr "डिफ़ॉल्ट शॉर्टकट" msgid "Default translator" msgstr "डिफ़ॉल्ट अनुवादक" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "डिफ़ॉल्ट:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "सेव किया गया मीडिया मिटाएँ" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "शॉर्टकट मिटाएँ" @@ -12102,13 +12157,13 @@ msgstr "डेवलपर स्थिति" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "डेवलपर टूल" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "चैनल हेडर में स्टाफ़ बैज से डेवलपर टूल उपलब्ध हैं।" @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "बैकएंड प्रीमियम पर्क बंद करें" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "बिल्ट-इन शॉर्टकट बंद करें" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "स्क्रीन शेयर पिक्चर-इन-पिक्चर पॉपआउट बंद करें?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "शॉर्टकट बंद करें" @@ -12888,7 +12943,7 @@ msgstr "इसके बजाय दान करें" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "डाउनलोड बटन" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "डेस्कटॉप ऐप डाउनलोड करें" @@ -13204,6 +13259,11 @@ msgstr "डच" msgid "e.g., YubiKey, iPhone, work computer" msgstr "जैसे: YubiKey, iPhone, ऑफ़िस का कंप्यूटर" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "हर कुंजी केवल एक सिस्टम-व्यापी शॉर्टकट चला सकती है।" + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "इनवाइट लिंक एडिट करें" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "कीबोर्ड शॉर्टकट एडिट करें" @@ -13425,7 +13485,7 @@ msgstr "सेव किया गया मीडिया फ़ॉर्म msgid "Edit search engine" msgstr "सर्च इंजन एडिट करें" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "शॉर्टकट एडिट करें" @@ -13753,20 +13813,16 @@ msgstr "खाली एडिट" msgid "Empty favorites" msgstr "पसंदीदा खाली हैं" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "चालू करें" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "{permissionName} अनुमति चालू करें" @@ -13876,11 +13932,6 @@ msgstr "हार्डवेयर एक्सीलरेशन चालू msgid "Enable hardware acceleration?" msgstr "हार्डवेयर एक्सीलरेशन चालू करें?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "वेयलैंड पर सिस्टम-वाइड शॉर्टकट इस्तेमाल करने के लिए इनपुट एक्सेस चालू करें। इनपुट एक्सेस बदलने के बाद {productName} को पूरी तरह बंद करके फिर से शुरू करें, ताकि शॉर्टकट इसका इस्तेमाल कर सकें।" - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "शिफ़्ट दबाकर बड़ा करना चालू करें" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "शॉर्टकट चालू करें" @@ -14357,7 +14408,7 @@ msgstr "पूरे सिस्टम का ऑडियो" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "एंट्रेंस साउंड" @@ -14467,7 +14518,7 @@ msgstr "शाम का समय" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "यह आपके माइक्रोफ़ोन की आवाज़ को बराबर करता है ताकि आपकी आवाज़ बहुत धीमी न लगे।" @@ -15682,6 +15733,26 @@ msgstr "केवल ब्रिटिश लोगों के लिए..." msgid "For me" msgstr "मेरे लिए" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "पुश-टू-म्यूट के लिए Ctrl, Alt या Shift के बिना एक ही कुंजी का उपयोग करें। कुंजी संयोजन इस्तेमाल करने पर पहले मुख्य कुंजी छोड़ें, नहीं तो माइक्रोफ़ोन म्यूट रह सकता है।" + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "पुश-टू-टॉक के लिए Ctrl, Alt या Shift के बिना एक ही कुंजी का उपयोग करें। कुंजी संयोजन इस्तेमाल करने पर पहले मुख्य कुंजी छोड़ें, नहीं तो माइक्रोफ़ोन चालू रह सकता है।" + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "इस शॉर्टकट के लिए Ctrl, Alt या Shift के बिना एक ही कुंजी का उपयोग करें। कुंजी संयोजन इस्तेमाल करने पर पहले मुख्य कुंजी छोड़ें, नहीं तो शॉर्टकट सक्रिय रह सकता है।" + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "वॉइस गतिविधि प्राथमिकता के लिए Ctrl, Alt या Shift के बिना एक ही कुंजी का उपयोग करें। कुंजी संयोजन इस्तेमाल करने पर पहले मुख्य कुंजी छोड़ें, नहीं तो प्राथमिकता चालू रह सकती है।" + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "आपकी सुरक्षा के लिए, आगे बढ़ने से पहले कृपया पुष्टि करें कि यह आप ही हैं।" @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "ग्लोबल प्रोफ़ाइल" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "ग्लोबल शॉर्टकट" @@ -17434,6 +17505,11 @@ msgstr "ऐप्स का चयन करने के लिए Shift दब msgid "Hold to record. Drag up to lock, or release to send." msgstr "रिकॉर्ड करने के लिए दबाए रखें। लॉक करने के लिए ऊपर खींचें या भेजने के लिए छोड़ दें।" +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Shift, Ctrl या Alt दबाए रखने पर यह शॉर्टकट काम नहीं करता। अपनी सिस्टम सेटिंग्स में वैकल्पिक कुंजियाँ जोड़ें।" + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "बढ़िया! सूचनाएं चालू हैं" msgid "Hyperlink" msgstr "हाइपरलिंक" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland में कोई शॉर्टकट डायलॉग नहीं है। अपने Hyprland संस्करण वाली लाइनें अपने Hyprland कॉन्फ़िगरेशन में जोड़ें और KEY को अपनी पसंद की कुंजी से बदलें।" + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland में कोई शॉर्टकट डायलॉग नहीं है। अपने Hyprland कॉन्फ़िगरेशन में कुंजियाँ बाइंड करें।" + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (Hyprland 0.55 से पहले)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 और बाद के संस्करण)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "मैं <0>गोपनीयता नीति से सहमत हूँ" @@ -17576,7 +17672,7 @@ msgstr "मैंने अपना मन बदल लिया" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "मैं समझ गया/गई" @@ -18000,7 +18096,7 @@ msgstr "चैनल" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "\"ऑपरेटिंग सिस्टम\" मोड में, ऊपर दी गई डिक्शनरी सिर्फ़ सलाह के तौर पर हैं। सपोर्ट होने पर {PRODUCT_NAME} ये भाषा टैग आपके OS स्पेलचेकर को भेजेगा। जिन टैग को वह नहीं पहचानता, उन्हें अनदेखा कर दिया जाता है।" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "ब्राउज़र में, पुश-टू-टॉक तभी काम करता है जब {PRODUCT_NAME} टैब फ़ोकस में हो। सिस्टम-वाइड पुश-टू-टॉक के लिए डेस्कटॉप ऐप इंस्टॉल करें।" @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "इनपुट" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "इनपुट एक्सेस अपने आप चालू नहीं किया जा सका। इसे मैन्युअल रूप से बदलें, फिर {productName} को पूरी तरह से बंद करके फिर से चालू करें ताकि शॉर्टकट इसका इस्तेमाल कर सकें।" - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "इनपुट एक्सेस चालू नहीं किया जा सका। {settingsMenuName} सेटिंग्स से फिर से कोशिश करें, फिर {productName} को पूरी तरह से बंद करके रीस्टार्ट करें।" - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "वेनलैंड पर सिस्टम-वाइड कीबोर्ड और माउस शॉर्टकट के लिए इनपुट एक्सेस ज़रूरी है। इनपुट एक्सेस बदलने के बाद, शॉर्टकट का इस्तेमाल करने के लिए {productName} को पूरी तरह से बंद करके फिर से चालू करें।" - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "इनपुट एक्सेस बदल दिया गया है। {productName} को पूरी तरह से बंद करें और फिर से शुरू करें ताकि सिस्टम-वाइड शॉर्टकट इसका उपयोग कर सकें।" - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "इनपुट और आउटपुट" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "अमान्य ऑथराइज़ेशन अनुरोध" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "अमान्य बॉट अनुमतियाँ।" @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "कोड अमान्य है या उसकी समय-सीमा समाप्त हो चुकी है। फिर से कोशिश करें।" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "अमान्य redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "शेयर करते समय इनवाइट लिंक छिपा हुआ है:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "शेयर करते समय इनवाइट लिंक छिपाया गया" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "{displayName} पर जाएं" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "{labelText} पर जाएं" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "मौजूदा कॉल पर जाएं" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "लिंक किए गए चैनल पर जाएं" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "लिंक किए गए मैसेज पर जाएं" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "{authorNickname} के मैसेज पर जाएँ" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "{labelText} में मैसेज पर जाएं" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "इन्हें सुरक्षित रखें। फिर से जनरेट करने पर मौजूदा इंटिग्रेशन टूट जाएँगे।" #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "रिलीज़ करने के बाद भी थोड़ी देर तक ट्रांसमिट करते रहें।" @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "कीबोर्ड" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "कीबोर्ड एक्सेस शुरू नहीं हो सका। शॉर्टकट केवल तभी काम करते हैं जब {productName} फ़ोकस में हो।" + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "कम्युनिटी को खुद मॉडरेट कर msgid "Let the member hear others in this community again" msgstr "सदस्य को इस कम्युनिटी में फिर से दूसरों को सुनने दिया" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "{productName} को आपका कीबोर्ड सीधे पढ़ने देता है। आपका सिस्टम पहले से इसकी अनुमति देता है।" + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "डेस्कटॉप ऐप को रीस्टार्ट करने के बाद कस्टम CSS पारदर्शी विंडो क्षेत्र दिखा सकता है।" @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "लिंक स्टाइल" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "लिंक उपलब्ध नहीं है" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "दिखावट और अनुभव" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "लगता है आप {0} का उपयोग कर रहे हैं। {PRODUCT_NAME} की प्रोसेसिंग को इसके ऊपर रखने से पंपिंग या डिस्टॉर्शन हो सकता है। डायरेक्ट इनपुट आपके ऑडियो को बिना किसी बदलाव के पास करता है।" @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "मैसेज लेआउट" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "मैसेज लिंक" @@ -21332,7 +21416,7 @@ msgstr "माइक" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "माइक टेस्ट" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "अन्य" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "client_id मौजूद नहीं है" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "ज़्यादा विकल्प" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "ज़्यादा शॉर्टकट विकल्प" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "{sendMessagesPermission} की अनुमति नहीं है" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "कोई एक्सेस नहीं" @@ -22984,7 +23068,7 @@ msgstr "कोई मिलान नहीं मिला" msgid "No mentions yet" msgstr "अभी तक कोई मेंशन नहीं है" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "कोई माइक्रोफ़ोन नहीं मिला" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "कोई सेटिंग्स नहीं मिलीं" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "कोई शॉर्टकट सेट नहीं है" @@ -23358,6 +23442,11 @@ msgstr "पूर्ण संख्या नहीं है" msgid "Not accepted" msgstr "स्वीकार नहीं किया गया" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "असाइन नहीं किया गया" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "अपने ब्राउज़र में {host} खोलें।" #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "{labelText} खोलें" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "सेटिंग्स में {labelText} खोलें" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "पेज डाउन" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "पेज लिंक" @@ -25611,11 +25700,11 @@ msgstr "उपस्थिति" msgid "Press {shortcut} to react" msgstr "रिएक्ट करने के लिए {shortcut} दबाएँ" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "बाइंड करने के लिए कोई कुंजी, माउस बटन या गेमपैड बटन दबाएँ। रद्द करने के लिए एस्केप दबाएँ।" -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "शॉर्टकट दबाएं…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "पुश टू टॉक कुंजी" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "पुश-टू-टॉक" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "पुश-टू-टॉक (सीमित)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "पुश-टू-टॉक के लिए अभी तक कोई सिस्टम-व्यापी कुंजी नहीं है। इसे सेट करने के लिए, सेटिंग्स > ऐप्स > {productName} पर जाएं।" + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "पुश-टू-टॉक के लिए अभी तक कोई सिस्टम-व्यापी कुंजी नहीं है। इसे सेट करने के लिए, सिस्टम सेटिंग्स > कीबोर्ड > शॉर्टकट > {productName} पर जाएं।" + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "पुश-टू-टॉक के लिए अभी तक कोई सिस्टम-व्यापी कुंजी नहीं है। इसे सेट करने के लिए, अपने डेस्कटॉप की कीबोर्ड शॉर्टकट सेटिंग्स खोलें।" + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "सीधा इनपुट मोड में पुश-टू-टॉक को अनदेखा किया जाता है। इसे इस्तेमाल करने के लिए फ़ोकस्ड वॉइस या कस्टम पर स्विच करें।" @@ -26293,21 +26397,51 @@ msgstr "सीधा इनपुट मोड में पुश-टू-टॉ msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "पुश-टू-टॉक चालू है। बोलने के लिए {pushToTalkHint} दबाए रखें।" +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "पुश-टू-टॉक चालू है। बोलने के लिए अपनी पुश-टू-टॉक कुंजी दबाकर रखें।" + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "पुश-टू-टॉक केवल तभी काम करता है जब {productName} फ़ोकस में हो क्योंकि आपके डेस्कटॉप ने सिस्टम-व्यापी शॉर्टकट सेट अप नहीं किए हैं।" + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "{permissionName} सक्षम होने तक पुश-टू-टॉक केवल तभी काम करता है जब {productName} फ़ोकस में हो। अनुमति बदलने के बाद, {productName} को पूरी तरह से बंद करें और पुनरारंभ करें ताकि सिस्टम-व्यापी हॉटकी इसका उपयोग कर सकें।" +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "पुश-टू-टॉक केवल तभी काम करता है जब {productName} फ़ोकस में हो, जब तक आप सिस्टम-व्यापी शॉर्टकट सेट अप नहीं कर लेते।" + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "पुश-टू-टॉक तभी काम करता है जब ब्राउज़र टैब फ़ोकस में हो।" +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "पुश-टू-टॉक उस कुंजी का उपयोग करेगा जो आपने {productName} में सेट की है। पहले एक कुंजी सेट करें, नहीं तो आपका माइक्रोफ़ोन वॉइस गतिविधि का उपयोग करेगा।" + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "जब तक आप डीफ़ेन हैं, पुश-टू-टॉक आपके माइक्रोफ़ोन को अनम्यूट नहीं करेगा। बात करने के लिए पहले खुद को अनडीफ़ेन करें।" +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "पुश-टू-टॉक {productName} के बाहर तभी काम करता है जब आप इसे अपने Hyprland कॉन्फ़िगरेशन में बाइंड करें।" + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "पुश-टू-टॉक {productName} के बाहर तभी काम करता है जब आप इसे अपने Hyprland कॉन्फ़िगरेशन में बाइंड करें। अपने Hyprland संस्करण वाली लाइन जोड़ें और KEY को अपनी पसंद की कुंजी से बदलें।" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "रॉ एम्बेड JSON" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "बिल्ट-इन शॉर्टकट फिर से चालू करें" @@ -26565,13 +26699,6 @@ msgstr "हाल के मेंशन" msgid "Recent searches" msgstr "हाल की खोजें" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "फिर से जांचें" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "सुझाया गया" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "सुझाया गया। स्पष्ट आवाज़ के लिए आपके माइक ऑडियो को साफ़ करता है।" @@ -26600,6 +26727,11 @@ msgstr "फिर से कनेक्ट करें" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "फिर से कनेक्ट हो गया। अगर आप अपनी पिछली स्ट्रीम फिर से शुरू करना चाहते हैं, तो दोबारा स्क्रीन चुनें।" +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "आपके डेस्कटॉप से फिर से कनेक्ट हो रहा है…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "फिर से कनेक्ट हो रहा है…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "शॉर्टकट रिकॉर्ड करें" @@ -26851,7 +26983,7 @@ msgstr "रिलेटिव" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "रिलीज़ में देरी" @@ -27739,13 +27871,13 @@ msgstr "फिर से भेजा गया" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "रीसेट करें" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "\"{label}\" को बिल्ट-इन पर रीसेट करें" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "वास्तविक मानों पर रीसेट करें" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "बिल्ट-इन पर रीसेट करें" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "वह खाता चुनें जिससे आप अपने नए डिवाइस पर साइन इन करना चाहते हैं।" #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "वॉइस प्रोसेसिंग मोड चुनें" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "बिना किसी प्रोसेसिंग के रॉ माइक ऑडियो भेजता है।" #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "आपका ऑडियो बिना किसी बदलाव के भेजता है। अगर आप बाहरी ऑडियो सॉफ़्टवेयर का उपयोग कर रहे हैं तो यह सबसे अच्छा है।" @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "{preset} प्रीसेट द्वारा सेट किया गया। यहाँ कोई मान चुनने पर कस्टम पर स्विच हो जाएगा।" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "कस्टम शॉर्टकट सेट करें" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "\"{label}\" के लिए कस्टम शॉर्टकट सेट करें" @@ -30066,6 +30198,11 @@ msgstr "वॉइस क्षेत्र को {region} पर सेट क msgid "Set the voice region to automatic" msgstr "वॉइस क्षेत्र ऑटोमैटिक पर सेट किया गया" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "सेट अप करें" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "सेटिंग्स" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "सेटिंग्स लिंक" @@ -30330,8 +30467,8 @@ msgstr "शॉर्टकोड" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "आरामदायक लेआउट पर स्विच कर msgid "Switch to dense layout" msgstr "सघन लेआउट पर स्विच करें" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "सीधे इनपुट पर स्विच करें" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "सिस्टम-प्रबंधित इंस्टॉल" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "सिस्टम-व्यापी शॉर्टकट" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "सिस्टम-व्यापी शॉर्टकट सेट अप नहीं किए जा सके।" + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "सिस्टम-व्यापी: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "सिस्टम-व्यापी: असाइन नहीं किया गया" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "आप जिस पेज को ढूंढ रहे हैं वह मौजूद नहीं है या उसे कहीं और ले जाया गया है।" #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "दिया गया redirect_uri इस ऐप्लिकेशन के लिए रजिस्टर नहीं है।" #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "दिए गए redirect_uri में HTTP या HTTPS का इस्तेमाल होना चाहिए।" @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "यह एनीमेशन क्रॉप करने के लिए बहुत लंबा या बहुत बड़ा है। एक छोटी या हल्की फ़ाइल आज़माएँ।" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "यह ऐप्लिकेशन" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "इस अटैचमेंट की समय-सीमा खत्म हो गई है" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "यह बॉट सार्वजनिक नहीं है और इसे दूसरे यूज़र नहीं जोड़ सकते।" @@ -34081,6 +34233,16 @@ msgstr "यह बातचीत अब उपलब्ध नहीं है msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "यह डेस्कटॉप बिल्ड {productName} के अंदर अपडेट इंस्टॉल नहीं कर सकता। इसके बजाय, नया डेस्कटॉप इंस्टॉलर डाउनलोड करें।" +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "यह डेस्कटॉप सिस्टम-व्यापी शॉर्टकट प्रदान नहीं करता है। पुश-टू-टॉक केवल तभी काम करता है जब {productName} फ़ोकस में हो।" + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "यह डेस्कटॉप सिस्टम-व्यापी शॉर्टकट प्रदान नहीं करता है। {productName} फ़ोकस में होने पर शॉर्टकट अब भी काम करते हैं।" + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "यह आपकी आखिरी पासकी है और पा msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "यह आपकी आखिरी पासकी है। इसके मिट जाने के बाद आप पासकी का उपयोग अपने दूसरे कारक के रूप में नहीं कर पाएँगे।" +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "यह कुंजी तब काम करती है जब {productName} फ़ोकस में हो। सिस्टम-व्यापी कुंजी आपका डेस्कटॉप {settingsTabName} > {sectionName} में सेट करता है।" + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "यह लिंक चैनल वयस्क सामग्री के लिए चिह्नित है और ऐसी सामग्री खोल सकता है जो कुछ यूज़र के लिए अनुपयुक्त हो सकती है।" #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "यह लिंक आपके अकाउंट के लिए उपलब्ध नहीं है।" @@ -34778,6 +34945,21 @@ msgstr "टाइटल बार" msgid "Title bar platform" msgstr "टाइटल बार प्लेटफ़ॉर्म" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "कुंजियों को बदलने के लिए, सेटिंग्स > ऐप्स > {productName} पर जाएं।" + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "कुंजियों को बदलने के लिए, सिस्टम सेटिंग्स > कीबोर्ड > शॉर्टकट > {productName} पर जाएं।" + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "कुंजियाँ बदलने के लिए, अपने डेस्कटॉप की कीबोर्ड शॉर्टकट सेटिंग्स खोलें।" + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "वेबहुक बनाने के लिए, <0>{channelWebhooksSettingsPath} खोलें। आप अभी भी यहाँ सभी मौजूदा वेबहुक को एडिट और व्यवस्थित कर सकते हैं।" @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "अज्ञात" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "अज्ञात ऐप्लिकेशन, समाप्त हो चुका सेशन या अमान्य ऑथराइज़ेशन अनुरोध।" @@ -36410,6 +36592,11 @@ msgstr "4K/60fps तक" msgid "Up to 720p at 30 FPS" msgstr "30 FPS पर 720p तक" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Wayland पर सिस्टम-व्यापी शॉर्टकट का उपयोग करने के लिए {productName} को अपडेट करें।" + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "पूरे ऐप्लिकेशन में डार्क थ msgid "Use default" msgstr "डिफ़ॉल्ट इस्तेमाल करें" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "इनपुट डिवाइस के सीधे एक्सेस का उपयोग करें" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "वॉइस प्रतिभागी" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "वॉइस प्रोसेसिंग" @@ -38264,7 +38456,7 @@ msgstr "वॉइस अनडीफ़ेन" msgid "Voice unmute" msgstr "वॉइस अनम्यूट" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "यह आवाज़ इस ब्राउज़र में आपके सिस्टम आउटपुट डिवाइस का उपयोग करती है। इसे अपनी सिस्टम सेटिंग में बदलें।" @@ -38278,7 +38470,7 @@ msgstr "रद्द किया गया" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "एक या ज़्यादा चुने गए चैनलो msgid "Waiting for you to..." msgstr "हम इंतज़ार कर रहे हैं कि आप..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "आपके डेस्कटॉप का इंतज़ार हो रहा है…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "कौन सी बातचीत" msgid "Which rule was broken?" msgstr "कौन सा नियम तोड़ा गया?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "जब तक पुश-टू-टॉक चालू है, आपका शॉर्टकट आपके माइक्रोफ़ोन को नियंत्रित करता है, इसलिए म्यूट बटन अक्षम है।" @@ -39094,6 +39291,11 @@ msgstr "इस अनुमति के बिना, पुश-टू-टॉ msgid "Wordmark" msgstr "वर्डमार्क" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "{productName} फ़ोकस में होने पर काम करता है" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "आपने {PREMIUM_PRODUCT_NAME} पर रहते हुए अपना टैग बदल दिया है। जब आपकी {PREMIUM_PRODUCT_NAME} की अवधि खत्म हो जाएगी, तो यह अपने आप बदल जाएगा, बशर्ते कि इसका रिन्यूअल न हो।" #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "आपके पास उस चैनल का एक्सेस नहीं है जहाँ यह मैसेज भेजा गया था।" @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "आपकी {premiumProductName} सदस्यता समाप्त हो गई है। आपने सभी {premiumProductName} पर्क खो दिए हैं। एक्सेस फिर से पाने के लिए अपनी सदस्यता दोबारा सक्रिय करें।" #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "आपका {productName} सेशन समाप्त हो गया है। इस ऐप्लिकेशन को अधिकृत करना जारी रखने के लिए फिर से साइन इन करें।" @@ -40144,6 +40346,16 @@ msgstr "डिस्कवरी में लिस्ट होने के msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "आपके क्रेडिट कार्ड की जाँच कर ली गई है। अगर कार्ड मान्य है, तो जल्द ही वयस्क सामग्री अनलॉक हो जाएगी। आप इस टैब को बंद करके ऐप पर वापस जा सकते हैं।" +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "आपके डेस्कटॉप ने सिस्टम-व्यापी शॉर्टकट सेट अप नहीं किए।" + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "आपका डेस्कटॉप इन शॉर्टकट को तब भी चलाता है जब {productName} फ़ोकस में न हो।" + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/hr/messages.po b/fluxer_app/src/features/i18n/locales/hr/messages.po index 0d3182655..1802c8252 100644 --- a/fluxer_app/src/features/i18n/locales/hr/messages.po +++ b/fluxer_app/src/features/i18n/locales/hr/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# kanal ga prati} other {# kanala ga prati}} ovu po msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# zajednica pronađena} few {# zajednice pronađene} other {# zajednica pronađeno}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# zajednica privremeno nije dostupna zbog kvara na protočnom kondenzatoru.} few {# zajednice privremeno nisu dostupne zbog kvara na protočnom kondenzatoru.} other {# zajednica privremeno nije dostupno zbog kvara na protočnom kondenzatoru.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} izbjegava slanje push obavijesti na vaše mobilne uređaje msgid "{productName} Bug Hunter" msgstr "Lovac na bugove {productName}a" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} može zatražiti od radnog okruženja da pokreće njegove prečace dok {productName} nije u fokusu. Radno okruženje tražit će potvrdu i omogućiti vam odabir tipki." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} više ne može čitati vašu tipkovnicu pa umjesto toga osluškuje prečace putem X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} više ne može čitati vašu tipkovnicu pa se umjesto toga koriste prečaci radnog okruženja." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} više ne može čitati vašu tipkovnicu. Prečaci rade samo dok je {productName} u fokusu." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} može registrirati svoje prečace u Hyprlandu kako bi radili dok {productName} nije u fokusu. Zatim dodijelite tipke u konfiguraciji Hyprlanda." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "Aplikacija {productName} treba pristup kameri za prikaz vaših uređaja." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "Aplikacija {productName} treba pristup mikrofonu za prikaz vaših uređaja." @@ -1837,6 +1862,11 @@ msgstr "{productName} se sada nalazi na adresi {host}. Instalirajte ga odande." msgid "{productName} Partner" msgstr "Partner {productName}a" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} je registrirao svoje prečace u Hyprlandu. Dodijelite im tipke u konfiguraciji Hyprlanda da biste ih koristili dok {productName} nije u fokusu." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Profil koji se ističe" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri je obavezan kada opseg bot nije jedini opseg." @@ -2847,7 +2877,7 @@ msgstr "Samo u svim otvorenim izravnim porukama" msgid "Across every community you're in" msgstr "U svim zajednicama u kojima ste član" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Radnja" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Dijeljenje aktivnosti" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Prag aktivnosti" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Dodajte još jedan sloj sigurnosti svom računu" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Dodaj još jedan prilagođeni prečac" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Adresa" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Sami prilagodite svaku postavku: potiskivanje buke, poništavanje jeke i pojačanje." @@ -3696,11 +3726,11 @@ msgstr "Dopusti {everyoneMention} i {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Dopustite aplikaciji {PRODUCT_NAME} pristup vašoj kameri u {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Dopustite aplikaciji {PRODUCT_NAME} pristup mikrofonu u {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Dopustite aplikaciji {PRODUCT_NAME} pristup mikrofonu. Provjerite adresnu traku ili postavke preglednika." @@ -3803,7 +3833,7 @@ msgstr "Dopusti članovima bez dopuštenja {readMessageHistoryPermissionLabel} d msgid "Allow mentions for this role" msgstr "Dopusti spominjanje za ovu ulogu" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Dopusti mikrofon" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Autorizacija nije uspjela" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Autorizacija nije uspjela. Pokušajte ponovno." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Automatsko prepoznavanje jezika" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Automatsko postavljanje praga aktivnosti" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Dopuštenja bota" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Dopuštenja bota ne mogu biti negativna." @@ -6869,6 +6899,11 @@ msgstr "Promijeni nadimak u grupi" msgid "Change icon" msgstr "Promijeni ikonu" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Promijeni u postavkama sustava" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Pristup kanalu" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Pristup kanalu odbijen" @@ -7294,7 +7329,7 @@ msgstr "ID kanala kopiran u međuspremnik" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "poveznica kanala" @@ -7462,6 +7497,11 @@ msgstr "unos za chat" msgid "Chat shortcuts" msgstr "Prečaci za chat" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Provjeri ponovno" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Odaberite prozor u sistemskom biraču" msgid "Choose an account" msgstr "Odaberite račun" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Odaberite radnju." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Odaberite pozadinu" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Odaberite guild_id ili channel_id, ne oboje." @@ -7990,7 +8030,7 @@ msgstr "Pročišćava mikrofon za jasan govor." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Kliknite za otvaranje birača boja i odabir nove boje." msgid "Click to react" msgstr "Kliknite za reakciju" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Kliknite za snimanje" @@ -9110,7 +9150,7 @@ msgstr "Potvrda" msgid "Confirmation or double-click for community voice joins" msgstr "Potvrda ili dvostruki klik za pridruživanje glasovnim kanalima zajednice" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "U sukobu s {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Povežite kameru ili virtualni uređaj za snimanje, pa pokušajte ponovn msgid "Connect a display, then try again." msgstr "Povežite zaslon, a zatim pokušajte ponovno." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Povežite mikrofon i pokušajte ponovno." @@ -9700,6 +9740,21 @@ msgstr "Kopiraj poveznicu za pozivnicu" msgid "Copy invite URL" msgstr "Kopiraj URL pozivnice" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Kopiraj retke" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Kopiraj retke za hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Kopiraj retke za hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Prilagođeno" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Prilagođeni odabir" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Prilagođena radnja prečaca" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Opcije prilagođenih prečaca" @@ -11466,7 +11521,7 @@ msgstr "Zadani prečaci" msgid "Default translator" msgstr "Zadani prevoditelj" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Zadano:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Izbriši spremljene medije" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Izbriši prečac" @@ -12102,13 +12157,13 @@ msgstr "Stanje za razvojne programere" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Alati za razvojne programere" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Alati za razvojne programere dostupni su putem značke osoblja u zaglavlju kanala." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Onemogući pozadinske premium pogodnosti" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Onemogući ugrađeni prečac" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Onemogućiti skočne prozore slike u slici za dijeljenje zaslona?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Onemogući prečac" @@ -12888,7 +12943,7 @@ msgstr "Doniraj radije" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Gumb za preuzimanje" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Preuzmi aplikaciju za stolno računalo" @@ -13204,6 +13259,11 @@ msgstr "Nizozemski" msgid "e.g., YubiKey, iPhone, work computer" msgstr "npr. YubiKey, iPhone, poslovno računalo" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Svaka tipka može pokretati samo jedan prečac za cijeli sustav." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Uredi poveznicu za pozivnicu" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Uredi prečac na tipkovnici" @@ -13425,7 +13485,7 @@ msgstr "Obrazac za uređivanje spremljenih medija" msgid "Edit search engine" msgstr "Uredi tražilicu" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Uredi prečac" @@ -13753,20 +13813,16 @@ msgstr "Prazna izmjena" msgid "Empty favorites" msgstr "Prazni favoriti" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Omogući" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Omogući dopuštenje {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Omogući hardversko ubrzanje i ponovno pokreni" msgid "Enable hardware acceleration?" msgstr "Omogućiti hardversko ubrzanje?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Omogućite pristup unosu za korištenje prečaca na razini sustava na Waylandu. Nakon promjene pristupa unosu, u potpunosti zatvorite i ponovno pokrenite {productName} kako bi prečaci mogli koristiti tu značajku." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Omogući Shift za proširivanje" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Omogući prečac" @@ -14357,7 +14408,7 @@ msgstr "Zvuk cijelog sustava" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Zvuk ulaska" @@ -14467,7 +14518,7 @@ msgstr "Večernje vrijeme" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Ujednačava glasnoću mikrofona kako ne biste bili pretihi." @@ -15682,6 +15733,26 @@ msgstr "Samo za britanske oči..." msgid "For me" msgstr "Za mene" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Za „Pritisni za isključivanje mikrofona” koristite jednu tipku bez Ctrl, Alt ili Shift. Kod kombinacije najprije otpustite glavnu tipku jer mikrofon inače može ostati isključen." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Za „Pritisni za govor” koristite jednu tipku bez Ctrl, Alt ili Shift. Kod kombinacije najprije otpustite glavnu tipku jer mikrofon inače može ostati uključen." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Za ovaj prečac koristite jednu tipku bez Ctrl, Alt ili Shift. Kod kombinacije najprije otpustite glavnu tipku jer prečac inače može ostati aktivan." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Za prioritet glasovne aktivnosti koristite jednu tipku bez Ctrl, Alt ili Shift. Kod kombinacije najprije otpustite glavnu tipku jer prioritet inače može ostati uključen." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Radi vaše sigurnosti, potvrdite svoj identitet za nastavak." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Globalni profil" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Globalni prečac" @@ -17434,6 +17505,11 @@ msgstr "Držite tipku Shift i pritisnite X za odabir aplikacija. Pritisnite {sel msgid "Hold to record. Drag up to lock, or release to send." msgstr "Držite za snimanje. Povucite gore za zaključavanje ili otpustite za slanje." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Držanje tipke Shift, Ctrl ili Alt blokira ovaj prečac. Dodajte zamjenske prečace u postavkama sustava." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Hura! Obavijesti su omogućene" msgid "Hyperlink" msgstr "Hiperveza" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland nema dijaloški okvir za prečace. Dodajte retke za svoju verziju Hyprlanda u njegovu konfiguraciju i zamijenite KEY željenom tipkom." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland nema dijaloški okvir za prečace. Dodijelite tipke u konfiguraciji Hyprlanda." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (prije Hyprlanda 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 i noviji)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Slažem se s <0>Pravilima privatnosti" @@ -17576,7 +17672,7 @@ msgstr "Predomislio/la sam se" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Razumijem" @@ -18000,7 +18096,7 @@ msgstr "U" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "U načinu rada \"Operativni sustav\" gore navedeni rječnici su savjetodavni. {PRODUCT_NAME} će proslijediti ove jezične oznake provjeri pravopisa vašeg OS-a kada je podržano. Oznake koje ne prepoznaje bit će zanemarene." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "U pregledniku pritisni za govor radi samo dok je kartica {PRODUCT_NAME} aktivna. Instalirajte desktop aplikaciju za značajku pritisni za govor na cijelom sustavu." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Unos" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Pristup unosu nije bilo moguće automatski omogućiti. Promijenite ga ručno, zatim u potpunosti zatvorite i ponovno pokrenite {productName} kako bi ga prečaci mogli koristiti." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Pristup unosu nije omogućen. Pokušajte ponovno u postavkama {settingsMenuName}, zatim u potpunosti zatvorite i ponovno pokrenite {productName}." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Pristup unosu potreban je za prečace tipkovnice i miša na razini sustava na Waylandu. Nakon promjene pristupa unosu, u potpunosti zatvorite i ponovno pokrenite {productName} kako bi ga prečaci mogli koristiti." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Pristup unosu je promijenjen. U potpunosti zatvorite i ponovno pokrenite {productName} kako bi ga prečaci na razini sustava mogli koristiti." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Ulaz i izlaz" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Nevažeći zahtjev za autorizaciju" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Nevažeća dopuštenja bota." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Nevažeći ili istekli kod. Pokušajte ponovno." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Nevažeći redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Poveznica za pozivnicu skrivena je tijekom dijeljenja:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Poveznica za pozivnicu maskirana tijekom dijeljenja" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Skoči na {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Skoči na {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Skoči na trenutačni poziv" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Skoči na povezani kanal" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Skoči na povezanu poruku" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Skoči na poruku od {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Skoči na poruku u {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Čuvajte ih na sigurnom. Ponovno generiranje prekinut će postojeće integracije." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Nastavi s prijenosom još nakratko nakon otpuštanja tipke." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Tipkovnica" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Pristup tipkovnici nije se mogao pokrenuti. Prečaci rade samo dok je {productName} u fokusu." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Neka se zajednica sama moderira" msgid "Let the member hear others in this community again" msgstr "Članu je ponovno omogućeno slušanje drugih u ovoj zajednici" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Omogućuje aplikaciji {productName} izravno čitanje tipkovnice. Vaš sustav to već dopušta." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Omogućuje prilagođenom CSS-u da otkrije prozirna područja prozora nakon ponovnog pokretanja aplikacije za stolna računala." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Stil poveznica" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Poveznica nije dostupna" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Izgled i dojam" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Čini se da koristite {0}. Dodatna obrada u {PRODUCT_NAME}u povrh toga može uzrokovati pumpanje ili izobličenje zvuka. Izravan unos propušta vaš zvuk netaknutim." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Izgled poruka" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "poveznica poruke" @@ -21332,7 +21416,7 @@ msgstr "Mikrofon" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Test mikrofona" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Razno" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Nedostaje client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Više opcija" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Više opcija prečaca" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Nema dopuštenja {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Nema pristupa" @@ -22984,7 +23068,7 @@ msgstr "Nema rezultata" msgid "No mentions yet" msgstr "Još nema spominjanja" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Nije pronađen mikrofon" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Nema pronađenih postavki" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Prečac nije postavljen" @@ -23358,6 +23442,11 @@ msgstr "Nije cijeli broj" msgid "Not accepted" msgstr "Nije prihvaćeno" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Nije dodijeljeno" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Otvorite {host} u svojem pregledniku." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Otvori {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Otvori {labelText} u postavkama" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "link na stranicu" @@ -25611,11 +25700,11 @@ msgstr "Prisutnost" msgid "Press {shortcut} to react" msgstr "Pritisnite {shortcut} za reakciju" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Pritisnite tipku, gumb miša ili gumb gamepada za postavljanje prečaca. Escape poništava." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Pritisnite prečac…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Tipka za pritisni za govor" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Pritisni za govor" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Pritisni za govor (ograničeno)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Pritisni za govor još nema tipku za cijeli sustav. Da biste je postavili, idite na Postavke > Aplikacije > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Pritisni za govor još nema tipku za cijeli sustav. Da biste je postavili, idite na Postavke sustava > Tipkovnica > Prečaci > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Pritisni za govor još nema tipku za cijeli sustav. Da biste je postavili, otvorite postavke tipkovničkih prečaca u svojem radnom okruženju." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Pritisni za govor zanemaruje se u izravnom unosu. Prebacite se na fokusirani glas ili prilagođeno da biste ga koristili." @@ -26293,21 +26397,51 @@ msgstr "Pritisni za govor zanemaruje se u izravnom unosu. Prebacite se na fokusi msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Uključena je opcija pritisni za govor. Držite {pushToTalkHint} da biste govorili." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Uključena je opcija pritisni za govor. Držite tipku za govor da biste govorili." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Pritisni za govor radi samo dok je {productName} u fokusu jer vaše radno okruženje nije postavilo prečace za cijeli sustav." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Pritisni za govor radi samo dok je {productName} u fokusu, sve dok se ne omogući {permissionName}. Nakon promjene dopuštenja, u potpunosti zatvorite i ponovno pokrenite {productName} kako bi ga prečaci na razini sustava mogli koristiti." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Pritisni za govor radi samo dok je {productName} u fokusu, sve dok ne postavite prečace za cijeli sustav." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Pritisni za govor radi samo dok je kartica preglednika u fokusu." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Pritisni za govor koristit će tipku koju postavite u aplikaciji {productName}. Najprije je postavite jer će inače mikrofon raditi na glasovnu aktivnost." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Pritisni za govor neće uključiti vaš mikrofon dok vam je zvuk isključen. Uključite zvuk kako biste mogli govoriti." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Pritisni za govor radi izvan aplikacije {productName} tek kada mu dodijelite tipku u konfiguraciji Hyprlanda." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Pritisni za govor radi izvan aplikacije {productName} tek kada mu dodijelite tipku u konfiguraciji Hyprlanda. Dodajte redak za svoju verziju Hyprlanda i zamijenite KEY željenom tipkom." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Sirovi JSON ugrađenog sadržaja" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Ponovno omogući ugrađeni prečac" @@ -26565,13 +26699,6 @@ msgstr "Nedavna spominjanja" msgid "Recent searches" msgstr "Nedavna pretraživanja" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Ponovno provjeri" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Preporučeno" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Preporučeno. Pročišćava mikrofon za jasan govor." @@ -26600,6 +26727,11 @@ msgstr "Ponovno poveži" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Ponovno povezivanje uspješno. Ponovno odaberite zaslon ako želite nastaviti prethodni prijenos." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Ponovno povezivanje s radnim okruženjem…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Ponovno povezivanje…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Snimi prečac" @@ -26851,7 +26983,7 @@ msgstr "Relativno" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Odgoda otpuštanja" @@ -27739,13 +27871,13 @@ msgstr "Ponovno poslano" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Poništi" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Vrati \"{label}\" na ugrađeno" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Vrati na stvarne vrijednosti" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Vrati na zadano" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Odaberite račun s kojim se želite prijaviti na novom uređaju." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Odaberite način obrade glasa" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Šalje sirovi zvuk mikrofona bez ikakve obrade." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Šalje vaš zvuk netaknut. Najbolje ako koristite vanjski audio softver." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Postavljeno pomoću predmemorije {preset}. Odabir vrijednosti ovdje prebacuje na Prilagođeno." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Postavi prilagođeni prečac" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Postavi prilagođeni prečac za „{label}”" @@ -30066,6 +30198,11 @@ msgstr "Postavljena je glasovna regija na {region}" msgid "Set the voice region to automatic" msgstr "Postavljena je glasovna regija na automatsku" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Postavi" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Postavke" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "poveznica postavki" @@ -30330,8 +30467,8 @@ msgstr "Kratki kodovi" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Prebaci na ugodan izgled" msgid "Switch to dense layout" msgstr "Prebaci na zgusnuti izgled" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Prebaci na izravan unos" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Instalacija kojom upravlja sustav" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Prečaci za cijeli sustav" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Prečace za cijeli sustav nije moguće postaviti." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Za cijeli sustav: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Za cijeli sustav: nije dodijeljeno" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Stranica koju tražite ne postoji ili je premještena." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Navedeni redirect_uri nije registriran za ovu aplikaciju." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Navedeni redirect_uri mora koristiti HTTP ili HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Ova animacija je predugačka ili prevelika za obrezivanje. Isprobajte kraću ili manju datoteku." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Ova aplikacija" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Ovaj je privitak istekao" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Ovaj bot nije javan i drugi ga korisnici ne mogu dodati." @@ -34081,6 +34233,16 @@ msgstr "Ovaj razgovor više nije dostupan." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Ova verzija za stolno računalo ne može instalirati ažuriranja unutar {productName}. Umjesto toga preuzmite najnoviji instalacijski program za stolno računalo." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Ovo radno okruženje ne nudi prečace za cijeli sustav. Pritisni za govor radi samo dok je {productName} u fokusu." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Ovo radno okruženje ne nudi prečace za cijeli sustav. Prečaci i dalje rade dok je {productName} u fokusu." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Ovo je vaš posljednji pristupni ključ, a pristupni ključevi su vaš d msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Ovo je vaš posljednji pristupni ključ. Kada ga više ne bude, nećete moći koristiti pristupni ključ kao drugi faktor." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Ova tipka radi dok je {productName} u fokusu. Tipku za cijeli sustav postavlja vaše radno okruženje, pod {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Ovaj kanal s poveznicama označen je kao sadržaj za odrasle i može otvoriti materijal koji možda nije prikladan za neke korisnike." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Ova poveznica nije dostupna za vaš račun." @@ -34778,6 +34945,21 @@ msgstr "Naslovna traka" msgid "Title bar platform" msgstr "Platforma naslovne trake" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Da biste promijenili tipke, idite na Postavke > Aplikacije > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Da biste promijenili tipke, idite na Postavke sustava > Tipkovnica > Prečaci > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Da biste promijenili tipke, otvorite postavke tipkovničkih prečaca u svojem radnom okruženju." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Za izradu webhooka otvorite <0>{channelWebhooksSettingsPath}. Ovdje i dalje možete uređivati i organizirati sve postojeće webhookove." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Nepoznato" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Nepoznata aplikacija, istekla sesija ili nevažeći zahtjev za autorizaciju." @@ -36410,6 +36592,11 @@ msgstr "Do 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "Do 720p pri 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Ažurirajte {productName} da biste koristili prečace za cijeli sustav na Waylandu." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Koristi tamnu temu u cijeloj aplikaciji" msgid "Use default" msgstr "Koristi zadano" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Koristi izravan pristup ulaznim uređajima" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Sudionici glasovnog poziva" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Obrada glasa" @@ -38264,7 +38456,7 @@ msgstr "Uključivanje zvuka" msgid "Voice unmute" msgstr "Uključivanje mikrofona" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Glas koristi vaš sistemski izlazni uređaj u ovom pregledniku. Promijenite ga u sistemskim postavkama." @@ -38278,7 +38470,7 @@ msgstr "Poništeno" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Čeka se istek usporenog načina rada u jednom ili više odabranih kanal msgid "Waiting for you to..." msgstr "Čekamo da..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Čekanje na radno okruženje…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Koje razgovore" msgid "Which rule was broken?" msgstr "Koje je pravilo prekršeno?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Dok je uključena značajka pritisni za govor, vaš prečac upravlja mikrofonom, pa je gumb za isključivanje mikrofona onemogućen." @@ -39094,6 +39291,11 @@ msgstr "Bez ovog dopuštenja obavezna je značajka pritisni za govor." msgid "Wordmark" msgstr "Tekstualni logotip" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Radi dok je {productName} u fokusu" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Promijenili ste svoju oznaku dok ste imali pretplatu na {PREMIUM_PRODUCT_NAME}. Nasumično će se ponovno dodijeliti kada vam istekne {PREMIUM_PRODUCT_NAME}, osim ako se ne obnovi." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Nemate pristup kanalu u kojem je poslana ova poruka." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Vaša pretplata na {premiumProductName} je istekla. Izgubili ste sve pogodnosti {premiumProductName}. Ponovno aktivirajte pretplatu kako biste ponovno dobili pristup." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Vaša je sesija za {productName} istekla. Prijavite se ponovno kako biste nastavili s autorizacijom ove aplikacije." @@ -40144,6 +40346,16 @@ msgstr "Vaša zajednica treba imati najmanje {minMemberCount, plural, one {# čl msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Vaša je kreditna kartica provjerena. Ako je kartica valjana, sadržaj za odrasle uskoro će biti otključan. Možete zatvoriti ovu karticu preglednika i vratiti se u aplikaciju." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Vaše radno okruženje nije postavilo prečace za cijeli sustav." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Vaše radno okruženje pokreće ove prečace čak i kad {productName} nije u fokusu." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/hu/messages.po b/fluxer_app/src/features/i18n/locales/hu/messages.po index 5a000a5c2..bb699bc22 100644 --- a/fluxer_app/src/features/i18n/locales/hu/messages.po +++ b/fluxer_app/src/features/i18n/locales/hu/messages.po @@ -993,7 +993,7 @@ msgstr "Ez a csatorna követőinek száma: {count, plural, one {# csatorna köve msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# közösség található} other {# közösség található}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# közösség átmenetileg nem elérhető egy „fluxuskondenzátor” meghibásodása miatt.} other {# közösség átmenetileg nem elérhető egy „fluxuskondenzátor” meghibásodása miatt.}}" @@ -1708,6 +1708,31 @@ msgstr "A(z) {productName} nem küld pushértesítéseket a mobileszközeidre, a msgid "{productName} Bug Hunter" msgstr "{productName} hibavadász" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "A(z) {productName} megkérheti a grafikus környezetedet, hogy akkor is futtassa a billentyűparancsait, ha a(z) {productName} nincs fókuszban. A grafikus környezeted megerősítést kér, és kiválaszthatod a billentyűket." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "A(z) {productName} már nem tudja olvasni a billentyűzetedet, ezért helyette az X11-en keresztül figyeli a billentyűparancsokat." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "A(z) {productName} már nem tudja olvasni a billentyűzetedet, ezért helyette a grafikus környezeted billentyűparancsai működnek." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "A(z) {productName} már nem tudja olvasni a billentyűzetedet. A billentyűparancsok csak akkor működnek, ha a(z) {productName} fókuszban van." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "A(z) {productName} regisztrálhatja a billentyűparancsait a Hyprlandben, így azok akkor is működnek, ha a(z) {productName} nincs fókuszban. Ezután a billentyűket a Hyprland konfigurációjában rendelheted hozzá." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "A(z) {productName} alkalmazásnak kamera-hozzáférésre van szüksége az eszközök listázásához." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "A(z) {productName} mikrofon-hozzáférést igényel az eszközök listázásához." @@ -1837,6 +1862,11 @@ msgstr "A(z) {productName} mostantól itt érhető el: {host}. Telepítsd onnan. msgid "{productName} Partner" msgstr "{productName} partner" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "A(z) {productName} regisztrálta a billentyűparancsait a Hyprlandben. Rendelj hozzájuk billentyűket a Hyprland konfigurációjában, hogy akkor is használhasd őket, ha a(z) {productName} nincs fókuszban." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Egy profil, ami kitűnik" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "A redirect_uri megadása kötelező, ha nem a „bot” az egyetlen kért hatókör." @@ -2847,7 +2877,7 @@ msgstr "Csak az összes megnyitott közvetlen beszélgetésben" msgid "Across every community you're in" msgstr "Az összes közösségben, amiben benne vagy" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Művelet" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Tevékenységmegosztás" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Tevékenységi küszöb" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Tedd még biztonságosabbá a fiókodat" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Újabb egyéni billentyűparancs hozzáadása" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Cím" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Állítsd be az egyes beállításokat magad: zajszűrés, visszhangszűrés és erősítés." @@ -3696,11 +3726,11 @@ msgstr "{everyoneMention} és {hereMention} engedélyezése" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Engedélyezd a(z) {PRODUCT_NAME} számára a kamera használatát itt: {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Engedélyezd a(z) {PRODUCT_NAME} számára a mikrofonod elérését a(z) {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME} útvonalon." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Engedélyezd, hogy a(z) {PRODUCT_NAME} hozzáférjen a mikrofonodhoz. Ellenőrizd a böngésződ címsorát vagy beállításait." @@ -3803,7 +3833,7 @@ msgstr "Engedélyezd a {readMessageHistoryPermissionLabel} jogosultsággal nem r msgid "Allow mentions for this role" msgstr "Említések engedélyezése ehhez a szerepkörhöz" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Mikrofon engedélyezése" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Sikertelen engedélyezés" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Az engedélyezés sikertelen. Próbáld újra." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Nyelv automatikus észlelése" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Tevékenységi küszöb automatikus beállítása" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Botjogosultságok" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "A bot jogosultságainak értéke nem lehet negatív." @@ -6869,6 +6899,11 @@ msgstr "Csoportbeli becenév módosítása" msgid "Change icon" msgstr "Ikon módosítása" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Módosítás a rendszerbeállításokban" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Csatorna-hozzáférés" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Csatorna-hozzáférés megtagadva" @@ -7294,7 +7329,7 @@ msgstr "Csatornaazonosító a vágólapra másolva" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "csatornalink" @@ -7462,6 +7497,11 @@ msgstr "csevegés beviteli mezője" msgid "Chat shortcuts" msgstr "Csevegési billentyűparancsok" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Újraellenőrzés" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Válassz egy ablakot a rendszer választójában" msgid "Choose an account" msgstr "Fiók kiválasztása" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Válassz egy műveletet." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Háttér kiválasztása" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Vagy a guild_id, vagy a channel_id mezőt add meg, ne mindkettőt." @@ -7990,7 +8030,7 @@ msgstr "Tisztábbá teszi a mikrofonod hangját, hogy a beszéd jól érthető l #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Kattints a színválasztó megnyitásához és egy új szín kiválaszt msgid "Click to react" msgstr "Kattints a reagáláshoz" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Kattints a rögzítéshez" @@ -9110,7 +9150,7 @@ msgstr "Megerősítés" msgid "Confirmation or double-click for community voice joins" msgstr "Megerősítés vagy dupla kattintás a közösségi hangcsatornákhoz való csatlakozáshoz." -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Ütközik a(z) {conflictLabel} billentyűkombinációval" @@ -9127,7 +9167,7 @@ msgstr "Csatlakoztass egy kamerát vagy virtuális rögzítőeszközt, majd pró msgid "Connect a display, then try again." msgstr "Csatlakoztass egy kijelzőt, majd próbáld újra." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Csatlakoztass egy mikrofont, és próbáld újra." @@ -9700,6 +9740,21 @@ msgstr "Meghívólink másolása" msgid "Copy invite URL" msgstr "Meghívó URL-jének másolása" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Sorok másolása" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Sorok másolása ehhez: hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Sorok másolása ehhez: hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Egyéni" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Egyéni kiválasztás" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Egyéni billentyűparancs művelete" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Egyéni billentyűparancs-beállítások" @@ -11466,7 +11521,7 @@ msgstr "Alapértelmezett billentyűparancsok" msgid "Default translator" msgstr "Alapértelmezett fordító" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Alapértelmezett:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Mentett média törlése" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Billentyűparancs törlése" @@ -12102,13 +12157,13 @@ msgstr "Fejlesztői állapot" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Fejlesztői eszközök" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "A fejlesztői eszközök a csatornafejlécben található személyzeti jelvényről érhetők el." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Prémiumelőnyök letiltása a szerveren" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Beépített billentyűparancs letiltása" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Letiltod a képernyőmegosztás kép a képben ablakait?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Billentyűparancs letiltása" @@ -12888,7 +12943,7 @@ msgstr "Adományozzon helyette" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Letöltés gomb" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Asztali alkalmazás letöltése" @@ -13204,6 +13259,11 @@ msgstr "Holland" msgid "e.g., YubiKey, iPhone, work computer" msgstr "pl. YubiKey, iPhone, munkahelyi számítógép" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Egy billentyűhöz csak egy rendszerszintű billentyűparancs tartozhat." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Meghívólink szerkesztése" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Billentyűparancs szerkesztése" @@ -13425,7 +13485,7 @@ msgstr "Mentett média szerkesztése – űrlap" msgid "Edit search engine" msgstr "Keresőmotor szerkesztése" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Billentyűparancs szerkesztése" @@ -13753,20 +13813,16 @@ msgstr "Üres szerkesztés" msgid "Empty favorites" msgstr "Üres kedvencek" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Engedélyezés" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "{permissionName} jogosultság engedélyezése" @@ -13876,11 +13932,6 @@ msgstr "Hardveres gyorsítás engedélyezése és újraindítás" msgid "Enable hardware acceleration?" msgstr "Engedélyezed a hardveres gyorsítást?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Engedélyezd a bemeneti hozzáférést a rendszerszintű billentyűparancsok használatához Waylanden. A hozzáférés módosítása után teljesen zárd be, majd indítsd újra a(z) {productName} alkalmazást, hogy a billentyűparancsok használhassák." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Shift billentyűvel kibontás engedélyezése" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Billentyűparancs engedélyezése" @@ -14357,7 +14408,7 @@ msgstr "Teljes rendszerhang" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Belépési hang" @@ -14467,7 +14518,7 @@ msgstr "Este" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Kiegyenlíti a mikrofonod hangerejét, hogy ne legyél túl halk." @@ -15682,6 +15733,26 @@ msgstr "Csak brit szemeknek..." msgid "For me" msgstr "Nekem" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "A „Nyomva tartva némítás” funkcióhoz egyetlen billentyűt használj Ctrl, Alt vagy Shift nélkül. Billentyűkombináció esetén először a fő billentyűt engedd fel, különben a mikrofon némítva maradhat." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Az adásgombhoz egyetlen billentyűt használj Ctrl, Alt vagy Shift nélkül. Billentyűkombináció esetén először a fő billentyűt engedd fel, különben a mikrofon bekapcsolva maradhat." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Ehhez a billentyűparancshoz egyetlen billentyűt használj Ctrl, Alt vagy Shift nélkül. Billentyűkombináció esetén először a fő billentyűt engedd fel, különben a billentyűparancs aktív maradhat." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "A „Beszédérzékelés elsőbbséggel” funkcióhoz egyetlen billentyűt használj Ctrl, Alt vagy Shift nélkül. Billentyűkombináció esetén először a fő billentyűt engedd fel, különben az elsőbbség bekapcsolva maradhat." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "A biztonságod érdekében kérjük, erősítsd meg, hogy te vagy az, mielőtt folytatnád." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Globális profil" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Globális billentyűparancs" @@ -17434,6 +17505,11 @@ msgstr "Tartsd lenyomva a Shift billentyűt, és nyomd meg az X-et az alkalmazá msgid "Hold to record. Drag up to lock, or release to send." msgstr "Tartsd nyomva a felvételhez. Húzd felfelé a kéz nélküli folytatáshoz, vagy engedd el a küldéshez." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "A Shift, Ctrl vagy Alt lenyomva tartása blokkolja ezt a billentyűparancsot. Alternatívákat a rendszerbeállításokban adhatsz hozzá." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Hurrá! Az értesítések engedélyezve vannak" msgid "Hyperlink" msgstr "Hivatkozás" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "A Hyprlandben nincs billentyűparancs-párbeszédablak. Add hozzá a Hyprland-verziódnak megfelelő sorokat a Hyprland konfigurációjához, és cseréld le a KEY szót a kívánt billentyűre." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "A Hyprlandben nincs billentyűparancs-párbeszédablak. A billentyűket a Hyprland konfigurációjában rendelheted hozzá." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (Hyprland 0.55 előtt)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 és újabb)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Elfogadom az <0>Adatvédelmi irányelveket" @@ -17576,7 +17672,7 @@ msgstr "Meggondoltam magam" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Értem" @@ -18000,7 +18096,7 @@ msgstr "Ebben" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "„Operációs rendszer” módban a fent felsorolt szótárak tájékoztató jellegűek. A(z) {PRODUCT_NAME} átadja ezeket a nyelvi címkéket az operációs rendszer helyesírás-ellenőrzőjének, ha támogatott. Azokat a címkéket, amelyeket nem ismer fel, figyelmen kívül hagyja." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "Böngészőben az adásgomb csak akkor működik, ha a(z) {PRODUCT_NAME} lap fókuszban van. Telepítsd az asztali alkalmazást a rendszerszintű adásgomb használatához." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Bevitel" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Nem sikerült automatikusan engedélyezni a bemeneti hozzáférést. Módosítsd kézzel, majd teljesen zárd be és indítsd újra a(z) {productName} alkalmazást, hogy a billentyűparancsok használhassák." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Nem sikerült engedélyezni a bemeneti hozzáférést. Próbáld újra a(z) {settingsMenuName} beállításainál, majd teljesen zárd be és indítsd újra a(z) {productName} alkalmazást." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "A rendszerszintű billentyű- és egérparancsok használatához Waylanden bemeneti hozzáférés szükséges. A hozzáférés módosítása után teljesen zárd be, majd indítsd újra a(z) {productName} alkalmazást, hogy a parancsok használhassák." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Megváltozott a bemeneti hozzáférés. Teljesen zárd be, majd indítsd újra a(z) {productName} alkalmazást, hogy a rendszerszintű billentyűparancsok használhassák." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Bemenet és kimenet" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Érvénytelen engedélyezési kérelem" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Érvénytelen botjogosultságok." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Érvénytelen vagy lejárt kód. Próbáld újra." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Érvénytelen redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "A meghívólink megosztás közben rejtve van:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Meghívólink elrejtve megosztás közben" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Ugrás ide: {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Ugrás ide: {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Ugrás az aktuális híváshoz" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Ugrás a hivatkozott csatornára" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Ugrás a hivatkozott üzenetre" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Ugrás a {authorNickname} üzenetéhez" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Ugrás az üzenethez itt: {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Őrizd meg őket biztonságosan. Az újragenerálás működésképtelenné teszi a meglévő integrációkat." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "A gomb elengedése után is tartsa aktívnak a jelet egy rövid ideig." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Billentyűzet" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Nem sikerült elindítani a billentyűzet-hozzáférést. A billentyűparancsok csak akkor működnek, ha a(z) {productName} fókuszban van." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Engedd, hogy a közösség önmagát moderálja" msgid "Let the member hear others in this community again" msgstr "Ismét engedélyezte, hogy a tag hallja a többieket ebben a közösségben" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Lehetővé teszi, hogy a(z) {productName} közvetlenül olvassa a billentyűzetedet. A rendszered ezt már engedélyezi." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Az egyéni CSS lehetővé teszi az átlátszó ablakrégiók megjelenítését az asztali alkalmazás újraindítása után." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Linkstílus" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "A hivatkozás nem érhető el" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Megjelenés és érzet" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Úgy tűnik, ezt használod: {0}. Ha erre a(z) {PRODUCT_NAME} hangfeldolgozása is rákerül, az hangerő-ingadozást vagy torzítást okozhat. A közvetlen bemenet változtatás nélkül továbbítja a hangodat." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Üzenetelrendezés" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "üzenet linkje" @@ -21332,7 +21416,7 @@ msgstr "Mikrofon" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Mikrofonteszt" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Egyéb" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Hiányzó client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "További lehetőségek" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "További billentyűparancs-beállítások" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Nincs {sendMessagesPermission} jogosultság" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Nincs hozzáférés" @@ -22984,7 +23068,7 @@ msgstr "Nincs találat" msgid "No mentions yet" msgstr "Még nincsenek említések" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Nem található mikrofon" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Nem található beállítás" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Nincs beállított billentyűparancs" @@ -23358,6 +23442,11 @@ msgstr "Nem egész szám" msgid "Not accepted" msgstr "Nem fogadta el" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Nincs hozzárendelve" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Nyisd meg a(z) {host} webhelyet a böngésződben." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Nyissa meg a(z) {labelText} oldalt" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "A(z) {labelText} megnyitása a beállításokban" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "oldal hivatkozás" @@ -25611,11 +25700,11 @@ msgstr "Jelenlét" msgid "Press {shortcut} to react" msgstr "A reagáláshoz nyomd meg ezt: {shortcut}" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "A hozzárendeléshez nyomj meg egy billentyűt, egérgombot vagy játékvezérlőgombot. Az Esc megszakítja a műveletet." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Nyomj meg egy billentyűkombinációt…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Adásgomb billentyűje" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Adásgomb" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Adásgomb (korlátozott)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Az adásgombhoz még nincs rendszerszintű billentyű rendelve. Beállításához nyisd meg a Beállítások > Alkalmazások > {productName} menüpontot." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Az adásgombhoz még nincs rendszerszintű billentyű rendelve. Beállításához nyisd meg a Rendszerbeállítások > Billentyűzet > Gyorsbillentyűk > {productName} menüpontot." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Az adásgombhoz még nincs rendszerszintű billentyű rendelve. Beállításához nyisd meg a grafikus környezeted billentyűparancs-beállításait." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Közvetlen bemenet használatakor az adásgomb nem működik. A használatához válts Beszédkiemelés vagy Egyéni módra." @@ -26293,21 +26397,51 @@ msgstr "Közvetlen bemenet használatakor az adásgomb nem működik. A használ msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Az adásgomb be van kapcsolva. A beszédhez tartsd lenyomva ezt: {pushToTalkHint}" +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Az adásgomb be van kapcsolva. A beszédhez tartsd lenyomva az adásgomb billentyűjét." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Az adásgomb csak akkor működik, ha a(z) {productName} fókuszban van, mert a grafikus környezeted nem állította be a rendszerszintű billentyűparancsokat." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Amíg nincs engedélyezve a(z) {permissionName}, az adásgomb csak akkor működik, ha a(z) {productName} fókuszban van. Az engedély módosítása után teljesen zárd be, majd indítsd újra a(z) {productName} alkalmazást, hogy a rendszerszintű billentyűparancsok használhassák." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Amíg nem állítod be a rendszerszintű billentyűparancsokat, az adásgomb csak akkor működik, ha a(z) {productName} fókuszban van." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Az adásgomb csak akkor működik, ha a böngészőlap fókuszban van." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Az adásgomb a(z) {productName} alkalmazásban beállított billentyűt fogja használni. Előbb állíts be egyet, különben a mikrofon hangaktivitás alapján működik." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Az adásgomb nem oldja fel a mikrofon némítását, amíg a bejövő hang ki van kapcsolva. A beszédhez kapcsold vissza a hangot." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Az adásgomb a(z) {productName} alkalmazáson kívül csak akkor működik, ha hozzárendeled a Hyprland konfigurációjában." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Az adásgomb a(z) {productName} alkalmazáson kívül csak akkor működik, ha hozzárendeled a Hyprland konfigurációjában. Add hozzá a Hyprland-verziódnak megfelelő sort, és cseréld le a KEY szót a kívánt billentyűre." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Beágyazott tartalom nyers JSON-ja" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Beépített billentyűparancs újraengedélyezése" @@ -26565,13 +26699,6 @@ msgstr "Legutóbbi említések" msgid "Recent searches" msgstr "Legutóbbi keresések" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Újraellenőrzés" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Ajánlott" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Ajánlott. Megtisztítja a mikrofon hangját az érthető beszédhez." @@ -26600,6 +26727,11 @@ msgstr "Újracsatlakozás" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Az újracsatlakozás sikeres. Ha folytatni szeretnéd az előző streamet, válassz újra egy képernyőt." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Újracsatlakozás a grafikus környezethez…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Újracsatlakozás…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Billentyűparancs rögzítése" @@ -26851,7 +26983,7 @@ msgstr "Relatív" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Késleltetés felengedéskor" @@ -27739,13 +27871,13 @@ msgstr "Újraküldve" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Visszaállítás" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "„{label}” visszaállítása a beépítettre" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Visszaállítás a tényleges értékekre" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Visszaállítás beépítettre" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Válaszd ki a fiókot, amellyel be szeretnél jelentkezni az új eszközödön." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Hangfeldolgozási mód kiválasztása" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Nyers mikrofonhang küldése feldolgozás nélkül." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Érintetlenül küldi a hangot. Akkor a legjobb, ha külső audioszoftvert használsz." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "A(z) {preset} előbeállítás állította be. Az itt kiválasztott értékre váltás egyénire kapcsol." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Egyéni billentyűparancs beállítása" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Egyéni billentyűparancs beállítása ehhez: „{label}”" @@ -30066,6 +30198,11 @@ msgstr "A hangcsatorna régiója beállítva erre: {region}" msgid "Set the voice region to automatic" msgstr "A hangcsatorna régiójának automatikusra állítása" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Beállítás" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Beállítások" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "beállítási hivatkozás" @@ -30330,8 +30467,8 @@ msgstr "Rövidkódok" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Átváltás kényelmes elrendezésre" msgid "Switch to dense layout" msgstr "Váltás sűrű elrendezésre" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Váltás közvetlen bemenetre" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Rendszer által kezelt telepítés" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Rendszerszintű billentyűparancsok" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Nem sikerült beállítani a rendszerszintű billentyűparancsokat." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Rendszerszinten: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Rendszerszinten: nincs hozzárendelve" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "A keresett oldal nem létezik, vagy áthelyezték." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "A megadott redirect_uri nincs regisztrálva ehhez az alkalmazáshoz." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "A megadott redirect_uri-nak HTTP vagy HTTPS protokollt kell használnia." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Ez az animáció túl hosszú vagy túl nagy a vágáshoz. Próbálkozz rövidebb vagy kisebb fájllal." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Ez az alkalmazás" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Ez a melléklet lejárt" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Ez a bot nem nyilvános, és más felhasználók nem adhatják hozzá." @@ -34081,6 +34233,16 @@ msgstr "Ez a beszélgetés már nem elérhető." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Ebben az asztali kiadásban a(z) {productName} alkalmazáson belül nem telepíthetők frissítések. Töltsd le a legújabb asztali telepítőt." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Ez a grafikus környezet nem támogatja a rendszerszintű billentyűparancsokat. Az adásgomb csak akkor működik, ha a(z) {productName} fókuszban van." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Ez a grafikus környezet nem támogatja a rendszerszintű billentyűparancsokat. A billentyűparancsok továbbra is működnek, amíg a(z) {productName} fókuszban van." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Ez az utolsó jelszókulcsod, és a jelszókulcsok a második hitelesít msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Ez az utolsó jelszókulcsod. Ha törlöd, nem tudsz többé jelszókulcsot használni második hitelesítési tényezőként." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Ez a billentyű akkor működik, ha a(z) {productName} fókuszban van. A rendszerszintű billentyűt a grafikus környezeted állítja be, itt: {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Ez a linkcsatorna felnőtt tartalomként van megjelölve, és olyan anyagokat nyithat meg, amelyek egyes felhasználók számára nem megfelelőek." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Ez a hivatkozás nem érhető el a fiókod számára." @@ -34778,6 +34945,21 @@ msgstr "Címsor" msgid "Title bar platform" msgstr "Címsor platform" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "A billentyűk módosításához nyisd meg a Beállítások > Alkalmazások > {productName} menüpontot." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "A billentyűk módosításához nyisd meg a Rendszerbeállítások > Billentyűzet > Gyorsbillentyűk > {productName} menüpontot." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "A billentyűk módosításához nyisd meg a grafikus környezeted billentyűparancs-beállításait." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Webhook létrehozásához nyisd meg ezt: <0>{channelWebhooksSettingsPath}. Az összes meglévő webhookot továbbra is szerkesztheted és rendszerezheted itt." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Ismeretlen" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Ismeretlen alkalmazás, lejárt munkamenet vagy érvénytelen engedélyezési kérelem." @@ -36410,6 +36592,11 @@ msgstr "Akár 4K/60fps felbontásig" msgid "Up to 720p at 30 FPS" msgstr "Akár 720p felbontás 30 FPS sebességgel" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Frissítsd a(z) {productName} alkalmazást, hogy Waylanden is használhasd a rendszerszintű billentyűparancsokat." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Sötét téma használata az alkalmazásban" msgid "Use default" msgstr "Alapértelmezett használata" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Közvetlen bemeneti eszközhozzáférés használata" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Hanghívás résztvevői" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Hangfeldolgozás" @@ -38264,7 +38456,7 @@ msgstr "Bejövő hang visszakapcsolása" msgid "Voice unmute" msgstr "Mikrofon némításának feloldása" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "A hang a böngészőben az alapértelmezett rendszerkimeneti eszközödet használja. Módosítsd a rendszerbeállításaidban." @@ -38278,7 +38470,7 @@ msgstr "Érvénytelenítve" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Várakozás, amíg a lassú mód lejár egy vagy több kiválasztott csa msgid "Waiting for you to..." msgstr "Rád vár, hogy..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Várakozás a grafikus környezetre…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Mely beszélgetések" msgid "Which rule was broken?" msgstr "Melyik szabályt szegték meg?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Amíg az adásgomb be van kapcsolva, a billentyűparancs vezérli a mikrofonodat, ezért a némítás gomb nem használható." @@ -39094,6 +39291,11 @@ msgstr "E jogosultság nélkül adásgombot kell használni." msgid "Wordmark" msgstr "Szójel" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Akkor működik, ha a(z) {productName} fókuszban van" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "A(z) {PREMIUM_PRODUCT_NAME} használata közben megváltoztattad a címkédet. Ha a(z) {PREMIUM_PRODUCT_NAME} nem újul meg, a lejáratakor véletlenszerű új címkét kapsz." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Nincs hozzáférésed ahhoz a csatornához, ahová ezt az üzenetet küldték." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Lejárt a(z) {premiumProductName} előfizetésed. Elvesztetted a(z) {premiumProductName} összes előnyét. Aktiváld újra az előfizetésedet a hozzáférés visszaszerzéséhez." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Lejárt a(z) {productName} munkameneted. Jelentkezz be újra, hogy folytathasd az alkalmazás engedélyezését." @@ -40144,6 +40346,16 @@ msgstr "A közösségednek legalább {minMemberCount, plural, one {# tagra} othe msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Ellenőriztük a hitelkártyádat. Ha a kártya érvényes, a felnőtt tartalom hamarosan elérhetővé válik. Bezárhatod ezt a lapot, és visszatérhetsz az alkalmazásba." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "A grafikus környezeted nem állította be a rendszerszintű billentyűparancsokat." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "A grafikus környezeted akkor is futtatja ezeket a billentyűparancsokat, ha a(z) {productName} nincs fókuszban." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/id/messages.po b/fluxer_app/src/features/i18n/locales/id/messages.po index 434784ba8..afdcd4825 100644 --- a/fluxer_app/src/features/i18n/locales/id/messages.po +++ b/fluxer_app/src/features/i18n/locales/id/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# kanal mengikuti} other {# kanal mengikuti}} kanal msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# komunitas ditemukan} other {# komunitas ditemukan}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# komunitas sementara tidak tersedia karena kerusakan kapasitor fluks.} other {# komunitas sementara tidak tersedia karena kerusakan kapasitor fluks.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} menghindari pengiriman notifikasi push ke perangkat selule msgid "{productName} Bug Hunter" msgstr "Pemburu Bug {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} dapat meminta desktop Anda menjalankan pintasannya saat {productName} tidak dalam fokus. Desktop Anda akan meminta konfirmasi dan membiarkan Anda memilih tombolnya." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} tidak dapat lagi membaca keyboard Anda, jadi pintasan dideteksi melalui X11 sebagai gantinya." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} tidak dapat membaca keyboard Anda lagi, jadi pintasan desktop Anda digunakan sebagai gantinya." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} tidak dapat lagi membaca keyboard Anda. Pintasan hanya berfungsi saat {productName} dalam fokus." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} dapat mendaftarkan pintasannya ke Hyprland agar tetap berjalan saat {productName} tidak dalam fokus. Setelah itu, tetapkan tombolnya di konfigurasi Hyprland Anda." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} memerlukan akses kamera untuk menampilkan daftar perangkatmu." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} memerlukan akses mikrofon untuk menampilkan daftar perangkatmu." @@ -1837,6 +1862,11 @@ msgstr "{productName} kini berada di {host}. Instal dari sana." msgid "{productName} Partner" msgstr "Mitra {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} telah mendaftarkan pintasannya ke Hyprland. Tetapkan tombolnya di konfigurasi Hyprland Anda untuk menggunakannya saat {productName} tidak dalam fokus." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Profil yang menonjol" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri diperlukan jika cakupan bot bukan satu-satunya cakupan." @@ -2847,7 +2877,7 @@ msgstr "Hanya di semua DM yang terbuka" msgid "Across every community you're in" msgstr "Di semua komunitas yang kamu ikuti" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Tindakan" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Berbagi aktivitas" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Ambang aktivitas" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Tambahkan lapisan keamanan ekstra ke akunmu" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Tambahkan pintasan khusus lainnya" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Alamat" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Sesuaikan sendiri setiap pengaturan: peredam bising, pembatalan gema, dan penguatan." @@ -3696,11 +3726,11 @@ msgstr "Izinkan {everyoneMention} dan {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Izinkan {PRODUCT_NAME} mengakses kameramu di {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Izinkan {PRODUCT_NAME} mengakses mikrofonmu di {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Izinkan {PRODUCT_NAME} mengakses mikrofonmu. Periksa bilah alamat atau pengaturan browsermu." @@ -3803,7 +3833,7 @@ msgstr "Izinkan anggota tanpa izin {readMessageHistoryPermissionLabel} untuk mel msgid "Allow mentions for this role" msgstr "Izinkan sebutan untuk peran ini" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Izinkan mikrofon" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Otorisasi gagal" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Otorisasi gagal. Coba lagi." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Deteksi bahasa otomatis" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Ambang aktivitas otomatis" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Izin bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Izin bot tidak boleh negatif." @@ -6869,6 +6899,11 @@ msgstr "Ubah nama panggilan grup" msgid "Change icon" msgstr "Ubah ikon" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Ubah di pengaturan sistem" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Akses saluran" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Akses saluran ditolak" @@ -7294,7 +7329,7 @@ msgstr "ID saluran disalin ke papan klip" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "tautan saluran" @@ -7462,6 +7497,11 @@ msgstr "input obrolan" msgid "Chat shortcuts" msgstr "Pintasan obrolan" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Periksa lagi" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Pilih jendela di pemilih sistemmu" msgid "Choose an account" msgstr "Pilih akun" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Pilih tindakan." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Pilih latar belakang" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Pilih guild_id atau channel_id, jangan keduanya." @@ -7990,7 +8030,7 @@ msgstr "Membersihkan mikrofonmu agar suara lebih jernih." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Klik untuk membuka pemilih warna dan memilih warna baru." msgid "Click to react" msgstr "Klik untuk bereaksi" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Klik untuk merekam" @@ -9110,7 +9150,7 @@ msgstr "Konfirmasi" msgid "Confirmation or double-click for community voice joins" msgstr "Konfirmasi atau klik dua kali untuk bergabung dengan saluran suara komunitas" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Berbenturan dengan {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Hubungkan kamera atau perangkat perekam virtual, lalu coba lagi." msgid "Connect a display, then try again." msgstr "Sambungkan layar, lalu coba lagi." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Sambungkan mikrofon dan coba lagi." @@ -9700,6 +9740,21 @@ msgstr "Salin tautan undangan" msgid "Copy invite URL" msgstr "Salin URL undangan" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Salin baris" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Salin baris untuk hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Salin baris untuk hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Kustom" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Pilihan khusus" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Tindakan pintasan khusus" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Opsi pintasan khusus" @@ -11466,7 +11521,7 @@ msgstr "Pintasan default" msgid "Default translator" msgstr "Penerjemah default" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Default:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Hapus media tersimpan" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Hapus pintasan" @@ -12102,13 +12157,13 @@ msgstr "Status pengembang" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Alat pengembang" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Alat pengembang tersedia dari lencana staf di header saluran." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Nonaktifkan keuntungan premium backend" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Nonaktifkan pintasan bawaan" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Nonaktifkan pop-out gambar-dalam-gambar berbagi layar?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Nonaktifkan pintasan" @@ -12888,7 +12943,7 @@ msgstr "Donasi saja" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Tombol unduh" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Unduh aplikasi desktop" @@ -13204,6 +13259,11 @@ msgstr "Belanda" msgid "e.g., YubiKey, iPhone, work computer" msgstr "misalnya, YubiKey, iPhone, komputer kantor" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Setiap tombol hanya dapat menjalankan satu pintasan di seluruh sistem." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Edit tautan undangan" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Edit pintasan keyboard" @@ -13425,7 +13485,7 @@ msgstr "Formulir edit media tersimpan" msgid "Edit search engine" msgstr "Edit mesin pencari" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Edit pintasan" @@ -13753,20 +13813,16 @@ msgstr "Edit kosong" msgid "Empty favorites" msgstr "Favorit kosong" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Aktifkan" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Aktifkan izin {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Aktifkan akselerasi hardware dan mulai ulang" msgid "Enable hardware acceleration?" msgstr "Aktifkan akselerasi hardware?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Aktifkan akses input untuk menggunakan pintasan di seluruh sistem pada Wayland. Setelah mengubah akses input, tutup sepenuhnya dan mulai ulang {productName} agar pintasan dapat menggunakannya." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Aktifkan Shift untuk memperluas" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Aktifkan pintasan" @@ -14357,7 +14408,7 @@ msgstr "Seluruh audio sistem" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Suara masuk" @@ -14467,7 +14518,7 @@ msgstr "Waktu malam" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Meratakan volume mikrofonmu agar suaramu tidak terlalu pelan." @@ -15682,6 +15733,26 @@ msgstr "Hanya untuk orang Inggris..." msgid "For me" msgstr "Untuk saya" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Untuk tekan untuk bisukan, gunakan satu tombol tanpa Ctrl, Alt, atau Shift. Jika memakai kombinasi, lepaskan tombol utama terlebih dahulu, atau mikrofon bisa tetap dibisukan." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Untuk push-to-talk, gunakan satu tombol tanpa Ctrl, Alt, atau Shift. Jika memakai kombinasi, lepaskan tombol utama terlebih dahulu, atau mikrofon bisa tetap menyala." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Untuk pintasan ini, gunakan satu tombol tanpa Ctrl, Alt, atau Shift. Jika memakai kombinasi, lepaskan tombol utama terlebih dahulu, atau pintasan bisa tetap aktif." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Untuk prioritas aktivitas suara, gunakan satu tombol tanpa Ctrl, Alt, atau Shift. Jika memakai kombinasi, lepaskan tombol utama terlebih dahulu, atau prioritas bisa tetap aktif." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Demi keamananmu, konfirmasi dulu bahwa ini benar kamu untuk melanjutkan." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Profil global" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Pintasan global" @@ -17434,6 +17505,11 @@ msgstr "Tahan Shift dan tekan X untuk memilih aplikasi. Tekan {selectAllShortcut msgid "Hold to record. Drag up to lock, or release to send." msgstr "Tekan dan tahan untuk merekam. Seret ke atas untuk mengunci, atau lepas untuk mengirim." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Menahan Shift, Ctrl, atau Alt akan memblokir pintasan ini. Tambahkan alternatif di pengaturan sistem Anda." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Hore! Notifikasi diaktifkan" msgid "Hyperlink" msgstr "Tautan" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland tidak memiliki dialog pintasan. Tambahkan baris untuk versi Hyprland Anda ke konfigurasi Hyprland dan ganti KEY dengan tombol yang Anda inginkan." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland tidak memiliki dialog pintasan. Tetapkan tombolnya di konfigurasi Hyprland Anda." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (sebelum Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 dan seterusnya)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Saya setuju dengan <0>Kebijakan privasi" @@ -17576,7 +17672,7 @@ msgstr "Saya berubah pikiran" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Saya mengerti" @@ -18000,7 +18096,7 @@ msgstr "Di" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "Dalam mode \"Sistem operasi\", kamus yang tercantum di atas bersifat saran. {PRODUCT_NAME} akan meneruskan tag bahasa ini ke pemeriksa ejaan OS kamu jika didukung. Tag yang tidak dikenali akan diabaikan." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "Di browser, push-to-talk hanya berfungsi saat tab {PRODUCT_NAME} sedang aktif. Instal aplikasi desktop untuk push-to-talk di seluruh sistem." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Input" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Akses input tidak dapat diaktifkan secara otomatis. Ubah secara manual, lalu keluar sepenuhnya dan mulai ulang {productName} agar pintasan dapat menggunakannya." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Akses input tidak dapat diaktifkan. Coba lagi dari pengaturan {settingsMenuName}, lalu keluar sepenuhnya dan mulai ulang {productName}." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Akses input diperlukan untuk pintasan keyboard dan mouse di seluruh sistem pada Wayland. Setelah mengubah akses input, keluar sepenuhnya dan mulai ulang {productName} agar pintasan dapat menggunakannya." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Akses input telah diubah. Keluar sepenuhnya dan mulai ulang {productName} agar pintasan sistem dapat menggunakannya." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Input dan output" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Permintaan otorisasi tidak valid" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Izin bot tidak valid." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Kode tidak valid atau kedaluwarsa. Coba lagi." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "redirect_uri tidak valid" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Tautan undangan disembunyikan saat berbagi:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Tautan undangan disamarkan saat berbagi" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Lompat ke {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Lompat ke {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Lompat ke panggilan saat ini" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Lompat ke saluran tertaut" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Lompat ke pesan tertaut" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Lompat ke pesan dari {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Lompat ke pesan di {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Jaga kerahasiaan ini. Membuat ulang akan merusak integrasi yang ada." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Terus mengirimkan sinyal sebentar setelah tombol dilepas." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Keyboard" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Akses keyboard tidak dapat dimulai. Pintasan hanya berfungsi saat {productName} dalam fokus." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Biarkan komunitas memoderasi sendiri" msgid "Let the member hear others in this community again" msgstr "Membuat anggota kembali dapat mendengar orang lain di komunitas ini" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Memungkinkan {productName} membaca keyboard Anda secara langsung. Sistem Anda sudah mengizinkannya." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Izinkan CSS kustom menampilkan area jendela transparan setelah memulai ulang aplikasi desktop." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Gaya tautan" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Tautan tidak tersedia" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Tampilan dan nuansa" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Sepertinya kamu menggunakan {0}. Menumpuk pemrosesan {PRODUCT_NAME} di atasnya dapat menyebabkan pumping atau distorsi. Input langsung meneruskan audiomu tanpa perubahan." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Tata letak pesan" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "tautan pesan" @@ -21332,7 +21416,7 @@ msgstr "Mic" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Tes mikrofon" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Lain-lain" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "client_id tidak ada" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Opsi lainnya" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Opsi pintasan lainnya" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Tidak ada izin {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Tidak ada akses" @@ -22984,7 +23068,7 @@ msgstr "Tidak ada yang cocok" msgid "No mentions yet" msgstr "Belum ada sebutan" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Tidak ada mikrofon yang ditemukan" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Tidak ada pengaturan yang ditemukan" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Tidak ada pintasan yang disetel" @@ -23358,6 +23442,11 @@ msgstr "Bukan bilangan bulat" msgid "Not accepted" msgstr "Tidak diterima" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Belum ditetapkan" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Buka {host} di browsermu." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Buka {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Buka {labelText} di pengaturan" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "tautan halaman" @@ -25611,11 +25700,11 @@ msgstr "Kehadiran" msgid "Press {shortcut} to react" msgstr "Tekan {shortcut} untuk bereaksi" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Tekan tombol keyboard, mouse, atau gamepad untuk menetapkan. Escape untuk membatalkan." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Tekan pintasan…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Tombol push to talk" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Push-to-talk" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Push-to-talk (terbatas)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Push-to-talk belum memiliki tombol di seluruh sistem. Untuk mengaturnya, buka Pengaturan > Aplikasi > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Push-to-talk belum memiliki tombol di seluruh sistem. Untuk mengaturnya, buka Pengaturan Sistem > Papan Ketik > Pintasan > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Push-to-talk belum memiliki tombol di seluruh sistem. Untuk mengaturnya, buka pengaturan pintasan keyboard desktop Anda." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Push-to-talk diabaikan dalam input langsung. Beralih ke suara terfokus atau kustom untuk menggunakannya." @@ -26293,21 +26397,51 @@ msgstr "Push-to-talk diabaikan dalam input langsung. Beralih ke suara terfokus a msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Push-to-talk aktif. Tahan {pushToTalkHint} untuk bicara." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Push-to-talk aktif. Tahan tombol push-to-talk Anda untuk berbicara." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Push-to-talk hanya berfungsi saat {productName} dalam fokus karena desktop Anda tidak menyiapkan pintasan di seluruh sistem." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Push-to-talk hanya berfungsi saat {productName} dalam fokus hingga {permissionName} diaktifkan. Setelah mengubah izin, tutup sepenuhnya dan mulai ulang {productName} agar hotkey di seluruh sistem dapat menggunakannya." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Push-to-talk hanya berfungsi saat {productName} dalam fokus hingga Anda menyiapkan pintasan di seluruh sistem." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Push-to-talk hanya berfungsi saat tab browser sedang aktif." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Push-to-talk akan menggunakan tombol yang Anda atur di {productName}. Atur tombolnya terlebih dahulu, atau mikrofon Anda akan menggunakan aktivitas suara." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Push-to-talk tidak akan mengaktifkan mikrofonmu saat suara masuk dibisukan. Aktifkan lagi suara masukmu agar bisa bicara." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Push-to-talk hanya berfungsi di luar {productName} setelah Anda menetapkan tombolnya di konfigurasi Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Push-to-talk hanya berfungsi di luar {productName} setelah Anda menetapkan tombolnya di konfigurasi Hyprland. Tambahkan baris untuk versi Hyprland Anda dan ganti KEY dengan tombol yang Anda inginkan." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "JSON sematan mentah" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Aktifkan kembali pintasan bawaan" @@ -26565,13 +26699,6 @@ msgstr "Sebutan terbaru" msgid "Recent searches" msgstr "Pencarian terbaru" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Periksa ulang" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Direkomendasikan" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Direkomendasikan. Membersihkan mikrofonmu agar suara lebih jernih." @@ -26600,6 +26727,11 @@ msgstr "Sambungkan ulang" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Sambung ulang berhasil. Pilih layar lagi jika kamu ingin melanjutkan streaming sebelumnya." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Menghubungkan kembali ke desktop Anda…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Menyambungkan ulang…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Rekam pintasan" @@ -26851,7 +26983,7 @@ msgstr "Relatif" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Jeda lepas tombol" @@ -27739,13 +27871,13 @@ msgstr "Dikirim ulang" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Setel ulang" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Setel ulang \"{label}\" ke bawaan" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Setel ulang ke nilai sebenarnya" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Setel ulang ke bawaan" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Pilih akun yang ingin kamu gunakan untuk masuk di perangkat barumu." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Pilih mode pemrosesan suara" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Mengirim audio mikrofon mentah tanpa pemrosesan." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Mengirim audiomu tanpa perubahan. Paling cocok jika kamu menggunakan perangkat lunak audio eksternal." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Disetel oleh preset {preset}. Memilih nilai di sini akan beralih ke Kustom." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Atur pintasan khusus" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Atur pintasan khusus untuk \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "Mengatur wilayah suara ke {region}" msgid "Set the voice region to automatic" msgstr "Mengatur wilayah suara ke otomatis" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Siapkan" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Pengaturan" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "tautan pengaturan" @@ -30330,8 +30467,8 @@ msgstr "Kode singkat" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Beralih ke tata letak nyaman" msgid "Switch to dense layout" msgstr "Beralih ke tata letak rapat" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Beralih ke input langsung" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Instalasi yang dikelola sistem" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Pintasan di seluruh sistem" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Pintasan seluruh sistem tidak dapat diatur." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Seluruh sistem: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Seluruh sistem: Belum ditetapkan" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Halaman yang kamu cari tidak ada atau sudah dipindahkan." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "redirect_uri yang diberikan tidak terdaftar untuk aplikasi ini." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "redirect_uri yang diberikan harus menggunakan HTTP atau HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Animasi ini terlalu panjang atau terlalu besar untuk dipotong. Coba file yang lebih pendek atau lebih kecil." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Aplikasi ini" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Lampiran ini sudah kedaluwarsa" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Bot ini tidak publik dan tidak dapat ditambahkan oleh pengguna lain." @@ -34081,6 +34233,16 @@ msgstr "Percakapan ini sudah tidak tersedia." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Versi desktop ini tidak dapat menginstal pembaruan di dalam {productName}. Unduh penginstal desktop terbaru sebagai gantinya." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Desktop ini tidak menyediakan pintasan di seluruh sistem. Push-to-talk hanya berfungsi saat {productName} dalam fokus." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Desktop ini tidak menyediakan pintasan di seluruh sistem. Pintasan tetap berfungsi saat {productName} dalam fokus." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Ini passkey terakhirmu dan passkey adalah faktor keduamu. Menghapusnya a msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Ini passkey terakhirmu. Setelah dihapus, kamu tidak bisa lagi menggunakan passkey sebagai faktor keduamu." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Tombol ini berfungsi saat {productName} dalam fokus. Tombol di seluruh sistem diatur oleh desktop Anda, di {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Saluran tautan ini ditandai untuk konten dewasa dan mungkin membuka materi yang tidak pantas untuk sebagian pengguna." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Tautan ini tidak tersedia untuk akunmu." @@ -34778,6 +34945,21 @@ msgstr "Bilah judul" msgid "Title bar platform" msgstr "Platform bilah judul" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Untuk mengubah tombolnya, buka Pengaturan > Aplikasi > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Untuk mengubah tombolnya, buka Pengaturan Sistem > Papan Ketik > Pintasan > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Untuk mengubah tombolnya, buka pengaturan pintasan keyboard desktop Anda." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Untuk membuat webhook, buka <0>{channelWebhooksSettingsPath}. Kamu masih bisa mengedit dan mengatur semua webhook yang ada di sini." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Tidak diketahui" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Aplikasi tidak dikenal, sesi kedaluwarsa, atau permintaan otorisasi tidak valid." @@ -36410,6 +36592,11 @@ msgstr "Hingga 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "Hingga 720p pada 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Perbarui {productName} untuk menggunakan pintasan seluruh sistem di Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Gunakan tema gelap di seluruh aplikasi" msgid "Use default" msgstr "Gunakan default" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Gunakan akses perangkat input langsung" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Peserta obrolan suara" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Pemrosesan suara" @@ -38264,7 +38456,7 @@ msgstr "Aktifkan suara masuk" msgid "Voice unmute" msgstr "Aktifkan mikrofon" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Suara menggunakan perangkat output sistem Anda di browser ini. Ubah di pengaturan sistem Anda." @@ -38278,7 +38470,7 @@ msgstr "Dibatalkan" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Menunggu mode lambat di satu atau beberapa saluran yang dipilih berakhir msgid "Waiting for you to..." msgstr "Menunggu kamu untuk..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Menunggu desktop Anda…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Percakapan mana" msgid "Which rule was broken?" msgstr "Aturan mana yang dilanggar?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Saat push-to-talk aktif, pintasanmu yang mengontrol mikrofon, jadi tombol bisukan dinonaktifkan." @@ -39094,6 +39291,11 @@ msgstr "Tanpa izin ini, push-to-talk wajib digunakan." msgid "Wordmark" msgstr "Wordmark" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Berfungsi saat {productName} dalam fokus" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Kamu mengubah tag saat menggunakan {PREMIUM_PRODUCT_NAME}. Tag akan diacak ulang secara otomatis saat {PREMIUM_PRODUCT_NAME} kamu berakhir, kecuali jika diperpanjang." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Kamu tidak memiliki akses ke saluran tempat pesan ini dikirim." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Langganan {premiumProductName} kamu telah berakhir. Kamu kehilangan semua keuntungan {premiumProductName}. Aktifkan kembali langgananmu untuk mendapatkan akses lagi." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Sesi {productName} kamu telah berakhir. Masuk lagi untuk melanjutkan otorisasi aplikasi ini." @@ -40144,6 +40346,16 @@ msgstr "Komunitasmu perlu memiliki setidaknya {minMemberCount, plural, other {# msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Kartu kreditmu sudah diperiksa. Jika kartunya valid, konten dewasa akan segera terbuka. Kamu bisa menutup tab ini dan kembali ke aplikasi." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Desktop Anda tidak mengatur pintasan di seluruh sistem." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Desktop Anda menjalankan pintasan ini bahkan saat {productName} tidak dalam fokus." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/it/messages.po b/fluxer_app/src/features/i18n/locales/it/messages.po index b21c08e79..632e77c43 100644 --- a/fluxer_app/src/features/i18n/locales/it/messages.po +++ b/fluxer_app/src/features/i18n/locales/it/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# canale segue} other {# canali seguono}} questo ca msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# community trovata} other {# community trovate}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# community è temporaneamente non disponibile a causa di un malfunzionamento del condensatore di flusso.} other {# community sono temporaneamente non disponibili a causa di un malfunzionamento del condensatore di flusso.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} evita di inviare notifiche push ai tuoi dispositivi mobili msgid "{productName} Bug Hunter" msgstr "Cacciatore di bug di {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} può chiedere al tuo desktop di eseguire le sue scorciatoie anche quando {productName} non è in primo piano. Il tuo desktop ti chiederà di confermare e ti permetterà di scegliere i tasti." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} non può più leggere la tua tastiera, quindi ascolta le scorciatoie tramite X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} non può più leggere la tua tastiera, quindi vengono usate le scorciatoie del tuo desktop." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} non può più leggere la tua tastiera. Le scorciatoie funzionano solo quando {productName} è in primo piano." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} può registrare le sue scorciatoie in Hyprland in modo che funzionino anche quando {productName} non è in primo piano. Poi associ i tasti nella tua configurazione di Hyprland." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} ha bisogno dell'accesso alla fotocamera per elencare i tuoi dispositivi." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} ha bisogno dell'accesso al microfono per elencare i tuoi dispositivi." @@ -1837,6 +1862,11 @@ msgstr "{productName} ora si trova su {host}. Installalo da lì." msgid "{productName} Partner" msgstr "Partner di {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} ha registrato le sue scorciatoie in Hyprland. Associale nella tua configurazione di Hyprland per usarle quando {productName} non è in primo piano." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Un profilo che si distingue" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri è obbligatorio quando bot non è l’unico ambito di autorizzazione richiesto." @@ -2847,7 +2877,7 @@ msgstr "Solo in tutti i DM aperti" msgid "Across every community you're in" msgstr "In tutte le community di cui fai parte" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Azione" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Condivisione attività" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Soglia attività" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Aggiungi un ulteriore livello di sicurezza al tuo account" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Aggiungi un'altra scorciatoia personalizzata" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Indirizzo" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Regola tu stesso ogni impostazione: soppressione del rumore, cancellazione dell'eco e guadagno." @@ -3696,11 +3726,11 @@ msgstr "Consenti {everyoneMention} e {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Consenti a {PRODUCT_NAME} di accedere alla tua fotocamera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Consenti a {PRODUCT_NAME} di accedere al microfono in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Consenti a {PRODUCT_NAME} di accedere al microfono. Controlla la barra degli indirizzi o le impostazioni del browser." @@ -3803,7 +3833,7 @@ msgstr "Consenti ai membri senza il permesso {readMessageHistoryPermissionLabel} msgid "Allow mentions for this role" msgstr "Consenti menzioni per questo ruolo" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Consenti accesso al microfono" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Autorizzazione non riuscita" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Autorizzazione non riuscita. Riprova." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Rileva lingua automaticamente" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Soglia attività automatica" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Permessi bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "I permessi del bot non possono essere negativi." @@ -6869,6 +6899,11 @@ msgstr "Cambia soprannome del gruppo" msgid "Change icon" msgstr "Cambia icona" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Modifica nelle impostazioni di sistema" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Accesso al canale" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Accesso al canale negato" @@ -7294,7 +7329,7 @@ msgstr "ID canale copiato negli appunti" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "link al canale" @@ -7462,6 +7497,11 @@ msgstr "campo di inserimento della chat" msgid "Chat shortcuts" msgstr "Scorciatoie chat" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Ricontrolla" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Scegli una finestra nel selettore di sistema" msgid "Choose an account" msgstr "Scegli un account" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Scegli un'azione." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Scegli sfondo" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Scegli guild_id o channel_id, non entrambi." @@ -7990,7 +8030,7 @@ msgstr "Ripulisce l’audio del microfono per rendere più chiara la voce." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Clicca per aprire il selettore colori e sceglierne uno nuovo." msgid "Click to react" msgstr "Fai clic per reagire" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Fai clic per registrare" @@ -9110,7 +9150,7 @@ msgstr "Conferma" msgid "Confirmation or double-click for community voice joins" msgstr "Conferma o doppio clic per partecipare alle chat vocali" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "In conflitto con {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Collega una fotocamera o un dispositivo di acquisizione virtuale, quindi msgid "Connect a display, then try again." msgstr "Collega un display, poi riprova." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Connetti un microfono e riprova." @@ -9700,6 +9740,21 @@ msgstr "Copia link d'invito" msgid "Copy invite URL" msgstr "Copia link d'invito" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Copia righe" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Copia righe per hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Copia righe per hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Personalizzato" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Selezione personalizzata" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Azione scorciatoia personalizzata" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Opzioni scorciatoie personalizzate" @@ -11466,7 +11521,7 @@ msgstr "Scorciatoie predefinite" msgid "Default translator" msgstr "Traduttore predefinito" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Predefinito:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Elimina elemento multimediale salvato" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Elimina scorciatoia" @@ -12102,13 +12157,13 @@ msgstr "Stato sviluppatore" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Strumenti per sviluppatori" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Gli strumenti per sviluppatori sono disponibili dal badge dello staff nell'intestazione del canale." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Disabilita i vantaggi premium del backend" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Disabilita scorciatoia integrata" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Disattivare le finestre picture-in-picture della condivisione schermo?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Disabilita scorciatoia" @@ -12888,7 +12943,7 @@ msgstr "Dona invece" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Pulsante di download" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Scarica l'app desktop" @@ -13204,6 +13259,11 @@ msgstr "Olandese" msgid "e.g., YubiKey, iPhone, work computer" msgstr "es. YubiKey, iPhone, computer di lavoro" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Ogni tasto può eseguire solo una scorciatoia a livello di sistema." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Modifica link d'invito" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Modifica scorciatoia da tastiera" @@ -13425,7 +13485,7 @@ msgstr "Modulo per modificare un elemento multimediale salvato" msgid "Edit search engine" msgstr "Modifica motore di ricerca" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Modifica scorciatoia" @@ -13753,20 +13813,16 @@ msgstr "Modifica vuota" msgid "Empty favorites" msgstr "Preferiti vuoti" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Abilita" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Abilita l'autorizzazione {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Abilita accelerazione hardware e riavvia" msgid "Enable hardware acceleration?" msgstr "Abilitare l'accelerazione hardware?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Abilita l'accesso all'input per usare le scorciatoie di sistema su Wayland. Dopo aver modificato l'accesso all'input, chiudi completamente e riavvia {productName} in modo che le scorciatoie possano usarlo." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Abilita Maiusc per espandere" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Abilita scorciatoia" @@ -14357,7 +14408,7 @@ msgstr "Audio dell’intero sistema" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Suono d'ingresso" @@ -14467,7 +14518,7 @@ msgstr "Sera" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Uniforma il volume del microfono per evitare che la tua voce sia troppo bassa." @@ -15682,6 +15733,26 @@ msgstr "Solo per i britannici..." msgid "For me" msgstr "Per me" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Per \"Premi per disattivare il microfono\", usa un singolo tasto senza Ctrl, Alt o Maiusc. Con una combinazione, rilascia prima il tasto principale o il microfono potrebbe rimanere disattivato." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Per il push-to-talk, usa un singolo tasto senza Ctrl, Alt o Maiusc. Con una combinazione, rilascia prima il tasto principale o il microfono potrebbe rimanere attivo." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Per questa scorciatoia, usa un singolo tasto senza Ctrl, Alt o Maiusc. Con una combinazione, rilascia prima il tasto principale o la scorciatoia potrebbe rimanere attiva." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Per \"Priorità attività vocale\", usa un singolo tasto senza Ctrl, Alt o Maiusc. Con una combinazione, rilascia prima il tasto principale o la priorità potrebbe rimanere attiva." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Per la tua sicurezza, conferma la tua identità per continuare." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Profilo globale" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Scorciatoia globale" @@ -17434,6 +17505,11 @@ msgstr "Tieni premuto Maiusc e premi X per selezionare le app. Premi {selectAllS msgid "Hold to record. Drag up to lock, or release to send." msgstr "Tieni premuto per registrare. Trascina in alto per bloccare, o rilascia per inviare." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Tenere premuto Maiusc, Ctrl o Alt blocca questa scorciatoia. Aggiungi alternative nelle impostazioni di sistema." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Evviva! Le notifiche sono attivate" msgid "Hyperlink" msgstr "Collegamento ipertestuale" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland non ha una finestra di dialogo per le scorciatoie. Aggiungi le righe per la tua versione di Hyprland alla configurazione di Hyprland e sostituisci KEY con il tasto che desideri." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland non ha una finestra di dialogo per le scorciatoie. Associa i tasti nella tua configurazione di Hyprland." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (prima di Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 e versioni successive)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Accetto l’<0>Informativa sulla privacy" @@ -17576,7 +17672,7 @@ msgstr "Ho cambiato idea" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Ho capito" @@ -18000,7 +18096,7 @@ msgstr "In" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "In modalità \"Sistema operativo\", i dizionari elencati sopra sono indicativi. {PRODUCT_NAME} passerà questi tag lingua al correttore ortografico del tuo sistema operativo, se supportato. I tag non riconosciuti verranno ignorati." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "In un browser, il push-to-talk funziona solo quando la scheda di {PRODUCT_NAME} è attiva. Installa l’app desktop per usare il push-to-talk a livello di sistema." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Input" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Non è stato possibile abilitare automaticamente l’accesso all’input. Modificalo manualmente, quindi chiudi completamente e riavvia {productName} per consentire alle scorciatoie di usarlo." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Impossibile abilitare l'accesso all'input. Riprova dalle impostazioni di {settingsMenuName}, quindi chiudi completamente e riavvia {productName}." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "L'accesso all'input è richiesto per le scorciatoie di tastiera e mouse a livello di sistema su Wayland. Dopo aver modificato l'accesso all'input, chiudi completamente e riavvia {productName} in modo che le scorciatoie possano usarlo." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "L'accesso all'input è stato modificato. Esci completamente e riavvia {productName} in modo che possa usare le scorciatoie di sistema." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Input e output" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Richiesta di autorizzazione non valida" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Permessi bot non validi." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Codice non valido o scaduto. Riprova." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "redirect_uri non valido" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Il link d'invito è nascosto durante la condivisione:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Link d'invito mascherato durante la condivisione" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Vai a {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Vai a {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Vai alla chiamata attuale" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Vai al canale collegato" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Vai al messaggio collegato" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Vai al messaggio di {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Vai al messaggio in {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Conservale al sicuro. La rigenerazione interromperà le integrazioni esistenti." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Continua a trasmettere per breve tempo dopo aver rilasciato il pulsante." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Tastiera" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Impossibile avviare l'accesso alla tastiera. Le scorciatoie funzionano solo quando {productName} è in primo piano." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Consenti alla community di auto-moderarsi" msgid "Let the member hear others in this community again" msgstr "Consentito al membro di sentire di nuovo gli altri in questa community" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Permette a {productName} di leggere direttamente la tastiera. Il tuo sistema lo consente già." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Consente al CSS personalizzato di rivelare le aree trasparenti della finestra dopo aver riavviato l'app desktop." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Stile link" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Link non disponibile" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Aspetto" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Sembra che tu stia usando {0}. L'elaborazione di {PRODUCT_NAME} in aggiunta può causare pumping o distorsione. L'input diretto lascia il tuo audio inalterato." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Layout messaggi" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "link del messaggio" @@ -21332,7 +21416,7 @@ msgstr "Microfono" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Prova microfono" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Varie" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "client_id mancante" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Altre opzioni" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Altre opzioni scorciatoia" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Nessun permesso {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Nessun accesso" @@ -22984,7 +23068,7 @@ msgstr "Nessun risultato" msgid "No mentions yet" msgstr "Nessuna menzione ancora" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Nessun microfono trovato" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Nessuna impostazione trovata" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Nessuna scorciatoia impostata" @@ -23358,6 +23442,11 @@ msgstr "Non è un numero intero" msgid "Not accepted" msgstr "Non accettato" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Non assegnato" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Apri {host} nel tuo browser." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Apri {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Apri {labelText} nelle impostazioni" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "PagGiù" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "link alla pagina" @@ -25611,11 +25700,11 @@ msgstr "Presenza" msgid "Press {shortcut} to react" msgstr "Premi {shortcut} per reagire" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Premi un tasto, un pulsante del mouse o del gamepad per associare. Esc annulla." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Premi una combinazione di tasti…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Tasto \"Premi per parlare\"" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Push-to-talk" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Premi per parlare (limitato)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Il push-to-talk non ha ancora un tasto a livello di sistema. Per impostarne uno, vai su Impostazioni > Applicazioni > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Il push-to-talk non ha ancora un tasto a livello di sistema. Per impostarne uno, vai su Impostazioni di sistema > Tastiera > Scorciatoie > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Il push-to-talk non ha ancora un tasto a livello di sistema. Per impostarne uno, apri le impostazioni delle scorciatoie da tastiera del tuo desktop." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Il push-to-talk viene ignorato in modalità \"Input diretto\". Passa a \"Voce isolata\" o \"Personalizzato\" per usarlo." @@ -26293,21 +26397,51 @@ msgstr "Il push-to-talk viene ignorato in modalità \"Input diretto\". Passa a \ msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Push-to-talk attivo. Tieni premuto {pushToTalkHint} per parlare." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Push-to-talk attivo. Tieni premuto il tasto del push-to-talk per parlare." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Il push-to-talk funziona solo quando {productName} è in primo piano perché il tuo desktop non ha configurato le scorciatoie a livello di sistema." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Il push-to-talk funziona solo quando {productName} è in primo piano finché non viene abilitato {permissionName}. Dopo aver modificato l'autorizzazione, chiudi completamente e riavvia {productName} in modo che i tasti di scelta rapida a livello di sistema possano usarlo." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Il push-to-talk funziona solo quando {productName} è in primo piano finché non configuri le scorciatoie a livello di sistema." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Il \"Premi per parlare\" funziona solo quando la scheda del browser è attiva." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Il push-to-talk userà il tasto impostato in {productName}. Impostane prima uno, altrimenti il microfono userà l'attività vocale." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Il push-to-talk non riattiva il microfono quando hai disattivato l’audio. Riattiva l’audio per parlare." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Il push-to-talk funziona al di fuori di {productName} solo dopo averlo associato nella configurazione di Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Il push-to-talk funziona al di fuori di {productName} solo dopo averlo associato nella configurazione di Hyprland. Aggiungi la riga per la tua versione di Hyprland e sostituisci KEY con il tasto desiderato." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "JSON grezzo del contenuto incorporato" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Riabilita scorciatoia integrata" @@ -26565,13 +26699,6 @@ msgstr "Menzioni recenti" msgid "Recent searches" msgstr "Ricerche recenti" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Verifica di nuovo" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Consigliato" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Consigliato. Migliora l'audio del microfono per una voce più chiara." @@ -26600,6 +26727,11 @@ msgstr "Riconnetti" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Riconnessione riuscita. Scegli di nuovo uno schermo se vuoi riprendere il tuo stream precedente." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Riconnessione al desktop in corso…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Riconnessione…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Registra scorciatoia" @@ -26851,7 +26983,7 @@ msgstr "Relativo" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Ritardo di rilascio" @@ -27739,13 +27871,13 @@ msgstr "Inviato di nuovo" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Reimposta" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Ripristina il valore predefinito di \"{label}\"" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Ripristina i valori effettivi" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Ripristina i valori predefiniti" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Seleziona l'account con cui vuoi accedere sul tuo nuovo dispositivo." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Seleziona la modalità di elaborazione vocale" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Invia l'audio grezzo del microfono senza elaborazione." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Invia l'audio senza modifiche. Ideale se usi software audio esterni." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Impostato dalla preselezione {preset}. La scelta di un valore qui passa a Personalizzato." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Imposta scorciatoia personalizzata" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Imposta scorciatoia personalizzata per \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "Impostata la regione vocale su {region}" msgid "Set the voice region to automatic" msgstr "Impostata la regione vocale su automatica" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Configura" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Impostazioni" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "link alle impostazioni" @@ -30330,8 +30467,8 @@ msgstr "Codici brevi" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Passa al layout ampio" msgid "Switch to dense layout" msgstr "Passa al layout compatto" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Passa all'input diretto" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Installazione gestita dal sistema" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Scorciatoie a livello di sistema" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Impossibile configurare le scorciatoie a livello di sistema." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "A livello di sistema: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "A livello di sistema: non assegnato" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "La pagina che cerchi non esiste o è stata spostata." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Il valore redirect_uri fornito non è registrato per questa applicazione." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Il valore redirect_uri fornito deve usare HTTP o HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Questa animazione è troppo lunga o troppo grande per essere ritagliata. Prova un file più corto o più piccolo." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Questa applicazione" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Questo allegato è scaduto" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Questo bot non è pubblico e non può essere aggiunto da altri utenti." @@ -34081,6 +34233,16 @@ msgstr "Questa conversazione non è più disponibile." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Questa versione per desktop non può installare gli aggiornamenti all'interno di {productName}. Scarica invece l'ultima versione dell'installer per desktop." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Questo desktop non offre scorciatoie a livello di sistema. Il push-to-talk funziona solo quando {productName} è in primo piano." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Questo desktop non offre scorciatoie a livello di sistema. Le scorciatoie funzionano comunque quando {productName} è in primo piano." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Questa è la tua ultima passkey e le passkey sono il tuo secondo fattore msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Questa è la tua ultima passkey. Una volta eliminata, non potrai più usare una passkey come secondo fattore." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Questo tasto funziona quando {productName} è in primo piano. Il tasto a livello di sistema lo imposta il tuo desktop, in {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Questo canale link è contrassegnato per contenuti per adulti e potrebbe aprire materiale non adatto ad alcuni utenti." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Questo link non è disponibile per il tuo account." @@ -34778,6 +34945,21 @@ msgstr "Barra del titolo" msgid "Title bar platform" msgstr "Piattaforma della barra del titolo" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Per cambiare i tasti, vai su Impostazioni > Applicazioni > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Per cambiare i tasti, vai su Impostazioni di sistema > Tastiera > Scorciatoie > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Per cambiare i tasti, apri le impostazioni delle scorciatoie da tastiera del tuo desktop." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Per creare un webhook, apri <0>{channelWebhooksSettingsPath}. Puoi comunque modificare e organizzare tutti i webhook esistenti qui." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Sconosciuto" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Applicazione sconosciuta, sessione scaduta o richiesta di autorizzazione non valida." @@ -36410,6 +36592,11 @@ msgstr "Fino a 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "Fino a 720p a 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Aggiorna {productName} per usare le scorciatoie a livello di sistema su Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Usa il tema scuro in tutta l'applicazione" msgid "Use default" msgstr "Usa predefinito" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Usa l'accesso diretto ai dispositivi di input" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Partecipanti alla chat vocale" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Elaborazione voce" @@ -38264,7 +38456,7 @@ msgstr "Audio riattivato" msgid "Voice unmute" msgstr "Microfono riattivato" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "La voce utilizza il dispositivo di output del tuo sistema in questo browser. Modificalo nelle impostazioni di sistema." @@ -38278,7 +38470,7 @@ msgstr "Annullato" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "In attesa che la modalità lenta scada in uno o più canali selezionati. msgid "Waiting for you to..." msgstr "In attesa che tu..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "In attesa del tuo desktop…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Quali conversazioni" msgid "Which rule was broken?" msgstr "Quale regola è stata infranta?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Quando il push-to-talk è attivo, la scorciatoia controlla il microfono, quindi il pulsante per disattivarlo è disabilitato." @@ -39094,6 +39291,11 @@ msgstr "Senza questo permesso, è necessario usare il push-to-talk." msgid "Wordmark" msgstr "Wordmark" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Funziona quando {productName} è in primo piano" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Hai cambiato il tuo tag mentre avevi {PREMIUM_PRODUCT_NAME}. Verrà rigenerato casualmente alla scadenza di {PREMIUM_PRODUCT_NAME}, a meno che non venga rinnovato." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Non hai accesso al canale in cui è stato inviato questo messaggio." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Il tuo abbonamento a {premiumProductName} è scaduto. Hai perso tutti i vantaggi di {premiumProductName}. Riattiva l'abbonamento per riottenere l'accesso." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "La tua sessione di {productName} è scaduta. Accedi di nuovo per continuare ad autorizzare questa applicazione." @@ -40144,6 +40346,16 @@ msgstr "La tua community deve avere almeno {minMemberCount, plural, one {# membr msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "La tua carta di credito è stata verificata. Se la carta è valida, i contenuti per adulti saranno sbloccati a breve. Puoi chiudere questa scheda e tornare all'app." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Il tuo desktop non ha configurato le scorciatoie a livello di sistema." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Il tuo desktop esegue queste scorciatoie anche quando {productName} non è in primo piano." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/ja/messages.po b/fluxer_app/src/features/i18n/locales/ja/messages.po index cd598eb1f..ef44dc1b8 100644 --- a/fluxer_app/src/features/i18n/locales/ja/messages.po +++ b/fluxer_app/src/features/i18n/locales/ja/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# チャンネルがフォローしています} ot msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {#件のコミュニティが見つかりました} other {#件のコミュニティが見つかりました}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {#件のコミュニティがフラックスキャパシタの不具合により一時的に利用できません。} other {#件のコミュニティがフラックスキャパシタの不具合により一時的に利用できません。}}" @@ -1708,6 +1708,31 @@ msgstr "{productName}は、パソコンを使用中はモバイルデバイス msgid "{productName} Bug Hunter" msgstr "{productName}バグハンター" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName}は、{productName}がフォーカスされていない間もショートカットを実行するようデスクトップに依頼できます。デスクトップで確認が求められ、キーを選択できます。" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName}がキーボードを読み取れなくなったため、代わりにX11経由でショートカットを受け付けます。" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName}がキーボードを読み取れなくなったため、代わりにデスクトップのショートカットが使用されます。" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName}がキーボードを読み取れなくなりました。ショートカットは{productName}がフォーカスされている間のみ機能します。" + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName}がフォーカスされていない間もショートカットが動作するよう、{productName}はショートカットをHyprlandに登録できます。登録後、Hyprlandの設定でキーをバインドしてください。" + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName}がデバイスを一覧表示するには、カメラへのアクセスが必要です。" #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName}がデバイスを一覧表示するには、マイクへのアクセスが必要です。" @@ -1837,6 +1862,11 @@ msgstr "{productName}は{host}に移動しました。そちらからインス msgid "{productName} Partner" msgstr "{productName}パートナー" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName}はショートカットをHyprlandに登録しました。{productName}がフォーカスされていない間も使うには、Hyprlandの設定でバインドしてください。" + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "個性を際立たせるプロフィール" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "ボットスコープが唯一のスコープではない場合、redirect_uriが必要です。" @@ -2847,7 +2877,7 @@ msgstr "開いているすべてのDMのみ" msgid "Across every community you're in" msgstr "参加しているすべてのコミュニティ" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "アクション" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "アクティビティの共有" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "アクティビティしきい値" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "アカウントのセキュリティをさらに強化します" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "カスタムショートカットをもう1つ追加" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "アドレス" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "ノイズ抑制、エコーキャンセル、ゲインを自分で調整します。" @@ -3696,11 +3726,11 @@ msgstr "{everyoneMention}と{hereMention}を許可する" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "{MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME} で、{PRODUCT_NAME}がカメラにアクセスすることを許可してください。" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "{PRODUCT_NAME}がマイクにアクセスできるようにするには、{MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}で設定してください。" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "{PRODUCT_NAME}がマイクにアクセスすることを許可してください。ブラウザのアドレスバーまたは設定を確認してください。" @@ -3803,7 +3833,7 @@ msgstr "「{readMessageHistoryPermissionLabel}」権限がないメンバーが msgid "Allow mentions for this role" msgstr "このロールへのメンションを許可" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "マイクを許可" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "認証に失敗しました" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "認証に失敗しました。もう一度お試しください。" @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "言語を自動検出" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "アクティビティしきい値を自動設定" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "ボットの権限" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "ボットの権限に負の値は指定できません。" @@ -6869,6 +6899,11 @@ msgstr "グループニックネームを変更" msgid "Change icon" msgstr "アイコンを変更" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "システム設定で変更" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "チャンネルアクセス" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "チャンネルへのアクセスが拒否されました" @@ -7294,7 +7329,7 @@ msgstr "チャンネルIDをクリップボードにコピーしました" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "チャンネルリンク" @@ -7462,6 +7497,11 @@ msgstr "チャット入力" msgid "Chat shortcuts" msgstr "チャットショートカット" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "再確認" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "システムピッカーでウィンドウを選択" msgid "Choose an account" msgstr "アカウントを選択" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "アクションを選択してください。" @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "背景を選択" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "guild_id または channel_id のいずれかを選択してください。両方は選択できません。" @@ -7990,7 +8030,7 @@ msgstr "マイクの音声をクリアにします。" #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "クリックしてカラーピッカーを開き、新しい色を選択 msgid "Click to react" msgstr "クリックしてリアクション" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "クリックして録音" @@ -9110,7 +9150,7 @@ msgstr "確認" msgid "Confirmation or double-click for community voice joins" msgstr "コミュニティボイスへの参加時の確認またはダブルクリック" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "{conflictLabel}と競合しています" @@ -9127,7 +9167,7 @@ msgstr "カメラまたは仮想キャプチャデバイスを接続してから msgid "Connect a display, then try again." msgstr "ディスプレイを接続してから、もう一度お試しください。" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "マイクを接続して、もう一度お試しください。" @@ -9700,6 +9740,21 @@ msgstr "招待リンクをコピー" msgid "Copy invite URL" msgstr "招待URLをコピー" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "行をコピー" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "行をコピー(hyprland.conf用)" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "行をコピー(hyprland.lua用)" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "カスタム" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "カスタム選択" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "カスタムショートカットアクション" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "カスタムショートカットオプション" @@ -11466,7 +11521,7 @@ msgstr "デフォルトのショートカット" msgid "Default translator" msgstr "デフォルトの翻訳ツール" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "デフォルト:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "保存済みメディアを削除" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "ショートカットを削除" @@ -12102,13 +12157,13 @@ msgstr "開発者向けステータス" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "開発者ツール" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "開発者ツールは、チャンネルヘッダーのスタッフバッジから利用できます。" @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "バックエンドのプレミアム特典を無効にする" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "組み込みのショートカットを無効にする" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "画面共有のピクチャーインピクチャーポップアウトを無効にしますか?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "ショートカットを無効にする" @@ -12888,7 +12943,7 @@ msgstr "代わりに寄付する" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "ダウンロードボタン" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "デスクトップ版アプリをダウンロード" @@ -13204,6 +13259,11 @@ msgstr "オランダ語" msgid "e.g., YubiKey, iPhone, work computer" msgstr "例: YubiKey、iPhone、会社のPC" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "各キーで実行できるシステム全体のショートカットは1つだけです。" + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "招待リンクを編集" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "キーボードショートカットを編集" @@ -13425,7 +13485,7 @@ msgstr "保存済みメディア編集フォーム" msgid "Edit search engine" msgstr "検索エンジンを編集" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "ショートカットを編集" @@ -13753,20 +13813,16 @@ msgstr "空の編集" msgid "Empty favorites" msgstr "お気に入りはありません" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "有効にする" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "{permissionName}の権限を有効にする" @@ -13876,11 +13932,6 @@ msgstr "ハードウェアアクセラレーションを有効にして再起動 msgid "Enable hardware acceleration?" msgstr "ハードウェアアクセラレーションを有効にしますか?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Waylandでシステム全体のショートカットを使用するには、入力アクセスを有効にしてください。入力アクセスを変更した後、ショートカットが使用できるように{productName}を完全に終了して再起動してください。" - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Shiftキーで展開" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "ショートカットを有効にする" @@ -14357,7 +14408,7 @@ msgstr "システム全体の音声" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "入室サウンド" @@ -14467,7 +14518,7 @@ msgstr "夜" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "マイクの音量を均一にして、小さくなりすぎないようにします。" @@ -15682,6 +15733,26 @@ msgstr "英国の方だけのお楽しみ…" msgid "For me" msgstr "自分用" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "プッシュトゥミュートには、Ctrl、Alt、Shiftを含まない単一のキーを使用してください。組み合わせキーの場合は、先にメインキーを離さないとマイクがミュートのままになることがあります。" + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "プッシュトゥトークには、Ctrl、Alt、Shiftを含まない単一のキーを使用してください。組み合わせキーの場合は、先にメインキーを離さないとマイクがオンのままになることがあります。" + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "このショートカットには、Ctrl、Alt、Shiftを含まない単一のキーを使用してください。組み合わせキーの場合は、先にメインキーを離さないとショートカットが有効なままになることがあります。" + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "音声検出の優先には、Ctrl、Alt、Shiftを含まない単一のキーを使用してください。組み合わせキーの場合は、先にメインキーを離さないと優先がオンのままになることがあります。" + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "セキュリティ保護のため、ご本人確認をお願いします。" @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "グローバルプロフィール" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "グローバルショートカット" @@ -17434,6 +17505,11 @@ msgstr "Shiftキーを押しながらXキーを押すとアプリを選択でき msgid "Hold to record. Drag up to lock, or release to send." msgstr "長押しで録音。上にドラッグしてロック、指を離して送信。" +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Shift、Ctrl、Altを押している間は、このショートカットは反応しません。システム設定で代替キーを追加してください。" + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "やったー!通知が有効になりました" msgid "Hyperlink" msgstr "ハイパーリンク" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprlandにはショートカットのダイアログがありません。お使いのHyprlandのバージョンに合った行をHyprlandの設定に追加し、KEYを使いたいキーに置き換えてください。" + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprlandにはショートカットのダイアログがありません。Hyprlandの設定でキーをバインドしてください。" + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf(Hyprland 0.55より前)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua(Hyprland 0.55以降)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "<0>プライバシーポリシーに同意します" @@ -17576,7 +17672,7 @@ msgstr "やっぱりやめます" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "内容を理解しました" @@ -18000,7 +18096,7 @@ msgstr "チャンネル内" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "「OS」モードでは、上記の辞書は参考として扱われます。{PRODUCT_NAME}は、サポートされている場合、これらの言語タグをOSのスペルチェッカーに渡します。認識されないタグは無視されます。" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "ブラウザでは、プッシュトゥトークは{PRODUCT_NAME}タブがフォーカスされている間のみ機能します。システム全体でプッシュトゥトークを使用するには、デスクトップアプリをインストールしてください。" @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "入力" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "入力アクセスを自動的に有効にできませんでした。手動で変更してから、ショートカットが使用できるように{productName}を完全に終了して再起動してください。" - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "入力アクセスを有効にできませんでした。{settingsMenuName}設定からもう一度お試しください。その後、{productName}を完全に終了して再起動してください。" - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Waylandでシステム全体のキーボードとマウスのショートカットを使用するには、入力アクセスが必要です。入力アクセスを変更した後、ショートカットが使用できるように{productName}を完全に終了して再起動してください。" - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "入力アクセスが変更されました。システム全体のショートカットが使用できるように、{productName}を完全に終了して再起動してください。" - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "入力と出力" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "無効な認証リクエスト" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "ボットの権限が無効です。" @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "コードが無効であるか、期限が切れています。もう一度お試しください。" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "redirect_uriが無効です" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "共有中は招待リンクが非表示になります:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "共有中は招待リンクがマスクされます" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "{displayName}へ移動" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "{labelText}へ移動" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "通話に移動" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "リンク先のチャンネルに移動" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "リンクされたメッセージに移動" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "{authorNickname}からのメッセージに移動" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "{labelText}のメッセージに移動" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "これらは安全に保管してください。再生成すると、既存の連携が動作しなくなります。" #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "ボタンを離した後も、しばらく送信し続けます。" @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "キーボード" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "キーボードへのアクセスを開始できませんでした。ショートカットは{productName}がフォーカスされている間のみ機能します。" + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "コミュニティに自主的なモデレーションを許可する" msgid "Let the member hear others in this community again" msgstr "このコミュニティで再び他のメンバーの声が聞こえるようにしました" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "{productName}がキーボードを直接読み取れるようにします。システムでは既に許可されています。" + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "デスクトップアプリの再起動後、カスタムCSSでウィンドウの透明な領域を表示します。" @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "リンクのスタイル" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "リンクを利用できません" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "見た目と操作感" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "{0}をお使いのようです。その上に{PRODUCT_NAME}の処理を重ねると、ポンピングや歪みが発生する可能性があります。ダイレクト入力では、オーディオはそのまま通過します。" @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "メッセージレイアウト" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "メッセージリンク" @@ -21332,7 +21416,7 @@ msgstr "マイク" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "マイクテスト" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "その他" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "client_idがありません" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "その他のオプション" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "その他のショートカットオプション" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "{sendMessagesPermission}の権限がありません" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "アクセス権がありません" @@ -22984,7 +23068,7 @@ msgstr "一致する項目はありません" msgid "No mentions yet" msgstr "メンションはまだありません" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "マイクが見つかりません" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "設定が見つかりませんでした" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "ショートカットは設定されていません" @@ -23358,6 +23442,11 @@ msgstr "整数ではありません" msgid "Not accepted" msgstr "未承認" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "未割り当て" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "{host}をブラウザで開いてください。" #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "{labelText} を開く" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "設定で{labelText}を開く" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "ページリンク" @@ -25611,11 +25700,11 @@ msgstr "プレゼンス" msgid "Press {shortcut} to react" msgstr "リアクションするには{shortcut}を押す" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "キー、マウスボタン、またはゲームパッドボタンを押して割り当ててください。Escでキャンセルします。" -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "ショートカットを入力…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "プッシュトゥトークキー" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "プッシュトゥトーク" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "プッシュトゥトーク(制限あり)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "プッシュトゥトークにシステム全体のキーがまだ割り当てられていません。設定するには、「設定」>「アプリ」>「{productName}」を開いてください。" + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "プッシュトゥトークにシステム全体のキーがまだ割り当てられていません。設定するには、「システム設定」>「キーボード」>「ショートカット」>「{productName}」を開いてください。" + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "プッシュトゥトークにシステム全体のキーがまだ割り当てられていません。設定するには、デスクトップのキーボードショートカット設定を開いてください。" + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "ダイレクト入力ではプッシュトゥトークは無視されます。使用するには「音声にフォーカス」または「カスタム」に切り替えてください。" @@ -26293,21 +26397,51 @@ msgstr "ダイレクト入力ではプッシュトゥトークは無視されま msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "プッシュトゥトークがオンです。{pushToTalkHint}を押している間は話せます。" +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "プッシュトゥトークがオンです。話すにはプッシュトゥトークのキーを押し続けてください。" + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "デスクトップでシステム全体のショートカットが設定されなかったため、プッシュトゥトークは{productName}がフォーカスされている間のみ機能します。" + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "{permissionName}を有効にするまで、{productName}はフォーカスされている間のみプッシュトゥトークが機能します。権限を変更した後、システム全体のホットキーを使用できるように、{productName}を完全に終了して再起動してください。" +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "システム全体のショートカットを設定するまで、プッシュトゥトークは{productName}がフォーカスされている間のみ機能します。" + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "プッシュトゥトークは、ブラウザのタブがアクティブな場合にのみ機能します。" +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "プッシュトゥトークには{productName}で設定したキーが使われます。先にキーを設定してください。設定しないと、マイクは音声アクティビティで動作します。" + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "プッシュトゥトークでは、スピーカーミュート中にマイクのミュートは解除されません。話すにはスピーカーミュートを解除してください。" +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "プッシュトゥトークを{productName}の外で使うには、Hyprlandの設定でバインドする必要があります。" + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "プッシュトゥトークを{productName}の外で使うには、Hyprlandの設定でバインドする必要があります。お使いのHyprlandのバージョンに合った行を追加し、KEYを使いたいキーに置き換えてください。" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "生の埋め込みJSON" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "組み込みのショートカットを再度有効にする" @@ -26565,13 +26699,6 @@ msgstr "最近のメンション" msgid "Recent searches" msgstr "最近の検索" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "再確認" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "おすすめ" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "おすすめ。マイクの音声をクリアにして、聞き取りやすくします。" @@ -26600,6 +26727,11 @@ msgstr "再接続" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "再接続しました。以前のストリームを再開したい場合は、もう一度画面を選択してください。" +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "デスクトップに再接続しています…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "再接続中…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "ショートカットを記録" @@ -26851,7 +26983,7 @@ msgstr "相対" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "離した後の遅延" @@ -27739,13 +27871,13 @@ msgstr "再送しました" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "リセット" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "「{label}」を組み込み設定にリセット" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "実際の値にリセット" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "組み込み設定にリセット" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "新しいデバイスでサインインするアカウントを選択してください。" #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "音声処理モードを選択" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "マイク音声を処理せずにそのまま送信します。" #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "音声を加工せずに送信します。外部のオーディオソフトウェアを使用している場合に最適です。" @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "{preset} プリセットによって設定されています。ここで値を選択すると、カスタムに切り替わります。" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "カスタムショートカットを設定" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "「{label}」のカスタムショートカットを設定" @@ -30066,6 +30198,11 @@ msgstr "ボイスリージョンを{region}に設定しました" msgid "Set the voice region to automatic" msgstr "ボイスリージョンを自動に設定しました" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "設定する" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "設定" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "設定へのリンク" @@ -30330,8 +30467,8 @@ msgstr "ショートコード" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "ゆったり表示に切り替える" msgid "Switch to dense layout" msgstr "コンパクト表示に切り替える" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "直接入力に切り替える" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "システム管理のインストール" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "システム全体のショートカット" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "システム全体のショートカットを設定できませんでした。" + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "システム全体: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "システム全体: 未割り当て" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "お探しのページは存在しないか、移動された可能性があります。" #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "指定されたredirect_uriは、このアプリケーションに登録されていません。" #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "指定されたredirect_uriはHTTPまたはHTTPSを使用する必要があります。" @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "このアニメーションは長すぎるか、大きすぎるため、トリミングできません。より短く、またはより小さいファイルをお試しください。" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "このアプリ" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "この添付ファイルの有効期限が切れました" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "このボットは公開されていません。他のユーザーが追加することはできません。" @@ -34081,6 +34233,16 @@ msgstr "この会話は利用できません。" msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "このデスクトップ版では、{productName} 内にアップデートをインストールできません。代わりに最新のデスクトップインストーラーをダウンロードしてください。" +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "このデスクトップはシステム全体のショートカットに対応していません。プッシュトゥトークは{productName}がフォーカスされている間のみ機能します。" + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "このデスクトップはシステム全体のショートカットに対応していません。ショートカットは{productName}がフォーカスされている間は引き続き機能します。" + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "これは最後のパスキーであり、パスキーはこのアカウ msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "これは最後のパスキーです。これを削除すると、パスキーを2つ目の認証要素として使用できなくなります。" +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "このキーは{productName}がフォーカスされている間に機能します。システム全体のキーはデスクトップが設定し、{settingsTabName} > {sectionName}で確認できます。" + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "このリンクチャンネルは成人向けコンテンツとして指定されており、一部のユーザーには不適切と思われる内容が表示される可能性があります。" #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "このリンクはあなたのアカウントでは利用できません。" @@ -34778,6 +34945,21 @@ msgstr "タイトルバー" msgid "Title bar platform" msgstr "タイトルバープラットフォーム" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "キーを変更するには、「設定」>「アプリ」>「{productName}」を開いてください。" + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "キーを変更するには、「システム設定」>「キーボード」>「ショートカット」>「{productName}」を開いてください。" + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "キーを変更するには、デスクトップのキーボードショートカット設定を開いてください。" + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "ウェブフックを作成するには、<0>{channelWebhooksSettingsPath}を開いてください。既存のウェブフックはすべて、引き続きこちらで編集・整理できます。" @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "不明" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "不明なアプリ、期限切れのセッション、または無効な認証リクエストです。" @@ -36410,6 +36592,11 @@ msgstr "最大4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "最大720p、30FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Waylandでシステム全体のショートカットを使うには、{productName}をアップデートしてください。" + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "アプリ全体でダークテーマを使用" msgid "Use default" msgstr "デフォルトを使用" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "入力デバイスへの直接アクセスを使用" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "通話の参加者" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "音声処理" @@ -38264,7 +38456,7 @@ msgstr "スピーカーミュート解除" msgid "Voice unmute" msgstr "音声ミュート解除" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "このブラウザでは、ボイスはシステムの出力デバイスを使用します。システム設定で変更してください。" @@ -38278,7 +38470,7 @@ msgstr "無効" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "選択したチャンネルのいずれかでスローモードが解除 msgid "Waiting for you to..." msgstr "そろそろ…" +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "デスクトップからの応答を待っています…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "会話の種類" msgid "Which rule was broken?" msgstr "違反しているルールはどれですか?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "プッシュトゥトークがオンの場合、ショートカットでマイクを操作するため、ミュートボタンは無効になります。" @@ -39094,6 +39291,11 @@ msgstr "この権限がない場合、プッシュトゥトークが必要です msgid "Wordmark" msgstr "ワードマーク" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "{productName}がフォーカスされている間に機能" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "{PREMIUM_PRODUCT_NAME}の利用中にタグを変更しました。{PREMIUM_PRODUCT_NAME}の有効期限が切れると、更新されない限りタグはランダムに再設定されます。" #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "このメッセージが送信されたチャンネルにアクセスできません。" @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "{premiumProductName}のサブスクリプションが期限切れになりました。すべての{premiumProductName}特典は失効しています。再開してアクセスを回復してください。" #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "{productName}のセッションの有効期限が切れました。このアプリケーションの認証を続行するには、もう一度サインインしてください。" @@ -40144,6 +40346,16 @@ msgstr "コミュニティを「見つける」に掲載するには、メンバ msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "クレジットカードの確認が完了しました。カードが有効な場合、まもなく成人向けコンテンツが利用可能になります。このタブを閉じてアプリに戻ることができます。" +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "デスクトップでシステム全体のショートカットが設定されませんでした。" + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "これらのショートカットは、{productName}がフォーカスされていなくてもデスクトップが実行します。" + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/ko/messages.po b/fluxer_app/src/features/i18n/locales/ko/messages.po index 1f24ce22a..314dca3f4 100644 --- a/fluxer_app/src/features/i18n/locales/ko/messages.po +++ b/fluxer_app/src/features/i18n/locales/ko/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {#개의 채널이 이 채널을 구독 중입니다 msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {#개 커뮤니티 찾음} other {#개 커뮤니티 찾음}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {#개 커뮤니티가 플럭스 캐패시터 오작동으로 인해 일시적으로 이용할 수 없습니다.} other {#개 커뮤니티가 플럭스 캐패시터 오작동으로 인해 일시적으로 이용할 수 없습니다.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName}은 사용자가 컴퓨터를 사용하는 중에는 모바 msgid "{productName} Bug Hunter" msgstr "{productName} 버그 헌터" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName}은(는) {productName}에 포커스가 없을 때도 단축키를 실행하도록 데스크톱에 요청할 수 있습니다. 데스크톱에서 확인을 요청하며 키를 직접 선택할 수 있습니다." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName}이(가) 더 이상 키보드를 읽을 수 없어 대신 X11을 통해 단축키를 감지합니다." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName}이(가) 더 이상 키보드를 읽을 수 없어 대신 데스크톱 단축키를 사용합니다." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName}이(가) 더 이상 키보드를 읽을 수 없습니다. 단축키는 {productName}에 포커스가 있을 때만 작동합니다." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName}은(는) {productName}에 포커스가 없을 때도 단축키가 실행되도록 Hyprland에 단축키를 등록할 수 있습니다. 등록한 후 Hyprland 설정에서 키를 바인딩하세요." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName}에서 기기 목록을 보려면 카메라 접근 권한이 필요합니다." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName}에서 기기 목록을 보려면 마이크 접근 권한이 필요합니다." @@ -1837,6 +1862,11 @@ msgstr "{productName}이(가) 이제 {host}에 있습니다. 해당 주소에서 msgid "{productName} Partner" msgstr "{productName} 파트너" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName}이(가) Hyprland에 단축키를 등록했습니다. {productName}에 포커스가 없을 때도 사용하려면 Hyprland 설정에서 바인딩하세요." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "눈에 띄는 프로필" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "봇 스코프가 유일한 스코프가 아닌 경우 redirect_uri가 필요합니다." @@ -2847,7 +2877,7 @@ msgstr "열려 있는 모든 DM에서만" msgid "Across every community you're in" msgstr "참여 중인 모든 커뮤니티에서" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "동작" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "활동 공유" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "활동 임계값" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "계정 보안을 한 단계 더 강화하세요" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "사용자 지정 단축키 추가" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "주소" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "노이즈 억제, 에코 제거, 게인 등 각 설정을 직접 조정하세요." @@ -3696,11 +3726,11 @@ msgstr "{everyoneMention} 및 {hereMention} 허용" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "{PRODUCT_NAME}이(가) {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}에서 카메라에 접근하도록 허용하세요." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "{PRODUCT_NAME}이(가) 마이크에 접근하도록 허용하려면 {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}에서 설정해 주세요." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "{PRODUCT_NAME}에서 마이크에 접근하도록 허용해 주세요. 브라우저 주소 표시줄 또는 설정을 확인하세요." @@ -3803,7 +3833,7 @@ msgstr "{readMessageHistoryPermissionLabel} 권한이 없는 멤버가 특정 msgid "Allow mentions for this role" msgstr "이 역할에 대한 멘션 허용" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "마이크 허용" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "승인 실패" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "승인에 실패했습니다. 다시 시도해 주세요." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "언어 자동 감지" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "활동 임계값 자동 설정" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "봇 권한" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "봇 권한은 음수일 수 없습니다." @@ -6869,6 +6899,11 @@ msgstr "그룹 닉네임 변경" msgid "Change icon" msgstr "아이콘 변경" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "시스템 설정에서 변경" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "채널 접근" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "채널 접근이 거부되었습니다" @@ -7294,7 +7329,7 @@ msgstr "채널 ID가 클립보드에 복사되었습니다" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "채널 링크" @@ -7462,6 +7497,11 @@ msgstr "채팅 입력창" msgid "Chat shortcuts" msgstr "채팅 단축키" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "다시 확인" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "시스템 선택기에서 창을 선택하세요" msgid "Choose an account" msgstr "계정 선택" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "작업을 선택하세요." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "배경 선택" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "guild_id 또는 channel_id 중 하나를 선택하세요. 둘 다 선택할 수 없습니다." @@ -7990,7 +8030,7 @@ msgstr "마이크 소리를 다듬어 음성을 선명하게 만듭니다." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "클릭하여 색상 선택 도구를 열고 새 색상을 선택하세 msgid "Click to react" msgstr "클릭하여 반응하기" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "클릭하여 녹음" @@ -9110,7 +9150,7 @@ msgstr "확인" msgid "Confirmation or double-click for community voice joins" msgstr "커뮤니티 음성 채널 참여 시 확인 또는 더블 클릭" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "{conflictLabel}과(와) 충돌" @@ -9127,7 +9167,7 @@ msgstr "카메라 또는 가상 캡처 장치를 연결한 후 다시 시도하 msgid "Connect a display, then try again." msgstr "디스플레이를 연결한 다음 다시 시도하세요." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "마이크를 연결하고 다시 시도하세요." @@ -9700,6 +9740,21 @@ msgstr "초대 링크 복사" msgid "Copy invite URL" msgstr "초대 URL 복사" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "줄 복사" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "줄 복사(hyprland.conf용)" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "줄 복사(hyprland.lua용)" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "사용자 지정" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "직접 선택" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "사용자 지정 단축키 동작" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "사용자 지정 단축키 옵션" @@ -11466,7 +11521,7 @@ msgstr "기본 단축키" msgid "Default translator" msgstr "기본 번역기" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "기본값:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "저장된 미디어 삭제" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "단축키 삭제" @@ -12102,13 +12157,13 @@ msgstr "개발자 상태" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "개발자 도구" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "채널 헤더의 스태프 배지에서 개발자 도구를 사용할 수 있습니다." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "백엔드 프리미엄 혜택 비활성화" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "내장 단축키 비활성화" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "화면 공유 PIP 팝업을 비활성화할까요?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "단축키 비활성화" @@ -12888,7 +12943,7 @@ msgstr "대신 기부하기" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "다운로드 버튼" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "데스크톱 앱 다운로드" @@ -13204,6 +13259,11 @@ msgstr "네덜란드어" msgid "e.g., YubiKey, iPhone, work computer" msgstr "예: YubiKey, iPhone, 회사 컴퓨터" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "각 키는 시스템 전체 단축키 하나만 실행할 수 있습니다." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "초대 링크 편집" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "단축키 편집" @@ -13425,7 +13485,7 @@ msgstr "저장된 미디어 편집 양식" msgid "Edit search engine" msgstr "검색 엔진 편집" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "단축키 편집" @@ -13753,20 +13813,16 @@ msgstr "빈 편집 내용" msgid "Empty favorites" msgstr "즐겨찾기 없음" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "활성화" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "{permissionName} 권한 허용" @@ -13876,11 +13932,6 @@ msgstr "하드웨어 가속 사용 및 다시 시작" msgid "Enable hardware acceleration?" msgstr "하드웨어 가속을 사용하시겠어요?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Wayland에서 시스템 전체 단축키를 사용하려면 입력 접근을 활성화하세요. 입력 접근을 변경한 후에는 {productName}을(를) 완전히 종료하고 다시 시작해야 단축키를 사용할 수 있습니다." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Shift 키로 확장 사용" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "단축키 사용" @@ -14357,7 +14408,7 @@ msgstr "시스템 오디오 전체" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "입장음" @@ -14467,7 +14518,7 @@ msgstr "저녁 시간" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "마이크 볼륨을 일정하게 조절하여 너무 작게 들리지 않도록 합니다." @@ -15682,6 +15733,26 @@ msgstr "영국인 전용입니다..." msgid "For me" msgstr "직접 구매" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "눌러서 음소거에는 Ctrl, Alt, Shift 없이 단일 키를 사용하세요. 조합 키를 사용하면 메인 키를 먼저 떼야 합니다. 그렇지 않으면 마이크가 계속 음소거될 수 있습니다." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "눌러서 말하기에는 Ctrl, Alt, Shift 없이 단일 키를 사용하세요. 조합 키를 사용하면 메인 키를 먼저 떼야 합니다. 그렇지 않으면 마이크가 계속 켜져 있을 수 있습니다." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "이 단축키에는 Ctrl, Alt, Shift 없이 단일 키를 사용하세요. 조합 키를 사용하면 메인 키를 먼저 떼야 합니다. 그렇지 않으면 단축키가 계속 활성 상태로 남을 수 있습니다." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "음성 활동 우선순위에는 Ctrl, Alt, Shift 없이 단일 키를 사용하세요. 조합 키를 사용하면 메인 키를 먼저 떼야 합니다. 그렇지 않으면 우선순위가 계속 켜져 있을 수 있습니다." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "안전을 위해 본인 확인 후 계속해 주세요." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "글로벌 프로필" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "전역 단축키" @@ -17434,6 +17505,11 @@ msgstr "Shift 키를 누른 상태에서 X 키를 눌러 앱을 선택하세요. msgid "Hold to record. Drag up to lock, or release to send." msgstr "길게 눌러 녹음. 위로 드래그하면 잠금, 놓으면 전송." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Shift, Ctrl 또는 Alt를 누르고 있으면 이 단축키가 작동하지 않습니다. 시스템 설정에서 대체 키를 추가하세요." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "좋아요! 알림이 활성화되었습니다" msgid "Hyperlink" msgstr "하이퍼링크" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland에는 단축키 대화상자가 없습니다. Hyprland 버전에 맞는 줄을 Hyprland 설정에 추가하고 KEY를 원하는 키로 바꾸세요." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland에는 단축키 대화상자가 없습니다. Hyprland 설정에서 키를 바인딩하세요." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (Hyprland 0.55 이전)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 이상)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "<0>개인정보처리방침에 동의합니다" @@ -17576,7 +17672,7 @@ msgstr "마음이 바뀌었어요" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "확인했습니다" @@ -18000,7 +18096,7 @@ msgstr "채널 내" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "\"운영체제\" 모드에서는 위에 나열된 사전이 권장 사항입니다. {PRODUCT_NAME}은 지원되는 경우 이러한 언어 태그를 OS 맞춤법 검사기에 전달합니다. 인식하지 못하는 태그는 무시됩니다." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "브라우저에서는 {PRODUCT_NAME} 탭이 활성화된 경우에만 PTT(Push-to-talk)가 작동합니다. 시스템 전체에서 PTT를 사용하려면 데스크톱 앱을 설치하세요." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "입력" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "입력 접근 권한을 자동으로 활성화할 수 없습니다. 수동으로 변경한 다음 {productName}을(를) 완전히 종료하고 다시 시작해야 단축키를 사용할 수 있습니다." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "입력 접근 권한을 활성화할 수 없습니다. {settingsMenuName} 설정에서 다시 시도한 다음, {productName}을(를) 완전히 종료하고 다시 시작하세요." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Wayland에서 시스템 전체 키보드 및 마우스 단축키를 사용하려면 입력 접근 권한이 필요합니다. 입력 접근 권한을 변경한 후 {productName}을(를) 완전히 종료하고 다시 시작해야 단축키를 사용할 수 있습니다." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "입력 접근 권한이 변경되었습니다. 시스템 전체 단축키를 사용할 수 있도록 {productName}을(를) 완전히 종료한 후 다시 시작하세요." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "입력 및 출력" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "유효하지 않은 승인 요청입니다" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "봇 권한이 유효하지 않습니다." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "코드가 유효하지 않거나 만료되었습니다. 다시 시도해 주세요." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "잘못된 redirect_uri입니다" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "공유 중에는 초대 링크가 숨겨집니다:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "공유 중에는 초대 링크가 가려집니다" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "{displayName}(으)로 이동" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "{labelText}(으)로 이동" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "현재 통화로 이동" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "연결된 채널로 이동" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "연결된 메시지로 이동" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "{authorNickname}의 메시지로 이동" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "{labelText}에서 메시지로 이동" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "안전하게 보관하세요. 재생성하면 기존 연동이 중단됩니다." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "버튼을 놓은 후에도 잠시 동안 전송을 유지합니다." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "키보드" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "키보드 접근을 시작할 수 없습니다. 단축키는 {productName}에 포커스가 있을 때만 작동합니다." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "커뮤니티가 자율적으로 운영되도록 허용" msgid "Let the member hear others in this community again" msgstr "이 커뮤니티에서 멤버가 다른 사람들의 소리를 다시 들을 수 있도록 허용했습니다" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "{productName}이(가) 키보드를 직접 읽을 수 있게 합니다. 시스템에서 이미 허용되어 있습니다." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "데스크톱 앱 재시작 시 사용자 지정 CSS가 투명한 창 영역을 표시하도록 허용합니다." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "링크 스타일" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "링크를 사용할 수 없습니다" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "모양 및 느낌" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "{0}을(를) 사용 중이신 것 같습니다. {PRODUCT_NAME}의 처리를 추가하면 펌핑 또는 왜곡이 발생할 수 있습니다. 직접 입력은 오디오를 그대로 통과시킵니다." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "메시지 레이아웃" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "메시지 링크" @@ -21332,7 +21416,7 @@ msgstr "마이크" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "마이크 테스트" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "기타" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "client_id가 누락되었습니다" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "더 보기" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "추가 단축키 옵션" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "{sendMessagesPermission} 권한 없음" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "액세스 권한 없음" @@ -22984,7 +23068,7 @@ msgstr "일치하는 항목 없음" msgid "No mentions yet" msgstr "아직 멘션이 없습니다" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "마이크를 찾을 수 없습니다" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "설정 없음" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "설정된 단축키 없음" @@ -23358,6 +23442,11 @@ msgstr "정수가 아닙니다" msgid "Not accepted" msgstr "수락되지 않음" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "할당되지 않음" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "{host}을(를) 브라우저에서 열어주세요." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "{labelText} 열기" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "설정에서 {labelText} 열기" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "페이지 링크" @@ -25611,11 +25700,11 @@ msgstr "활동 상태" msgid "Press {shortcut} to react" msgstr "{shortcut} 키를 눌러 반응하기" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "연결할 키, 마우스 버튼 또는 게임패드 버튼을 누르세요. Esc를 누르면 취소됩니다." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "단축키를 누르세요…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "눌러서 말하기 키" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "눌러서 말하기" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "눌러서 말하기 (제한됨)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "눌러서 말하기에 아직 시스템 전체 키가 없습니다. 설정하려면 설정 > 앱 > {productName}(으)로 이동하세요." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "눌러서 말하기에 아직 시스템 전체 키가 없습니다. 설정하려면 시스템 설정 > 키보드 > 단축키 > {productName}(으)로 이동하세요." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "눌러서 말하기에 아직 시스템 전체 키가 없습니다. 설정하려면 데스크톱의 키보드 단축키 설정을 여세요." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "원본 입력에서는 눌러서 말하기가 무시됩니다. 사용하려면 음성 포커스 또는 사용자 지정으로 전환하세요." @@ -26293,21 +26397,51 @@ msgstr "원본 입력에서는 눌러서 말하기가 무시됩니다. 사용하 msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "눌러서 말하기가 켜져 있습니다. {pushToTalkHint} 키를 누른 채 말하세요." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "눌러서 말하기가 켜져 있습니다. 눌러서 말하기 키를 누른 채 말하세요." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "데스크톱에서 시스템 전체 단축키를 설정하지 않아 눌러서 말하기는 {productName}에 포커스가 있을 때만 작동합니다." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "{permissionName}을(를) 활성화하기 전까지는 {productName}이(가) 포커스된 상태에서만 눌러서 말하기가 작동합니다. 권한을 변경한 후에는 {productName}을(를) 완전히 종료하고 다시 시작해야 시스템 전체 단축키를 사용할 수 있습니다." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "시스템 전체 단축키를 설정하기 전까지 눌러서 말하기는 {productName}에 포커스가 있을 때만 작동합니다." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "눌러서 말하기는 브라우저 탭이 활성화된 상태에서만 작동합니다." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "눌러서 말하기는 {productName}에서 설정한 키를 사용합니다. 먼저 키를 설정하지 않으면 마이크가 음성 활동으로 작동합니다." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "소리 끄기 상태에서는 눌러서 말하기로 마이크 음소거가 해제되지 않습니다. 대화하려면 소리 끄기를 먼저 해제하세요." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "눌러서 말하기를 {productName} 밖에서 사용하려면 Hyprland 설정에서 바인딩해야 합니다." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "눌러서 말하기를 {productName} 밖에서 사용하려면 Hyprland 설정에서 바인딩해야 합니다. Hyprland 버전에 맞는 줄을 추가하고 KEY를 원하는 키로 바꾸세요." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "원시 임베드 JSON" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "내장 단축키 다시 활성화" @@ -26565,13 +26699,6 @@ msgstr "최근 멘션" msgid "Recent searches" msgstr "최근 검색" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "다시 확인" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "권장" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "권장. 마이크 소리를 다듬어 말소리를 선명하게 만듭니다." @@ -26600,6 +26727,11 @@ msgstr "다시 연결" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "재연결되었습니다. 이전 스트림을 다시 시작하려면 화면을 다시 선택하세요." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "데스크톱에 다시 연결 중…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "다시 연결 중…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "단축키 기록" @@ -26851,7 +26983,7 @@ msgstr "상대 시간" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "해제 지연 시간" @@ -27739,13 +27871,13 @@ msgstr "다시 보냄" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "초기화" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "\"{label}\"을(를) 기본값으로 재설정" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "실제 값으로 재설정" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "기본값으로 재설정" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "새 기기에서 로그인할 계정을 선택하세요." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "음성 처리 모드 선택" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "처리되지 않은 마이크 오디오를 전송합니다." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "오디오를 그대로 보냅니다. 외부 오디오 소프트웨어를 사용하는 경우에 가장 적합합니다." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "{preset} 사전 설정으로 설정됩니다. 여기서 값을 선택하면 사용자 지정으로 전환됩니다." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "사용자 지정 단축키 설정" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "\"{label}\"에 대한 사용자 지정 단축키 설정" @@ -30066,6 +30198,11 @@ msgstr "음성 지역을 {region}(으)로 설정했습니다" msgid "Set the voice region to automatic" msgstr "음성 지역을 자동으로 설정했습니다" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "설정" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "설정" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "설정 링크" @@ -30330,8 +30467,8 @@ msgstr "단축 코드" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "편안한 레이아웃으로 전환" msgid "Switch to dense layout" msgstr "간결한 레이아웃으로 전환" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "직접 입력으로 전환" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "시스템 관리 설치" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "시스템 전체 단축키" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "시스템 전체 단축키를 설정할 수 없습니다." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "시스템 전체: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "시스템 전체: 할당되지 않음" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "찾으시는 페이지가 없거나 이동되었어요." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "제공된 redirect_uri가 이 애플리케이션에 등록되지 않았습니다." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "제공된 redirect_uri는 HTTP 또는 HTTPS를 사용해야 합니다." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "이 애니메이션은 너무 길거나 커서 자를 수 없습니다. 더 짧거나 작은 파일을 사용해 보세요." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "이 애플리케이션" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "만료된 첨부파일입니다" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "이 봇은 비공개이며 다른 사용자가 추가할 수 없습니다." @@ -34081,6 +34233,16 @@ msgstr "이 대화는 더 이상 이용할 수 없습니다." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "이 데스크톱 빌드는 {productName}에서 업데이트를 설치할 수 없습니다. 대신 최신 데스크톱 설치 파일을 다운로드하세요." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "이 데스크톱은 시스템 전체 단축키를 지원하지 않습니다. 눌러서 말하기는 {productName}에 포커스가 있을 때만 작동합니다." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "이 데스크톱은 시스템 전체 단축키를 지원하지 않습니다. {productName}에 포커스가 있을 때는 단축키가 계속 작동합니다." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "이것이 마지막 패스키이며, 패스키가 두 번째 인증 수 msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "이것이 마지막 패스키입니다. 삭제하고 나면 패스키를 두 번째 인증 수단으로 사용할 수 없습니다." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "이 키는 {productName}에 포커스가 있을 때 작동합니다. 시스템 전체 키는 데스크톱에서 설정하며 {settingsTabName} > {sectionName}에서 확인할 수 있습니다." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "이 링크 채널은 성인 콘텐츠로 표시되어 있으며 일부 사용자에게 부적절할 수 있는 자료가 열릴 수 있습니다." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "회원님의 계정에서는 이 링크를 사용할 수 없습니다." @@ -34778,6 +34945,21 @@ msgstr "제목 표시줄" msgid "Title bar platform" msgstr "제목 표시줄 플랫폼" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "키를 변경하려면 설정 > 앱 > {productName}(으)로 이동하세요." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "키를 변경하려면 시스템 설정 > 키보드 > 단축키 > {productName}(으)로 이동하세요." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "키를 변경하려면 데스크톱의 키보드 단축키 설정을 여세요." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "웹훅을 생성하려면 <0>{channelWebhooksSettingsPath}을(를) 여세요. 여기에서 기존 웹훅을 모두 수정하고 정리할 수 있습니다." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "알 수 없음" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "알 수 없는 앱, 만료된 세션 또는 유효하지 않은 인증 요청입니다." @@ -36410,6 +36592,11 @@ msgstr "최대 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "최대 720p, 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Wayland에서 시스템 전체 단축키를 사용하려면 {productName}을(를) 업데이트하세요." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "앱 전체에 다크 테마 사용" msgid "Use default" msgstr "기본값 사용" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "입력 장치 직접 접근 사용" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "음성 참여자" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "음성 처리" @@ -38264,7 +38456,7 @@ msgstr "음성 소리 켜기" msgid "Voice unmute" msgstr "음성 음소거 해제" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "음성에는 이 브라우저의 시스템 출력 기기가 사용됩니다. 시스템 설정에서 변경하세요." @@ -38278,7 +38470,7 @@ msgstr "무효" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "선택한 채널 중 일부 채널의 슬로우 모드 만료를 기다 msgid "Waiting for you to..." msgstr "기다리고 있어요..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "데스크톱 응답 대기 중…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "대화 선택" msgid "Which rule was broken?" msgstr "어떤 규칙이 위반되었나요?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "눌러서 말하기가 켜져 있는 동안에는 단축키로 마이크를 제어하므로 음소거 버튼이 비활성화됩니다." @@ -39094,6 +39291,11 @@ msgstr "이 권한이 없으면 눌러서 말하기를 사용해야 합니다." msgid "Wordmark" msgstr "워드마크" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "{productName}에 포커스가 있을 때 작동" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "{PREMIUM_PRODUCT_NAME} 사용 중에 태그를 변경했습니다. 구독이 갱신되지 않고 {PREMIUM_PRODUCT_NAME}이(가) 만료되면 태그가 무작위로 다시 지정됩니다." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "이 메시지가 전송된 채널에 접근할 수 없습니다." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "{premiumProductName} 구독이 만료되었습니다. 모든 {premiumProductName} 혜택이 사라졌습니다. 구독을 다시 활성화하면 혜택을 다시 이용할 수 있습니다." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "{productName} 세션이 만료되었습니다. 이 애플리케이션의 인증을 계속하려면 다시 로그인하세요." @@ -40144,6 +40346,16 @@ msgstr "커뮤니티를 디스커버리에 등록하려면 멤버가 최소 {min msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "신용카드 확인이 완료되었습니다. 유효한 카드인 경우, 성인 콘텐츠가 곧 잠금 해제됩니다. 이 탭을 닫고 앱으로 돌아가세요." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "데스크톱에서 시스템 전체 단축키를 설정하지 못했습니다." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "{productName}에 포커스가 없어도 데스크톱이 이 단축키를 실행합니다." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/lt/messages.po b/fluxer_app/src/features/i18n/locales/lt/messages.po index eaa354531..eea9421a5 100644 --- a/fluxer_app/src/features/i18n/locales/lt/messages.po +++ b/fluxer_app/src/features/i18n/locales/lt/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# kanalas jį seka} other {# kanalai jį seka}} š msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {Rasta # bendruomenė} few {Rastos # bendruomenės} other {Rasta # bendruomenių}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# bendruomenė laikinai nepasiekiama dėl srauto kondensatoriaus gedimo.} few {# bendruomenės laikinai nepasiekiamos dėl srauto kondensatoriaus gedimo.} other {# bendruomenių laikinai nepasiekiama dėl srauto kondensatoriaus gedimo.}}" @@ -1708,6 +1708,31 @@ msgstr "„{productName}“ nesiunčia tiesioginių pranešimų į jūsų mobili msgid "{productName} Bug Hunter" msgstr "{productName} klaidų medžiotojas" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} gali paprašyti jūsų darbalaukio vykdyti jo sparčiuosius klavišus, kai {productName} nėra aktyvus. Darbalaukis paprašys patvirtinti ir leis pasirinkti klavišus." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} nebegali skaityti jūsų klaviatūros, todėl sparčiųjų klavišų klausosi per X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} nebegali skaityti jūsų klaviatūros, todėl naudojami jūsų darbalaukio spartieji klavišai." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} nebegali nuskaityti jūsų klaviatūros. Spartieji klavišai veikia tik tada, kai {productName} yra aktyvus." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} gali užregistruoti savo sparčiuosius klavišus Hyprland, kad jie veiktų, net kai {productName} nėra aktyvus. Tada klavišus susiesite savo Hyprland konfigūracijoje." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} reikia prieigos prie kameros, kad būtų galima parodyti jūsų įrenginius." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} reikia prieigos prie mikrofono, kad būtų galima parodyti jūsų įrenginius." @@ -1837,6 +1862,11 @@ msgstr "{productName} dabar veikia adresu {host}. Įdiekite jį iš ten." msgid "{productName} Partner" msgstr "„{productName}“ partneris" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} užregistravo savo sparčiuosius klavišus Hyprland. Susiekite juos savo Hyprland konfigūracijoje, kad galėtumėte juos naudoti, kai {productName} nėra aktyvus." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Profilis, kuris išsiskiria" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri yra būtinas, kai bot nėra vienintelė aprėptis." @@ -2847,7 +2877,7 @@ msgstr "Tik visuose atidarytuose DM" msgid "Across every community you're in" msgstr "Visose bendruomenėse, kurioms priklausote" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Veiksmas" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Dalijimasis veikla" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Aktyvumo slenkstis" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Pridėkite papildomą saugumo lygį savo paskyrai" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Pridėti kitą pasirinktinį spartųjį klavišą" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Adresas" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Reguliuokite kiekvieną nustatymą patys: triukšmo slopinimą, aido slopinimą ir stiprinimą." @@ -3696,11 +3726,11 @@ msgstr "Leisti {everyoneMention} ir {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Leiskite {PRODUCT_NAME} pasiekti jūsų kamerą: {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Leiskite {PRODUCT_NAME} pasiekti jūsų mikrofoną: {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Leiskite {PRODUCT_NAME} pasiekti jūsų mikrofoną. Patikrinkite naršyklės adreso juostą arba nustatymus." @@ -3803,7 +3833,7 @@ msgstr "Leisti nariams be leidimo „{readMessageHistoryPermissionLabel}“ maty msgid "Allow mentions for this role" msgstr "Leisti paminėti šį vaidmenį" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Leisti naudoti mikrofoną" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Autorizacija nepavyko" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Autorizacija nepavyko. Bandykite dar kartą." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Automatiškai aptikti kalbą" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Automatiškai nustatyti aktyvumo slenkstį" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "boto leidimai" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Boto leidimai negali būti neigiami." @@ -6869,6 +6899,11 @@ msgstr "Keisti grupės slapyvardį" msgid "Change icon" msgstr "Keisti piktogramą" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Keisti sistemos nustatymuose" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Prieiga prie kanalo" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Prieiga prie kanalo uždrausta" @@ -7294,7 +7329,7 @@ msgstr "Kanalo ID nukopijuotas į iškarpinę" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "kanalo nuoroda" @@ -7462,6 +7497,11 @@ msgstr "pokalbių įvestis" msgid "Chat shortcuts" msgstr "Pokalbių spartieji klavišai" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Patikrinti dar kartą" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Pasirinkite langą sistemos parinkiklyje" msgid "Choose an account" msgstr "Pasirinkite paskyrą" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Pasirinkite veiksmą." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Pasirinkite foną" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Pasirinkite „guild_id“ arba „channel_id“, bet ne abu." @@ -7990,7 +8030,7 @@ msgstr "Išvalo mikrofono garsą, kad kalba būtų aiški." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Spustelėkite, kad atidarytumėte spalvų parinkiklį ir pasirinktumėte msgid "Click to react" msgstr "Spustelėkite, kad reaguotumėte" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Spustelėkite, kad įrašytumėte" @@ -9110,7 +9150,7 @@ msgstr "Patvirtinimas" msgid "Confirmation or double-click for community voice joins" msgstr "Patvirtinimas arba dvigubas spustelėjimas prisijungiant prie bendruomenės balso kanalų" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Kertasi su „{conflictLabel}“" @@ -9127,7 +9167,7 @@ msgstr "Prijunkite kamerą arba virtualų fiksavimo įrenginį ir bandykite dar msgid "Connect a display, then try again." msgstr "Prijunkite ekraną ir bandykite dar kartą." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Prijunkite mikrofoną ir bandykite dar kartą." @@ -9700,6 +9740,21 @@ msgstr "Kopijuoti kvietimo nuorodą" msgid "Copy invite URL" msgstr "Kopijuoti kvietimo URL" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Kopijuoti eilutes" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Kopijuoti eilutes, skirtas hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Kopijuoti eilutes, skirtas hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Pasirinktinis šaltinis" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Atskiras pasirinkimas" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Pasirinktinio sparčiojo klavišo veiksmas" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Pasirinktinio sparčiojo klavišo parinktys" @@ -11466,7 +11521,7 @@ msgstr "Numatytieji spartieji klavišai" msgid "Default translator" msgstr "Numatytasis vertėjas" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Numatyta:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Ištrinti išsaugotą mediją" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Ištrinti spartųjį klavišą" @@ -12102,13 +12157,13 @@ msgstr "Kūrėjo būsena" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Kūrėjo įrankiai" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Kūrėjo įrankiai pasiekiami iš personalo ženkliuko kanalo antraštėje." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Išjungti premium privilegijas serveryje" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Išjungti integruotą spartųjį klavišą" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Išjungti ekrano bendrinimo „vaizdas vaizde“ iššokančiuosius langus?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Išjungti spartųjį klavišą" @@ -12888,7 +12943,7 @@ msgstr "Paaukoti vietoj to" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Atsisiuntimo mygtukas" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Atsisiųsti darbalaukio programėlę" @@ -13204,6 +13259,11 @@ msgstr "olandų" msgid "e.g., YubiKey, iPhone, work computer" msgstr "pvz., YubiKey, iPhone, darbinis kompiuteris" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Kiekvienas klavišas gali paleisti tik vieną sistemos spartųjį klavišą." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Redaguoti kvietimo nuorodą" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Redaguoti spartųjį klavišą" @@ -13425,7 +13485,7 @@ msgstr "Išsaugotos medijos redagavimo forma" msgid "Edit search engine" msgstr "Redaguoti paieškos variklį" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Redaguoti spartųjį klavišą" @@ -13753,20 +13813,16 @@ msgstr "Tuščias redagavimas" msgid "Empty favorites" msgstr "Nėra mėgstamiausių" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Įjungti" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Įjungti {permissionName} leidimą" @@ -13876,11 +13932,6 @@ msgstr "Įjungti aparatinės įrangos spartinimą ir paleisti iš naujo" msgid "Enable hardware acceleration?" msgstr "Įjungti aparatinės įrangos spartinimą?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Įjunkite įvesties prieigą, kad „Wayland“ aplinkoje veiktų visos sistemos spartieji klavišai. Pakeitę įvesties prieigą, visiškai išjunkite „{productName}“ ir paleiskite iš naujo, kad spartieji klavišai galėtų ja naudotis." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Įjungti išplėtimą laikant „Shift“" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Įjungti spartųjį klavišą" @@ -14357,7 +14408,7 @@ msgstr "Visos sistemos garsas" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Įėjimo garsas" @@ -14467,7 +14518,7 @@ msgstr "Vakaras" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Sulygina mikrofono garsumą, kad jūsų balsas nebūtų per tylus." @@ -15682,6 +15733,26 @@ msgstr "Tik britams skirta..." msgid "For me" msgstr "Man" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Nutildymui paspaudus naudokite vieną klavišą be Ctrl, Alt ar Shift. Jei naudojate kombinaciją, pirmiausia atleiskite pagrindinį klavišą, kitaip mikrofonas gali likti išjungtas." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Kalbėjimui paspaudus naudokite vieną klavišą be Ctrl, Alt ar Shift. Jei naudojate kombinaciją, pirmiausia atleiskite pagrindinį klavišą, kitaip mikrofonas gali likti įjungtas." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Šiam sparčiajam klavišui naudokite vieną klavišą be Ctrl, Alt ar Shift. Jei naudojate kombinaciją, pirmiausia atleiskite pagrindinį klavišą, kitaip spartusis klavišas gali likti aktyvus." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Balso aktyvumo prioritetui naudokite vieną klavišą be Ctrl, Alt ar Shift. Jei naudojate kombinaciją, pirmiausia atleiskite pagrindinį klavišą, kitaip prioritetas gali likti įjungtas." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Siekdami užtikrinti jūsų saugumą, patvirtinkite, kad tai jūs." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Visuotinis profilis" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Visuotinis spartusis klavišas" @@ -17435,6 +17506,11 @@ msgstr "Paspauskite ir palaikykite „Shift“, tada paspauskite „X“, kad pa msgid "Hold to record. Drag up to lock, or release to send." msgstr "Laikykite, kad įrašytumėte. Vilkite aukštyn, kad užrakintumėte, arba atleiskite, kad išsiųstumėte." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Laikant nuspaustą Shift, Ctrl arba Alt, šis spartusis klavišas neveikia. Pridėkite alternatyvių klavišų sistemos nustatymuose." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17538,6 +17614,26 @@ msgstr "Valio! Pranešimai įjungti" msgid "Hyperlink" msgstr "Hipersaitas" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland neturi sparčiųjų klavišų dialogo lango. Pridėkite savo Hyprland versijai skirtas eilutes į Hyprland konfigūraciją ir pakeiskite KEY norimu klavišu." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland neturi sparčiųjų klavišų dialogo lango. Susiekite klavišus savo Hyprland konfigūracijoje." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (prieš Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 ir vėlesnės versijos)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Sutinku su <0>Privatumo politika" @@ -17577,7 +17673,7 @@ msgstr "Persigalvojau" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Supratau" @@ -18001,7 +18097,7 @@ msgstr "Kanale" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "Režimu „Operacinė sistema“ aukščiau išvardyti žodynai yra tik patariamieji. {PRODUCT_NAME} perduos šias kalbų žymas jūsų OS rašybos tikrintuvui, kai tai bus palaikoma. Žymų, kurių jis neatpažįsta, bus nepaisoma." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "Naršyklėje funkcija „Kalbėti paspaudus“ veikia tik tada, kai {PRODUCT_NAME} skirtukas yra aktyvus. Įdiekite darbalaukio programą, kad ji veiktų visoje sistemoje." @@ -18269,30 +18365,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Įvestis" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Nepavyko automatiškai įjungti įvesties prieigos. Pakeiskite ją rankiniu būdu, tada visiškai išjunkite ir iš naujo paleiskite „{productName}“, kad sparčiuosius klavišus būtų galima naudoti." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Nepavyko įjungti įvesties prieigos. Bandykite dar kartą „{settingsMenuName}“ nustatymuose, tada visiškai išjunkite ir iš naujo paleiskite „{productName}“." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Sisteminiams klaviatūros ir pelės spartiesiems klavišams „Wayland“ aplinkoje reikalinga įvesties prieiga. Pakeitę įvesties prieigą, visiškai išjunkite ir iš naujo paleiskite „{productName}“, kad spartieji klavišai galėtų ja naudotis." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Pakeista įvesties prieiga. Visiškai išjunkite ir iš naujo paleiskite „{productName}“, kad sistemos spartieji klavišai galėtų ja naudotis." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Įvestis ir išvestis" @@ -18451,7 +18525,7 @@ msgid "Invalid authorization request" msgstr "Netinkama autorizacijos užklausa" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Netinkami boto leidimai." @@ -18494,7 +18568,7 @@ msgid "Invalid or expired code. Try again." msgstr "Neteisingas arba pasibaigęs kodas. Bandykite dar kartą." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Neteisingas „redirect_uri“" @@ -18598,7 +18672,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Kvietimo nuoroda paslėpta, kol bendrinama:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Kvietimo nuoroda paslėpta, kol bendrinama" @@ -19012,7 +19086,7 @@ msgid "Jump to {displayName}" msgstr "Pereiti prie {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Pereiti prie „{labelText}“" @@ -19069,12 +19143,12 @@ msgid "Jump to the current call" msgstr "Pereiti prie dabartinio skambučio" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Pereiti į susietą kanalą" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Pereiti prie susietos žinutės" @@ -19084,7 +19158,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Pereiti prie {authorNickname} žinutės" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Pereiti prie žinutės iš „{labelText}“" @@ -19168,7 +19242,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Saugokite juos. Sugeneravus iš naujo, esamos integracijos nutrūks." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Tęsti perdavimą trumpą laiką po mygtuko atleidimo." @@ -19205,6 +19279,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Klaviatūra" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Nepavyko įjungti prieigos prie klaviatūros. Spartieji klavišai veikia tik tada, kai {productName} yra aktyvus." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19578,6 +19657,11 @@ msgstr "Leisti bendruomenei moderuoti save" msgid "Let the member hear others in this community again" msgstr "Nariui vėl leista girdėti kitus šioje bendruomenėje" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Leidžia {productName} tiesiogiai skaityti jūsų klaviatūrą. Jūsų sistema tai jau leidžia." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Leidžia pasirinktiniam CSS atskleisti permatomas lango sritis iš naujo paleidus darbalaukio programą." @@ -19761,7 +19845,7 @@ msgid "Link styling" msgstr "Nuorodų stilius" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Nuoroda nepasiekiama" @@ -20037,7 +20121,7 @@ msgid "Look and feel" msgstr "Išvaizda ir stilius" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Panašu, kad naudojate „{0}“. Papildomas „{PRODUCT_NAME}“ apdorojimas gali sukelti garso pulsavimą arba iškraipymus. Tiesioginė įvestis perduoda jūsų garsą nepakeistą." @@ -21145,7 +21229,7 @@ msgid "Message layout" msgstr "Žinučių išdėstymas" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "nuoroda į žinutę" @@ -21333,7 +21417,7 @@ msgstr "Mikrofonas" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Mikrofono patikra" @@ -21473,7 +21557,7 @@ msgid "Miscellaneous" msgstr "Įvairūs" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Trūksta client_id" @@ -21691,7 +21775,7 @@ msgid "More options" msgstr "Daugiau parinkčių" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Daugiau sparčiųjų klavišų parinkčių" @@ -22673,7 +22757,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Nėra leidimo „{sendMessagesPermission}“" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Nėra prieigos" @@ -22985,7 +23069,7 @@ msgstr "Nieko nerasta" msgid "No mentions yet" msgstr "Kol kas nėra paminėjimų" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Mikrofonas nerastas" @@ -23135,7 +23219,7 @@ msgid "No settings found" msgstr "Nustatymų nerasta" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Spartusis klavišas nenustatytas" @@ -23359,6 +23443,11 @@ msgstr "Ne sveikasis skaičius" msgid "Not accepted" msgstr "Nepriimta" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Nepriskirta" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23859,12 +23948,12 @@ msgid "Open {host} in your browser." msgstr "Atidarykite {host} naršyklėje." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Atidarykite {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Atidaryti „{labelText}“ nustatymuose" @@ -24612,7 +24701,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "nuoroda į puslapį" @@ -25612,11 +25701,11 @@ msgstr "Buvimas prisijungus" msgid "Press {shortcut} to react" msgstr "Paspauskite {shortcut}, kad reaguotumėte" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Norėdami susieti, paspauskite klavišą, pelės mygtuką arba žaidimų pultelio mygtuką. „Escape“ atšaukia." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Paspauskite spartųjį klavišą…" @@ -26274,18 +26363,33 @@ msgid "Push to talk key" msgstr "Kalbėjimo paspaudus klavišas" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Kalbėjimas paspaudus" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Kalbėjimas paspaudus (ribotas)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Kalbėjimui paspaudus dar nepriskirtas sistemos klavišas. Norėdami jį nustatyti, eikite į Nustatymai > Programos > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Kalbėjimui paspaudus dar nepriskirtas sistemos klavišas. Norėdami jį nustatyti, eikite į Sistemos nuostatos > Klaviatūra > Spartieji klavišai > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Kalbėjimui paspaudus dar nepriskirtas sistemos klavišas. Norėdami jį nustatyti, atidarykite darbalaukio klaviatūros sparčiųjų klavišų nustatymus." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Kalbėjimas paspaudus tiesioginėje įvestyje ignoruojamas. Norėdami jį naudoti, perjunkite į fokusuotą balsą arba pasirinktinį." @@ -26294,21 +26398,51 @@ msgstr "Kalbėjimas paspaudus tiesioginėje įvestyje ignoruojamas. Norėdami j msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Įjungtas kalbėjimas paspaudus. Laikykite {pushToTalkHint}, kad kalbėtumėte." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Kalbėjimas paspaudus įjungtas. Norėdami kalbėti, laikykite nuspaudę kalbėjimo paspaudus klavišą." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Kalbėjimas paspaudus veikia tik tada, kai {productName} yra aktyvus, nes jūsų darbalaukis nenustatė sistemos sparčiųjų klavišų." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Kalbėjimas paspaudus veikia tik tada, kai „{productName}“ langas yra aktyvus, kol neįjungtas „{permissionName}“. Pakeitę leidimą, visiškai išjunkite ir iš naujo paleiskite „{productName}“, kad sistemos spartieji klavišai galėtų jį naudoti." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Kalbėjimas paspaudus veikia tik tada, kai {productName} yra aktyvus, kol nenustatysite sistemos sparčiųjų klavišų." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Kalbėjimas paspaudus veikia tik tada, kai naršyklės skirtukas yra aktyvus." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Kalbėjimas paspaudus naudos klavišą, kurį nustatėte {productName}. Pirmiausia jį nustatykite, kitaip mikrofonas veiks pagal balso aktyvumą." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Kalbėjimas paspaudus neįjungs mikrofono, kol jūsų garsas išjungtas. Norėdami kalbėti, vėl įjunkite garsą." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Kalbėjimas paspaudus veikia už {productName} ribų tik tada, kai jį susiesite savo Hyprland konfigūracijoje." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Kalbėjimas paspaudus veikia už {productName} ribų tik tada, kai jį susiesite savo Hyprland konfigūracijoje. Pridėkite savo Hyprland versijai skirtą eilutę ir pakeiskite KEY norimu klavišu." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26407,7 +26541,7 @@ msgid "Raw embed JSON" msgstr "Neapdorotas įterptojo elemento JSON" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Vėl įjungti integruotą spartųjį klavišą" @@ -26566,13 +26700,6 @@ msgstr "Naujausi paminėjimai" msgid "Recent searches" msgstr "Naujausios paieškos" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Patikrinti iš naujo" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26587,7 +26714,7 @@ msgid "Recommended" msgstr "Rekomenduojama" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Rekomenduojama. Išvalo mikrofoną, kad kalba būtų aiški." @@ -26601,6 +26728,11 @@ msgstr "Prisijungti iš naujo" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Pavyko prisijungti iš naujo. Jei norite tęsti ankstesnę transliaciją, pasirinkite ekraną dar kartą." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Jungiamasi iš naujo prie darbalaukio…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26612,7 +26744,7 @@ msgid "Reconnecting…" msgstr "Jungiamasi iš naujo…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Įrašyti spartųjį klavišą" @@ -26852,7 +26984,7 @@ msgstr "Santykinis" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Atleidimo delsa" @@ -27740,13 +27872,13 @@ msgstr "Išsiųsta iš naujo" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Atkurti" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Atkurti „{label}“ numatytuosius nustatymus" @@ -27867,7 +27999,7 @@ msgid "Reset to actual values" msgstr "Atkurti tikrąsias reikšmes" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Atkurti numatytuosius" @@ -29510,7 +29642,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Pasirinkite paskyrą, su kuria norite prisijungti naujame įrenginyje." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Pasirinkite balso apdorojimo režimą" @@ -29815,7 +29947,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Siunčia neapdorotą mikrofono garsą be apdorojimo." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Siunčia jūsų garsą be pakeitimų. Geriausia, jei naudojate išorinę garso programinę įrangą." @@ -29914,12 +30046,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Nustatyta pagal {preset} iš anksto nustatytą parinktį. Pasirinkus reikšmę čia, bus pereita prie pasirinktinio nustatymo." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Nustatyti pasirinktinį spartųjį klavišą" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Nustatyti pasirinktinį spartųjį klavišą, skirtą „{label}“" @@ -30067,6 +30199,11 @@ msgstr "Nustatytas balso regionas: {region}" msgid "Set the voice region to automatic" msgstr "Nustatytas automatinis balso regionas" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Nustatyti" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30119,7 +30256,7 @@ msgid "Settings" msgstr "Nustatymai" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "nustatymų nuoroda" @@ -30331,8 +30468,8 @@ msgstr "Trumpieji kodai" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32760,7 +32897,7 @@ msgstr "Perjungti į patogų išdėstymą" msgid "Switch to dense layout" msgstr "Perjungti į tankų išdėstymą" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Perjungti į tiesioginį įvedimą" @@ -33028,10 +33165,25 @@ msgid "System-managed install" msgstr "Sistemos tvarkomas diegimas" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Visos sistemos spartieji klavišai" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Nepavyko nustatyti sistemos sparčiųjų klavišų." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Visoje sistemoje: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Visoje sistemoje: nepriskirta" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33613,12 +33765,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Puslapis, kurio ieškote, neegzistuoja arba buvo perkeltas." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Pateiktas „redirect_uri“ neužregistruotas šiai programai." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Pateiktas „redirect_uri“ turi naudoti HTTP arba HTTPS." @@ -33851,7 +34003,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Ši animacija yra per ilga arba per didelė, kad ją būtų galima apkarpyti. Pabandykite trumpesnį arba mažesnį failą." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Ši programa" @@ -33861,7 +34013,7 @@ msgid "This attachment has expired" msgstr "Šis priedas nebegalioja" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Šis botas nėra viešas, todėl kiti naudotojai negali jo pridėti." @@ -34082,6 +34234,16 @@ msgstr "Šis pokalbis nebepasiekiamas." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Ši darbalaukio versija negali diegti naujinimų programoje {productName}. Vietoj to atsisiųskite naujausią darbalaukio diegimo programą." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Šis darbalaukis nepalaiko sistemos sparčiųjų klavišų. Kalbėjimas paspaudus veikia tik tada, kai {productName} yra aktyvus." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Šis darbalaukis nepalaiko sistemos sparčiųjų klavišų. Spartieji klavišai vis tiek veikia, kai {productName} yra aktyvus." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34295,6 +34457,11 @@ msgstr "Tai paskutinis jūsų prieigos raktas, o prieigos raktai yra jūsų antr msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Tai paskutinis jūsų prieigos raktas. Kai jo nebeliks, negalėsite naudoti prieigos rakto kaip antrojo veiksnio." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Šis klavišas veikia, kai {productName} yra aktyvus. Sistemos klavišą nustato jūsų darbalaukis, jį rasite skiltyje {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34306,7 +34473,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Šis nuorodų kanalas pažymėtas kaip skirtas brandžiam turiniui ir gali atverti medžiagą, kuri kai kuriems naudotojams gali būti netinkama." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Ši nuoroda nepasiekiama jūsų paskyrai." @@ -34779,6 +34946,21 @@ msgstr "Pavadinimo juosta" msgid "Title bar platform" msgstr "Pavadinimo juostos platforma" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Norėdami pakeisti klavišus, eikite į Nustatymai > Programos > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Norėdami pakeisti klavišus, eikite į Sistemos nuostatos > Klaviatūra > Spartieji klavišai > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Norėdami pakeisti klavišus, atidarykite savo darbalaukio klaviatūros sparčiųjų klavišų nustatymus." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Norėdami sukurti webhooką, atidarykite <0>{channelWebhooksSettingsPath}. Čia vis tiek galite redaguoti ir tvarkyti visus esamus webhookus." @@ -35873,7 +36055,7 @@ msgid "Unknown" msgstr "Nežinoma" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Nežinoma programa, pasibaigusi sesija arba neteisinga autorizacijos užklausa." @@ -36411,6 +36593,11 @@ msgstr "Iki 4K/60 kadr./s" msgid "Up to 720p at 30 FPS" msgstr "Iki 720p esant 30 kadrų per sekundę" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Atnaujinkite {productName}, kad galėtumėte naudoti sistemos sparčiuosius klavišus Wayland aplinkoje." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36852,6 +37039,11 @@ msgstr "Naudoti tamsiąją temą visoje programėlėje" msgid "Use default" msgstr "Naudoti numatytąjį" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Naudoti tiesioginę prieigą prie įvesties įrenginių" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38219,7 +38411,7 @@ msgid "Voice participants" msgstr "Balso pokalbio dalyviai" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Balso apdorojimas" @@ -38265,7 +38457,7 @@ msgstr "Garso įjungimas" msgid "Voice unmute" msgstr "Mikrofono įjungimas" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Šioje naršyklėje balso pokalbiams naudojamas sistemos garso išvesties įrenginys. Pakeiskite jį sistemos nustatymuose." @@ -38279,7 +38471,7 @@ msgstr "Anuliuota" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38346,6 +38538,11 @@ msgstr "Laukiama, kol pasibaigs lėtasis režimas viename ar keliuose pasirinktu msgid "Waiting for you to..." msgstr "Laukiama, kol jūs..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Laukiama jūsų darbalaukio…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38927,7 +39124,7 @@ msgstr "Kurie pokalbiai" msgid "Which rule was broken?" msgstr "Kokia taisyklė buvo pažeista?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Kai įjungtas kalbėjimas paspaudus, mikrofoną valdo jūsų spartusis klavišas, todėl nutildymo mygtukas išjungtas." @@ -39095,6 +39292,11 @@ msgstr "Be šio leidimo privaloma naudoti kalbėjimą paspaudus." msgid "Wordmark" msgstr "Žodinis logotipas" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Veikia, kai {productName} yra aktyvus" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39522,7 +39724,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Pakeitėte savo žymą, kol naudojatės {PREMIUM_PRODUCT_NAME}. Ji bus atsitiktinai pakeista, kai baigs galioti jūsų {PREMIUM_PRODUCT_NAME}, nebent ji bus atnaujinta." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Neturite prieigos prie kanalo, kuriame buvo išsiųsta ši žinutė." @@ -40057,7 +40259,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Jūsų „{premiumProductName}“ prenumerata baigėsi. Praradote visas „{premiumProductName}“ privilegijas. Norėdami atgauti prieigą, aktyvuokite prenumeratą iš naujo." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Jūsų „{productName}“ seanso laikas baigėsi. Prisijunkite iš naujo, kad tęstumėte šios programos autorizavimą." @@ -40145,6 +40347,16 @@ msgstr "Jūsų bendruomenėje turi būti bent {minMemberCount, plural, one {# na msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Jūsų kredito kortelė patikrinta. Jei kortelė galioja, netrukus bus atrakintas suaugusiesiems skirtas turinys. Galite uždaryti šį skirtuką ir grįžti į programėlę." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Jūsų darbalaukis nenustatė sistemos sparčiųjų klavišų." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Jūsų darbalaukis vykdo šiuos sparčiuosius klavišus net tada, kai {productName} nėra aktyvus." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/nl/messages.po b/fluxer_app/src/features/i18n/locales/nl/messages.po index 3421f3cc5..65104262b 100644 --- a/fluxer_app/src/features/i18n/locales/nl/messages.po +++ b/fluxer_app/src/features/i18n/locales/nl/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# kanaal volgt} other {# kanalen volgen}} dit kanaa msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# community gevonden} other {# communities gevonden}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# community is tijdelijk niet beschikbaar door een storing in de fluxcondensator.} other {# communities zijn tijdelijk niet beschikbaar door een storing in de fluxcondensator.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} stuurt geen pushmeldingen naar je mobiele apparaten zolang msgid "{productName} Bug Hunter" msgstr "{productName}-bugjager" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} kan je desktop vragen om de sneltoetsen uit te voeren als {productName} niet actief is. Je desktop vraagt je om bevestiging en laat je de toetsen kiezen." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} kan je toetsenbord niet meer uitlezen en luistert daarom via X11 naar sneltoetsen." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} kan je toetsenbord niet meer uitlezen, dus worden de sneltoetsen van je desktop gebruikt." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} kan je toetsenbord niet meer uitlezen. Sneltoetsen werken alleen als {productName} actief is." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} kan zijn sneltoetsen registreren bij Hyprland, zodat ze ook werken als {productName} niet actief is. Je stelt de toetsen daarna in je Hyprland-configuratie in." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} heeft cameratoegang nodig om je apparaten weer te geven." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} heeft microfoontoegang nodig om je apparaten weer te geven." @@ -1837,6 +1862,11 @@ msgstr "{productName} is nu te vinden op {host}. Installeer het vanaf daar." msgid "{productName} Partner" msgstr "{productName}-partner" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} heeft zijn sneltoetsen geregistreerd bij Hyprland. Stel ze in je Hyprland-configuratie in om ze te gebruiken als {productName} niet actief is." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Een profiel dat opvalt" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Een redirect_uri is vereist als \"bot\" niet het enige machtigingsbereik is." @@ -2847,7 +2877,7 @@ msgstr "Alleen in alle openstaande DM's" msgid "Across every community you're in" msgstr "In elke community waar je lid van bent" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Actie" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Activiteit delen" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Activiteitsdrempel" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Voeg een extra beveiligingslaag toe aan je account" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Nog een aangepaste sneltoets toevoegen" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Adres" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Pas elke instelling zelf aan: ruisonderdrukking, echo-onderdrukking en versterking." @@ -3696,11 +3726,11 @@ msgstr "Sta {everyoneMention} en {hereMention} toe" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Sta {PRODUCT_NAME} toe om je camera te gebruiken in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Geef {PRODUCT_NAME} toegang tot je microfoon via {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Sta {PRODUCT_NAME} toe om je microfoon te gebruiken. Controleer de adresbalk of instellingen van je browser." @@ -3803,7 +3833,7 @@ msgstr "Sta leden zonder de machtiging \"{readMessageHistoryPermissionLabel}\" t msgid "Allow mentions for this role" msgstr "Vermeldingen voor deze rol toestaan" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Microfoontoegang toestaan" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Autorisatie mislukt" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Autorisatie mislukt. Probeer opnieuw." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Taal automatisch detecteren" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Activiteitsdrempel automatisch instellen" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Botmachtigingen" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Botmachtigingen kunnen niet negatief zijn." @@ -6869,6 +6899,11 @@ msgstr "Groepsbijnaam wijzigen" msgid "Change icon" msgstr "Pictogram wijzigen" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Wijzigen in systeeminstellingen" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Kanaaltoegang" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Toegang tot kanaal geweigerd" @@ -7294,7 +7329,7 @@ msgstr "Kanaal-ID gekopieerd naar klembord" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "kanaallink" @@ -7462,6 +7497,11 @@ msgstr "chat-invoer" msgid "Chat shortcuts" msgstr "Chatsneltoetsen" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Opnieuw controleren" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Kies een venster in je systeemkiezer" msgid "Choose an account" msgstr "Kies een account" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Kies een actie." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Kies achtergrond" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Kies guild_id of channel_id, niet beide." @@ -7990,7 +8030,7 @@ msgstr "Maakt je microfoongeluid helderder zodat je goed verstaanbaar bent." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Klik om de kleurkiezer te openen en een nieuwe kleur te kiezen." msgid "Click to react" msgstr "Klik om te reageren" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Klik om op te nemen" @@ -9110,7 +9150,7 @@ msgstr "Bevestiging" msgid "Confirmation or double-click for community voice joins" msgstr "Bevestiging of dubbelklik bij deelname aan spraakkanalen in communities" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Conflict met {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Sluit een camera of virtueel opnameapparaat aan en probeer het opnieuw." msgid "Connect a display, then try again." msgstr "Sluit een beeldscherm aan en probeer het opnieuw." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Sluit een microfoon aan en probeer het opnieuw." @@ -9700,6 +9740,21 @@ msgstr "Uitnodigingslink kopiëren" msgid "Copy invite URL" msgstr "Uitnodigings-URL kopiëren" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Regels kopiëren" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Regels kopiëren voor hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Regels kopiëren voor hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Aangepast" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Aangepaste selectie" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Aangepaste sneltoetsactie" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Opties voor aangepaste sneltoetsen" @@ -11466,7 +11521,7 @@ msgstr "Standaardsneltoetsen" msgid "Default translator" msgstr "Standaard vertaler" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Standaard:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Opgeslagen media verwijderen" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Sneltoets verwijderen" @@ -12102,13 +12157,13 @@ msgstr "Ontwikkelaarsstatus" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Ontwikkelaarstools" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Ontwikkelaarstools zijn beschikbaar via de medewerkersbadge in de kanaalkop." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Premium-voordelen op de backend uitschakelen" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Ingebouwde sneltoets uitschakelen" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Beeld-in-beeldvensters voor schermdeling uitschakelen?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Sneltoets uitschakelen" @@ -12888,7 +12943,7 @@ msgstr "Doneer in plaats daarvan" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Downloadknop" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Desktopapp downloaden" @@ -13204,6 +13259,11 @@ msgstr "Nederlands" msgid "e.g., YubiKey, iPhone, work computer" msgstr "bijv. YubiKey, iPhone, werkcomputer" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Elke toets kan maar één systeembrede sneltoets uitvoeren." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Uitnodigingslink bewerken" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Sneltoets bewerken" @@ -13425,7 +13485,7 @@ msgstr "Formulier om opgeslagen media te bewerken" msgid "Edit search engine" msgstr "Zoekmachine bewerken" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Sneltoets bewerken" @@ -13753,20 +13813,16 @@ msgstr "Lege bewerking" msgid "Empty favorites" msgstr "Favorieten leeg" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Inschakelen" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Machtiging {permissionName} inschakelen" @@ -13876,11 +13932,6 @@ msgstr "Hardwareversnelling inschakelen en opnieuw opstarten" msgid "Enable hardware acceleration?" msgstr "Hardwareversnelling inschakelen?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Schakel invoertoegang in om systeembrede sneltoetsen op Wayland te gebruiken. Sluit {productName} na het wijzigen van de invoertoegang volledig af en start de app opnieuw op, zodat sneltoetsen deze toegang kunnen gebruiken." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Uitvouwen met Shift inschakelen" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Sneltoets inschakelen" @@ -14357,7 +14408,7 @@ msgstr "Volledige systeemaudio" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Binnenkomstgeluid" @@ -14467,7 +14518,7 @@ msgstr "Avond" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Maakt je microfoonvolume gelijkmatiger zodat je niet te zacht klinkt." @@ -15682,6 +15733,26 @@ msgstr "Alleen voor Britse ogen..." msgid "For me" msgstr "Voor mij" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Gebruik voor 'Indrukken om te dempen' één toets zonder Ctrl, Alt of Shift. Laat bij een combinatie eerst de hoofdtoets los, anders kan je microfoon gedempt blijven." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Gebruik voor push-to-talk één toets zonder Ctrl, Alt of Shift. Laat bij een combinatie eerst de hoofdtoets los, anders kan je microfoon aan blijven." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Gebruik voor deze sneltoets één toets zonder Ctrl, Alt of Shift. Laat bij een combinatie eerst de hoofdtoets los, anders kan de sneltoets actief blijven." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Gebruik voor 'Prioriteit voor spraakactiviteit' één toets zonder Ctrl, Alt of Shift. Laat bij een combinatie eerst de hoofdtoets los, anders kan de prioriteit aan blijven." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Bevestig voor je veiligheid dat jij het bent om verder te gaan." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Algemeen profiel" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Systeembrede sneltoets" @@ -17434,6 +17505,11 @@ msgstr "Houd Shift ingedrukt en druk op X om apps te selecteren. Druk op {select msgid "Hold to record. Drag up to lock, or release to send." msgstr "Houd ingedrukt om op te nemen. Sleep omhoog om te vergrendelen, of laat los om te verzenden." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Als je Shift, Ctrl of Alt ingedrukt houdt, wordt deze sneltoets geblokkeerd. Voeg alternatieven toe in je systeeminstellingen." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Hoera! Meldingen zijn ingeschakeld" msgid "Hyperlink" msgstr "Hyperlink" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland heeft geen venster voor sneltoetsen. Voeg de regels voor je Hyprland-versie toe aan je Hyprland-configuratie en vervang KEY door de gewenste toets." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland heeft geen venster voor sneltoetsen. Stel de toetsen in je Hyprland-configuratie in." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (vóór Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 en later)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Ik ga akkoord met het <0>privacybeleid" @@ -17576,7 +17672,7 @@ msgstr "Ik heb me bedacht" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Ik begrijp het" @@ -18000,7 +18096,7 @@ msgstr "In" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "In de modus \"Besturingssysteem\" zijn de hierboven vermelde woordenboeken alleen bedoeld als suggestie. {PRODUCT_NAME} geeft deze taaltags door aan de spellingcontrole van je besturingssysteem, als dat wordt ondersteund. Onbekende tags worden genegeerd." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "In een browser werkt push-to-talk alleen als het tabblad van {PRODUCT_NAME} actief is. Installeer de desktopapp voor systeembrede push-to-talk." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Invoer" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Invoertoegang kon niet automatisch worden ingeschakeld. Wijzig dit handmatig, sluit {productName} vervolgens volledig af en start de app opnieuw op, zodat sneltoetsen deze toegang kunnen gebruiken." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Invoertoegang kon niet worden ingeschakeld. Probeer het opnieuw via de instellingen van {settingsMenuName} en sluit {productName} daarna volledig af en start het opnieuw op." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Invoertoegang is vereist voor systeembrede toetsenbord- en muissneltoetsen op Wayland. Sluit {productName} na het wijzigen van de invoertoegang volledig af en start de app opnieuw op, zodat sneltoetsen deze toegang kunnen gebruiken." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "De invoertoegang is gewijzigd. Sluit {productName} volledig af en start de app opnieuw op, zodat systeembrede sneltoetsen deze toegang kunnen gebruiken." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Invoer en uitvoer" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Ongeldig autorisatieverzoek" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Ongeldige botmachtigingen." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Ongeldige of verlopen code. Probeer het opnieuw." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Ongeldige redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Uitnodigingslink is verborgen tijdens delen:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Uitnodigingslink gemaskeerd tijdens delen" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Ga naar {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Ga naar {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Ga naar het huidige gesprek" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Ga naar het gekoppelde kanaal" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Ga naar het gelinkte bericht" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Ga naar het bericht van {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Ga naar het bericht in {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Bewaar deze goed. Als je ze opnieuw genereert, werken bestaande integraties niet meer." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Blijf kort uitzenden nadat de knop is losgelaten." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Toetsenbord" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Toegang tot het toetsenbord kon niet worden gestart. Sneltoetsen werken alleen als {productName} actief is." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Laat de community zelf modereren" msgid "Let the member hear others in this community again" msgstr "Ervoor gezorgd dat het lid anderen in deze community weer hoort" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Hiermee kan {productName} je toetsenbord direct uitlezen. Je systeem staat dit al toe." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Laat aangepaste CSS transparante venstergebieden weergeven na het herstarten van de desktopapp." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Linkstijl" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Link niet beschikbaar" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Uiterlijk" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Het lijkt erop dat je {0} gebruikt. Als je daar de audioverwerking van {PRODUCT_NAME} bovenop toepast, kan dit pompend geluid of vervorming veroorzaken. Directe invoer stuurt je audio onbewerkt door." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Berichtweergave" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "berichtlink" @@ -21332,7 +21416,7 @@ msgstr "Microfoon" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Microfoontest" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Overig" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "client_id ontbreekt" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Meer opties" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Meer sneltoetsopties" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Geen {sendMessagesPermission}-machtiging" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Geen toegang" @@ -22984,7 +23068,7 @@ msgstr "Geen resultaten gevonden" msgid "No mentions yet" msgstr "Nog geen vermeldingen" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Geen microfoon gevonden" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Geen instellingen gevonden" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Geen sneltoets ingesteld" @@ -23358,6 +23442,11 @@ msgstr "Geen geheel getal" msgid "Not accepted" msgstr "Niet geaccepteerd" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Niet toegewezen" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Open {host} in je browser." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Open {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Open {labelText} in instellingen" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "paginalink" @@ -25611,11 +25700,11 @@ msgstr "Aanwezigheid" msgid "Press {shortcut} to react" msgstr "Druk op {shortcut} om te reageren" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Druk op een toets, muisknop of gamepadknop om toe te wijzen. Esc annuleert." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Druk op een sneltoets…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Push-to-talk-toets" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Push-to-talk" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Push-to-talk (beperkt)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Push-to-talk heeft nog geen systeembrede toets. Ga naar Instellingen > Apps > {productName} om er een in te stellen." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Push-to-talk heeft nog geen systeembrede toets. Ga naar Systeeminstellingen > Toetsenbord > Sneltoetsen > {productName} om er een in te stellen." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Push-to-talk heeft nog geen systeembrede toets. Open de sneltoetsinstellingen van je desktop om er een in te stellen." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Push-to-talk wordt genegeerd bij directe invoer. Schakel over naar gerichte spraak of aangepast om het te gebruiken." @@ -26293,21 +26397,51 @@ msgstr "Push-to-talk wordt genegeerd bij directe invoer. Schakel over naar geric msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Push-to-talk is ingeschakeld. Houd {pushToTalkHint} ingedrukt om te spreken." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Push-to-talk is ingeschakeld. Houd je push-to-talk-toets ingedrukt om te praten." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Push-to-talk werkt alleen als {productName} actief is, omdat je desktop geen systeembrede sneltoetsen heeft ingesteld." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Push-to-talk werkt alleen als {productName} actief is totdat {permissionName} is ingeschakeld. Sluit {productName} na het wijzigen van de toestemming volledig af en start de app opnieuw op, zodat systeembrede sneltoetsen kunnen werken." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Push-to-talk werkt alleen als {productName} actief is, totdat je systeembrede sneltoetsen instelt." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Push-to-talk werkt alleen als het browsertabblad is geselecteerd." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Push-to-talk gebruikt de toets die je in {productName} hebt ingesteld. Stel er eerst een in, anders gebruikt je microfoon spraakactiviteit." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Push-to-talk heft het dempen van je microfoon niet op wanneer je het geluid hebt uitgeschakeld. Schakel het geluid weer in om te praten." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Push-to-talk werkt buiten {productName} pas nadat je het hebt ingesteld in je Hyprland-configuratie." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Push-to-talk werkt buiten {productName} pas nadat je het hebt ingesteld in je Hyprland-configuratie. Voeg de regel toe voor je Hyprland-versie en vervang KEY door de gewenste toets." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Ruwe embed-JSON" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Ingebouwde sneltoets weer inschakelen" @@ -26565,13 +26699,6 @@ msgstr "Recente vermeldingen" msgid "Recent searches" msgstr "Recente zoekopdrachten" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Opnieuw controleren" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Aanbevolen" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Aanbevolen. Verbetert je microfoongeluid voor duidelijke spraak." @@ -26600,6 +26727,11 @@ msgstr "Opnieuw verbinden" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Opnieuw verbinden gelukt. Kies opnieuw een scherm als je je vorige stream wilt hervatten." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Opnieuw verbinden met je desktop…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Opnieuw verbinden…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Sneltoets opnemen" @@ -26851,7 +26983,7 @@ msgstr "Relatief" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Vertraging bij loslaten" @@ -27739,13 +27871,13 @@ msgstr "Opnieuw verzonden" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Opnieuw instellen" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Standaardinstellingen herstellen voor \"{label}\"" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Terugzetten naar werkelijke waarden" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Terugzetten naar standaard" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Selecteer het account waarmee je wilt inloggen op je nieuwe apparaat." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Selecteer spraakverwerkingsmodus" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Verstuurt onbewerkte microfoonaudio zonder verwerking." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Verstuurt je audio onbewerkt. Het beste als je externe audiosoftware gebruikt." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Instellen door de {preset} preset. Als je hier een waarde kiest, schakelt dit over naar Aangepast." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Aangepaste sneltoets instellen" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Aangepaste sneltoets instellen voor \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "De spraakregio ingesteld op {region}" msgid "Set the voice region to automatic" msgstr "De spraakregio ingesteld op automatisch" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Instellen" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Instellingen" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "instellingenlink" @@ -30330,8 +30467,8 @@ msgstr "Korte codes" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Overschakelen naar comfortabele lay-out" msgid "Switch to dense layout" msgstr "Overschakelen naar compacte lay-out" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Overschakelen naar directe invoer" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Systeembeheerde installatie" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Systeembrede sneltoetsen" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Systeembrede sneltoetsen konden niet worden ingesteld." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Systeembreed: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Systeembreed: niet toegewezen" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "De pagina die je zoekt bestaat niet of is verplaatst." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "De opgegeven redirect_uri is niet geregistreerd voor deze app." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "De opgegeven redirect_uri moet HTTP of HTTPS gebruiken." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Deze animatie is te lang of te groot om bij te snijden. Probeer een korter of kleiner bestand." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Deze app" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Deze bijlage is verlopen" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Deze bot is niet openbaar en kan niet worden toegevoegd door andere gebruikers." @@ -34081,6 +34233,16 @@ msgstr "Dit gesprek is niet meer beschikbaar." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Deze computerapp kan geen updates binnen {productName} installeren. Download het nieuwste installatieprogramma voor de computer." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Deze desktop biedt geen systeembrede sneltoetsen. Push-to-talk werkt alleen als {productName} actief is." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Deze desktop biedt geen systeembrede sneltoetsen. Sneltoetsen werken nog steeds als {productName} actief is." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Dit is je laatste passkey en passkeys zijn je tweede factor. Als je deze msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Dit is je laatste passkey. Als deze weg is, kun je geen passkey meer gebruiken als je tweede factor." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Deze toets werkt terwijl {productName} actief is. Je desktop stelt de systeembrede toets in, onder {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Dit linkkanaal is gemarkeerd voor inhoud voor volwassenen en kan materiaal openen dat ongeschikt is voor sommige gebruikers." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Deze link is niet beschikbaar voor je account." @@ -34778,6 +34945,21 @@ msgstr "Titelbalk" msgid "Title bar platform" msgstr "Titelbalkplatform" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Ga naar Instellingen > Apps > {productName} om de toetsen te wijzigen." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Ga naar Systeeminstellingen > Toetsenbord > Sneltoetsen > {productName} om de toetsen te wijzigen." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Open de sneltoetsinstellingen van je desktop om de toetsen te wijzigen." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Om een webhook te maken, open je <0>{channelWebhooksSettingsPath}. Je kunt hier nog steeds alle bestaande webhooks bewerken en organiseren." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Onbekend" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Onbekende app, sessie verlopen of ongeldige autorisatieaanvraag." @@ -36410,6 +36592,11 @@ msgstr "Tot 4K/60 FPS" msgid "Up to 720p at 30 FPS" msgstr "Tot 720p op 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Update {productName} om systeembrede sneltoetsen op Wayland te gebruiken." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Donker thema gebruiken in de hele app" msgid "Use default" msgstr "Standaard gebruiken" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Directe toegang tot invoerapparaten gebruiken" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Deelnemers spraakoproep" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Spraakverwerking" @@ -38264,7 +38456,7 @@ msgstr "Geluid inschakelen" msgid "Voice unmute" msgstr "Microfoon inschakelen" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Spraak gebruikt in deze browser het uitvoerapparaat van je systeem. Wijzig dit in je systeeminstellingen." @@ -38278,7 +38470,7 @@ msgstr "Ongeldig" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Wachten tot de slowmodus in een of meer geselecteerde kanalen is verlope msgid "Waiting for you to..." msgstr "Wachten tot je..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Wachten op je desktop…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Welke gesprekken" msgid "Which rule was broken?" msgstr "Welke regel is overtreden?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Zolang push-to-talk is ingeschakeld, bedient je sneltoets je microfoon. Daarom is de knop om te dempen uitgeschakeld." @@ -39094,6 +39291,11 @@ msgstr "Zonder deze machtiging is push-to-talk vereist." msgid "Wordmark" msgstr "Woordmerk" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Werkt terwijl {productName} actief is" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Je hebt je tag gewijzigd terwijl je {PREMIUM_PRODUCT_NAME} had. Deze wordt willekeurig opnieuw ingesteld wanneer je {PREMIUM_PRODUCT_NAME} verloopt, tenzij het wordt verlengd." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Je hebt geen toegang tot het kanaal waar dit bericht is verzonden." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Je {premiumProductName}-abonnement is verlopen. Je bent alle {premiumProductName}-voordelen kwijtgeraakt. Activeer je abonnement opnieuw om weer toegang te krijgen." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Je {productName}-sessie is verlopen. Log opnieuw in om door te gaan met het autoriseren van deze app." @@ -40144,6 +40346,16 @@ msgstr "Je community heeft minimaal {minMemberCount, plural, one {# lid} other { msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Je creditcard is gecontroleerd. Als de kaart geldig is, wordt inhoud voor volwassenen binnenkort ontgrendeld. Je kunt dit tabblad sluiten en teruggaan naar de app." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Je desktop heeft geen systeembrede sneltoetsen ingesteld." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Je desktop voert deze sneltoetsen uit, ook als {productName} niet actief is." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/no/messages.po b/fluxer_app/src/features/i18n/locales/no/messages.po index f9ba3f199..f024574a2 100644 --- a/fluxer_app/src/features/i18n/locales/no/messages.po +++ b/fluxer_app/src/features/i18n/locales/no/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# kanal følger} other {# kanaler følger}} denne k msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# fellesskap funnet} other {# fellesskap funnet}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# fellesskap er midlertidig utilgjengelig på grunn av en feil i flukskondensatoren.} other {# fellesskap er midlertidig utilgjengelige på grunn av en feil i flukskondensatoren.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} unngår å sende push-varsler til mobilenhetene dine når msgid "{productName} Bug Hunter" msgstr "{productName}-feilfinner" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} kan be skrivebordsmiljøet ditt om å kjøre snarveiene når {productName} ikke er i fokus. Skrivebordsmiljøet ber deg bekrefte og lar deg velge tastene." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} kan ikke lenger lese tastaturet ditt, så det lytter etter snarveier via X11 i stedet." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} kan ikke lenger lese tastaturet ditt, så snarveiene i skrivebordsmiljøet brukes i stedet." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} kan ikke lenger lese tastaturet ditt. Snarveier fungerer bare mens {productName} er i fokus." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} kan registrere snarveiene sine hos Hyprland slik at de kjører når {productName} ikke er i fokus. Deretter binder du tastene i Hyprland-konfigurasjonen din." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} trenger tilgang til kameraet for å vise enhetene dine." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} trenger mikrofontilgang for å vise enhetene dine." @@ -1837,6 +1862,11 @@ msgstr "{productName} holder nå til på {host}. Installer det derfra." msgid "{productName} Partner" msgstr "{productName}-partner" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} har registrert snarveiene sine hos Hyprland. Bind dem i Hyprland-konfigurasjonen din for å bruke dem når {productName} ikke er i fokus." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "En profil som skiller seg ut" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "En redirect_uri er påkrevd når bot ikke er det eneste tilgangsomfanget." @@ -2847,7 +2877,7 @@ msgstr "Bare i alle åpne direktemeldingssamtaler" msgid "Across every community you're in" msgstr "I alle fellesskapene du er med i" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Handling" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Aktivitetsdeling" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Aktivitetsterskel" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Legg til et ekstra sikkerhetslag på kontoen din" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Legg til en ny egendefinert snarvei" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Adresse" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Juster hver innstilling selv: støydemping, ekkodemping og forsterkning." @@ -3696,11 +3726,11 @@ msgstr "Tillat {everyoneMention} og {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Tillat {PRODUCT_NAME} å få tilgang til kameraet ditt i {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Gi {PRODUCT_NAME} tilgang til mikrofonen din i {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Tillat {PRODUCT_NAME} å få tilgang til mikrofonen din. Sjekk nettleserens adressefelt eller innstillinger." @@ -3803,7 +3833,7 @@ msgstr "Tillat medlemmer uten tillatelsen {readMessageHistoryPermissionLabel} å msgid "Allow mentions for this role" msgstr "Tillat omtaler for denne rollen" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Tillat mikrofontilgang" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Autorisasjon mislyktes" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Autorisasjon mislyktes. Prøv igjen." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Gjenkjenn språk automatisk" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Angi aktivitetsterskel automatisk" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Bottillatelser" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Verdien for bottillatelser kan ikke være negativ." @@ -6869,6 +6899,11 @@ msgstr "Endre kallenavn i gruppen" msgid "Change icon" msgstr "Endre ikon" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Endre i systeminnstillinger" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Kanaltilgang" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Ingen tilgang til kanal" @@ -7294,7 +7329,7 @@ msgstr "Kanal-ID kopiert til utklippstavlen" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "kanallenke" @@ -7462,6 +7497,11 @@ msgstr "chatfelt" msgid "Chat shortcuts" msgstr "Chatsnarveier" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Sjekk igjen" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Velg et vindu i systemvelgeren din" msgid "Choose an account" msgstr "Velg en konto" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Velg en handling." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Velg bakgrunn" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Velg enten guild_id eller channel_id, ikke begge." @@ -7990,7 +8030,7 @@ msgstr "Renser mikrofonlyden for tydelig tale." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Klikk for å åpne fargevelgeren og velge en ny farge." msgid "Click to react" msgstr "Klikk for å reagere" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Klikk for å registrere en snarvei" @@ -9110,7 +9150,7 @@ msgstr "Bekreftelse" msgid "Confirmation or double-click for community voice joins" msgstr "Bekreftelse eller dobbeltklikk for å bli med i talekanaler i fellesskap" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "I konflikt med {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Koble til et kamera eller en virtuell opptaksenhet, og prøv deretter p msgid "Connect a display, then try again." msgstr "Koble til en skjerm, og prøv igjen." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Koble til en mikrofon og prøv igjen." @@ -9700,6 +9740,21 @@ msgstr "Kopier invitasjonslenke" msgid "Copy invite URL" msgstr "Kopier invitasjonslenke" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Kopier linjer" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Kopier linjer for hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Kopier linjer for hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Egendefinert" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Egendefinert utvalg" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Egendefinert snarveishandling" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Alternativer for egendefinert snarvei" @@ -11466,7 +11521,7 @@ msgstr "Standard snarveier" msgid "Default translator" msgstr "Standard oversetter" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Standard:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Slett lagrede medier" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Slett snarvei" @@ -12102,13 +12157,13 @@ msgstr "Utviklerstatus" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Utviklerverktøy" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Utviklerverktøy er tilgjengelige via ansattmerket øverst i kanalen." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Deaktiver premiumfordeler på serversiden" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Deaktiver innebygd snarvei" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Deaktivere bilde-i-bilde-vinduer for skjermdeling?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Deaktiver snarvei" @@ -12888,7 +12943,7 @@ msgstr "Doner i stedet" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Last ned-knapp" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Last ned skrivebordsappen" @@ -13204,6 +13259,11 @@ msgstr "Nederlandsk" msgid "e.g., YubiKey, iPhone, work computer" msgstr "f.eks. YubiKey, iPhone, jobb-PC" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Hver tast kan bare kjøre én systemomfattende snarvei." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Rediger invitasjonslenke" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Rediger tastatursnarvei" @@ -13425,7 +13485,7 @@ msgstr "Skjema for å redigere lagrede medier" msgid "Edit search engine" msgstr "Rediger søkemotor" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Rediger snarvei" @@ -13753,20 +13813,16 @@ msgstr "Tom redigering" msgid "Empty favorites" msgstr "Tom favorittliste" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Aktiver" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Aktiver tillatelsen {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Aktiver maskinvareakselerasjon og start på nytt" msgid "Enable hardware acceleration?" msgstr "Aktivere maskinvareakselerasjon?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Aktiver inndatatilgang for å bruke systemomfattende snarveier på Wayland. Etter at du har endret tilgangen, må du avslutte {productName} helt og starte appen på nytt slik at snarveiene får tilgang." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Aktiver Shift for å utvide" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Aktiver snarvei" @@ -14357,7 +14408,7 @@ msgstr "All systemlyd" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Ankomstlyd" @@ -14467,7 +14518,7 @@ msgstr "Kveldstid" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Jevner ut mikrofonvolumet ditt slik at du ikke er for stille." @@ -15682,6 +15733,26 @@ msgstr "Kun for britiske øyne ..." msgid "For me" msgstr "For meg" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Bruk én enkelt tast uten Ctrl, Alt eller Shift for «Trykk for å dempe». Med en kombinasjon må du slippe hovedtasten først, ellers kan mikrofonen forbli dempet." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Bruk én enkelt tast uten Ctrl, Alt eller Shift for «Trykk for å snakke». Med en kombinasjon må du slippe hovedtasten først, ellers kan mikrofonen forbli på." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Bruk én enkelt tast uten Ctrl, Alt eller Shift for denne snarveien. Med en kombinasjon må du slippe hovedtasten først, ellers kan snarveien forbli aktiv." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Bruk én enkelt tast uten Ctrl, Alt eller Shift for «Taleaktivitet (prioritet)». Med en kombinasjon må du slippe hovedtasten først, ellers kan prioriteten forbli på." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Av sikkerhetshensyn må du bekrefte at det er deg, for å fortsette." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Global profil" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Global snarvei" @@ -17434,6 +17505,11 @@ msgstr "Hold nede Shift og trykk på X for å velge apper. Trykk på {selectAllS msgid "Hold to record. Drag up to lock, or release to send." msgstr "Hold inne for å ta opp. Dra opp for å låse, eller slipp for å sende." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Når du holder inne Shift, Ctrl eller Alt, blokkeres denne snarveien. Legg til alternativer i systeminnstillingene dine." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Hurra! Varsler er aktivert" msgid "Hyperlink" msgstr "Hyperkobling" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland har ingen snarveidialog. Legg til linjene for din Hyprland-versjon i din Hyprland-konfigurasjon og erstatt KEY med tasten du ønsker." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland har ingen snarveidialog. Bind tastene i Hyprland-konfigurasjonen din." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (før Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 og senere)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Jeg godtar <0>personvernreglene" @@ -17576,7 +17672,7 @@ msgstr "Jeg ombestemte meg" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Jeg forstår" @@ -18000,7 +18096,7 @@ msgstr "I" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "I modusen «Operativsystem» er ordbøkene ovenfor veiledende. {PRODUCT_NAME} sender disse språkkodene til operativsystemets stavekontroll når det støttes. Koder som ikke gjenkjennes, blir ignorert." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "I en nettleser fungerer «Trykk for å snakke» bare når {PRODUCT_NAME}-fanen er i fokus. Installer skrivebordsappen for å bruke «Trykk for å snakke» i hele systemet." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Skrivefelt" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Inndatatilgang kunne ikke aktiveres automatisk. Endre tilgangen manuelt, avslutt deretter {productName} helt og start appen på nytt slik at snarveiene får tilgang." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Inndatatilgang kunne ikke aktiveres. Prøv igjen fra innstillingene for {settingsMenuName}, avslutt deretter {productName} helt og start appen på nytt." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Inndatatilgang kreves for systemomfattende tastatur- og musesnarveier på Wayland. Etter at du har endret tilgangen, må du avslutte {productName} helt og starte appen på nytt slik at snarveiene får tilgang." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Inndatatilgangen er endret. Avslutt {productName} helt og start appen på nytt slik at systemomfattende snarveier får tilgang." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Lyd inn og ut" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Ugyldig autoriseringsforespørsel" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Ugyldige bot-tillatelser." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Ugyldig eller utløpt kode. Prøv igjen." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Ugyldig redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Invitasjonslenken er skjult under deling:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Invitasjonslenke maskert under deling" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Hopp til {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Gå til {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Gå til den pågående samtalen" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Gå til den lenkede kanalen" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Gå til den lenkede meldingen" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Gå til meldingen fra {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Gå til meldingen i {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Oppbevar disse trygt. Hvis du genererer dem på nytt, vil eksisterende integrasjoner slutte å fungere." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Fortsett å sende lyd en kort stund etter at du slipper knappen." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Tastatur" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Tastaturtilgang kunne ikke startes. Snarveier fungerer bare mens {productName} er i fokus." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "La fellesskapet moderere seg selv" msgid "Let the member hear others in this community again" msgstr "Lot medlemmet høre andre i dette fellesskapet igjen" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Lar {productName} lese tastaturet ditt direkte. Systemet ditt tillater dette allerede." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Lar egendefinert CSS vise gjennomsiktige vindusområder etter at du har startet skrivebordsappen på nytt." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Lenkestil" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Lenken er utilgjengelig" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Utseende og følelse" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Det ser ut som du bruker {0}. Hvis du legger til lydbehandlingen fra {PRODUCT_NAME} i tillegg, kan det føre til pumping eller forvrengning. Direkte inndata sender lyden din uendret gjennom." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Meldingsoppsett" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "meldingslenke" @@ -21332,7 +21416,7 @@ msgstr "Mikrofon" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Mikrofontest" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Diverse" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Mangler client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Flere alternativer" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Flere snarveialternativer" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Mangler tillatelsen «{sendMessagesPermission}»" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Ingen tilgang" @@ -22984,7 +23068,7 @@ msgstr "Ingen treff" msgid "No mentions yet" msgstr "Ingen omtaler ennå" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Ingen mikrofon funnet" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Ingen innstillinger funnet" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Ingen snarvei angitt" @@ -23358,6 +23442,11 @@ msgstr "Ikke et heltall" msgid "Not accepted" msgstr "Ikke akseptert" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Ikke tilordnet" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Åpne {host} i nettleseren din." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Åpne {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Åpne {labelText} i innstillinger" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Side ned" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "lenke til side" @@ -25611,11 +25700,11 @@ msgstr "Tilstedeværelse" msgid "Press {shortcut} to react" msgstr "Trykk {shortcut} for å reagere" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Trykk en tast, museknapp eller gamepad-knapp for å tilordne. Esc avbryter." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Trykk en snarvei …" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Tast for trykk for å snakke" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Trykk for å snakke" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Trykk for å snakke (begrenset)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "«Trykk for å snakke» har ingen systemomfattende tast ennå. Gå til Innstillinger > Apper > {productName} for å angi en." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "«Trykk for å snakke» har ingen systemomfattende tast ennå. Gå til Systeminnstillinger > Tastatur > Snarveier > {productName} for å angi en." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "«Trykk for å snakke» har ingen systemomfattende tast ennå. Åpne innstillingene for tastatursnarveier i skrivebordsmiljøet ditt for å angi en." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Trykk for å snakke ignoreres med «Direkte inndata». Bytt til «Tale i fokus» eller «Egendefinert» for å bruke det." @@ -26293,21 +26397,51 @@ msgstr "Trykk for å snakke ignoreres med «Direkte inndata». Bytt til «Tale i msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Trykk for å snakke er på. Hold inne {pushToTalkHint} for å snakke." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "«Trykk for å snakke» er på. Hold inne tasten din for å snakke." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "«Trykk for å snakke» fungerer bare når {productName} er i fokus fordi skrivebordsmiljøet ditt ikke satte opp systemomfattende snarveier." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Trykk for å snakke fungerer bare når {productName} er i fokus, til {permissionName} er aktivert. Etter at du har endret tillatelsen, må du avslutte {productName} helt og starte appen på nytt slik at hurtigtaster kan brukes i hele systemet." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "«Trykk for å snakke» fungerer bare når {productName} er i fokus, til du setter opp systemomfattende snarveier." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Trykk for å snakke fungerer bare når nettleserfanen er aktiv." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "«Trykk for å snakke» bruker tasten du angir i {productName}. Angi en først, ellers bruker mikrofonen taleaktivitet." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Trykk for å snakke slår ikke på mikrofonen mens du har slått av lyden. Slå på lyden igjen for å snakke." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "«Trykk for å snakke» fungerer utenfor {productName} først når du har bundet det i Hyprland-konfigurasjonen din." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "«Trykk for å snakke» fungerer utenfor {productName} først når du har bundet det i Hyprland-konfigurasjonen din. Legg til linjen for Hyprland-versjonen din og erstatt KEY med tasten du ønsker." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Rå JSON for innebygd innhold" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Aktiver innebygd snarvei igjen" @@ -26565,13 +26699,6 @@ msgstr "Nylige omtaler" msgid "Recent searches" msgstr "Nylige søk" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Sjekk på nytt" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Anbefalt" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Anbefalt. Renser mikrofonlyden for tydelig tale." @@ -26600,6 +26727,11 @@ msgstr "Koble til på nytt" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Tilkoblingen er gjenopprettet. Velg en skjerm på nytt hvis du vil fortsette den forrige strømmen din." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Kobler til skrivebordsmiljøet på nytt…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Kobler til på nytt …" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Ta opp snarvei" @@ -26851,7 +26983,7 @@ msgstr "Relativ" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Forsinkelse ved tastslipp" @@ -27739,13 +27871,13 @@ msgstr "Sendt på nytt" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Tilbakestill" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Tilbakestill «{label}» til innebygd snarvei" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Tilbakestill til faktiske verdier" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Tilbakestill til innebygd snarvei" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Velg kontoen du vil logge på med på den nye enheten din." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Velg modus for talebehandling" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Sender rå mikrofonlyd uten behandling." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Sender lyden din uendret. Best hvis du bruker ekstern lydprogramvare." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Angitt av {preset}-forhåndsinnstillingen. Å velge en verdi her bytter til Egendefinert." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Angi egendefinert snarvei" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Angi egendefinert snarvei for «{label}»" @@ -30066,6 +30198,11 @@ msgstr "Satte taleregionen til {region}" msgid "Set the voice region to automatic" msgstr "Satte taleregionen til automatisk" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Sett opp" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Innstillinger" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "innstillingslenke" @@ -30330,8 +30467,8 @@ msgstr "Emojikoder" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Bytt til romslig oppsett" msgid "Switch to dense layout" msgstr "Bytt til kompakt oppsett" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Bytt til direkte inndata" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Systemadministrert installasjon" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Systemomfattende snarveier" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Systemomfattende snarveier kunne ikke settes opp." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Systemomfattende: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Systemomfattende: ikke tilordnet" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Siden du leter etter finnes ikke eller har blitt flyttet." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Den oppgitte redirect_uri er ikke registrert for denne appen." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Den oppgitte redirect_uri må bruke HTTP eller HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Denne animasjonen er for lang eller for stor til å beskjæres. Prøv en kortere eller mindre fil." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Denne appen" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Dette vedlegget har utløpt" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Denne boten er ikke offentlig og kan ikke legges til av andre brukere." @@ -34081,6 +34233,16 @@ msgstr "Denne samtalen er ikke lenger tilgjengelig." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Denne skrivebordsversjonen kan ikke installere oppdateringer i {productName}. Last ned den nyeste installasjonspakken for skrivebordsappen i stedet." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Dette skrivebordsmiljøet tilbyr ikke systemomfattende snarveier. «Trykk for å snakke» fungerer bare når {productName} er i fokus." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Dette skrivebordsmiljøet tilbyr ikke systemomfattende snarveier. Snarveier fungerer fortsatt når {productName} er i fokus." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Dette er din siste passnøkkel, og passnøkler er din andre faktor. Hvis msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Dette er din siste passnøkkel. Når den er borte, kan du ikke lenger bruke en passnøkkel som din andre faktor." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Denne tasten fungerer når {productName} er i fokus. Skrivebordsmiljøet ditt angir den systemomfattende tasten, under {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Denne lenkekanalen er merket for voksent innhold og kan åpne materiale som kan være upassende for enkelte brukere." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Denne lenken er ikke tilgjengelig for kontoen din." @@ -34778,6 +34945,21 @@ msgstr "Tittellinje" msgid "Title bar platform" msgstr "Plattform for tittellinje" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Gå til Innstillinger > Apper > {productName} for å endre tastene." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Gå til Systeminnstillinger > Tastatur > Snarveier > {productName} for å endre tastene." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Åpne innstillingene for tastatursnarveier i skrivebordsmiljøet ditt for å endre tastene." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "For å opprette en webhook, åpne <0>{channelWebhooksSettingsPath}. Du kan fortsatt redigere og organisere alle eksisterende webhooker her." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Ukjent" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Ukjent app, utløpt økt eller ugyldig autorisasjonsforespørsel." @@ -36410,6 +36592,11 @@ msgstr "Opptil 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "Opptil 720p med 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Oppdater {productName} for å bruke systemomfattende snarveier på Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Bruk mørkt tema i hele appen" msgid "Use default" msgstr "Bruk standard" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Bruk direkte tilgang til inndataenheter" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Taledeltakere" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Talebehandling" @@ -38264,7 +38456,7 @@ msgstr "Lyden slått på" msgid "Voice unmute" msgstr "Mikrofonen slått på" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Tale bruker systemets utdataenhet i denne nettleseren. Endre den i systeminnstillingene." @@ -38278,7 +38470,7 @@ msgstr "Annullert" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Venter på at sakte modus skal utløpe i én eller flere valgte kanaler. msgid "Waiting for you to..." msgstr "Venter på at du skal ..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Venter på skrivebordsmiljøet…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Hvilke samtaler" msgid "Which rule was broken?" msgstr "Hvilken regel ble brutt?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Når «Trykk for å snakke» er på, styrer snarveien mikrofonen din, så dempeknappen er deaktivert." @@ -39094,6 +39291,11 @@ msgstr "Uten denne tillatelsen må «Trykk for å snakke» brukes." msgid "Wordmark" msgstr "Ordmerke" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Fungerer når {productName} er i fokus" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Du endret taggen din mens du hadde {PREMIUM_PRODUCT_NAME}. Den byttes ut med en tilfeldig tagg når {PREMIUM_PRODUCT_NAME} utløper, med mindre {PREMIUM_PRODUCT_NAME} fornyes." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Du har ikke tilgang til kanalen meldingen ble sendt i." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Abonnementet ditt på {premiumProductName} har utløpt. Du har mistet alle fordelene med {premiumProductName}. Reaktiver abonnementet ditt for å få tilgang igjen." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Økten din i {productName} har utløpt. Logg på igjen for å fortsette å autorisere denne appen." @@ -40144,6 +40346,16 @@ msgstr "Fellesskapet ditt må ha minst {minMemberCount, plural, one {# medlem} o msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Kredittkortet ditt er sjekket. Hvis kortet er gyldig, vil voksent innhold låses opp om kort tid. Du kan lukke denne fanen og gå tilbake til appen." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Skrivebordsmiljøet ditt satte ikke opp systemomfattende snarveier." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Skrivebordsmiljøet ditt kjører disse snarveiene også når {productName} ikke er i fokus." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/pl/messages.po b/fluxer_app/src/features/i18n/locales/pl/messages.po index 82be198ec..15a9cb49c 100644 --- a/fluxer_app/src/features/i18n/locales/pl/messages.po +++ b/fluxer_app/src/features/i18n/locales/pl/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# kanał obserwuje} other {# kanałów obserwuje}} msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {Znaleziono # społeczność} few {Znaleziono # społeczności} many {Znaleziono # społeczności} other {Znaleziono # społeczności}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# społeczność jest tymczasowo niedostępna z powodu awarii \"kondensatora strumienia\".} few {# społeczności są tymczasowo niedostępne z powodu awarii \"kondensatora strumienia\".} many {# społeczności jest tymczasowo niedostępnych z powodu awarii \"kondensatora strumienia\".} other {# społeczności są tymczasowo niedostępne z powodu awarii \"kondensatora strumienia\".}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} zapobiega wysyłaniu powiadomień push na urządzenia mobi msgid "{productName} Bug Hunter" msgstr "Łowca błędów {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} może poprosić środowisko graficzne o uruchamianie swoich skrótów, gdy okno {productName} nie jest aktywne. Środowisko poprosi o potwierdzenie i pozwoli wybrać klawisze." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} nie może już odczytywać klawiatury, więc nasłuchuje skrótów przez X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} nie może już odczytywać klawiatury, więc zamiast tego używane są skróty środowiska graficznego." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} nie może już odczytywać klawiatury. Skróty działają tylko wtedy, gdy okno {productName} jest aktywne." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} może zarejestrować swoje skróty w Hyprlandzie, aby działały, gdy okno {productName} nie jest aktywne. Klawisze przypisujesz potem w konfiguracji Hyprlanda." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} potrzebuje dostępu do kamery, aby wyświetlić Twoje urządzenia." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} potrzebuje dostępu do mikrofonu, aby wyświetlić Twoje urządzenia." @@ -1837,6 +1862,11 @@ msgstr "Aplikacja {productName} jest teraz dostępna pod adresem {host}. Zainsta msgid "{productName} Partner" msgstr "Partner {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "Aplikacja {productName} zarejestrowała swoje skróty w Hyprlandzie. Przypisz je w konfiguracji Hyprlanda, aby używać ich, gdy okno {productName} nie jest aktywne." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Profil, który się wyróżnia" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Parametr \"redirect_uri\" jest wymagany, gdy zakres \"bot\" nie jest jedynym zakresem." @@ -2847,7 +2877,7 @@ msgstr "Tylko we wszystkich otwartych rozmowach prywatnych" msgid "Across every community you're in" msgstr "We wszystkich społecznościach, w których jesteś" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Czynność" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Udostępnianie aktywności" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Próg aktywności" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Dodaj dodatkową warstwę zabezpieczeń do swojego konta" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Dodaj kolejny skrót niestandardowy" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Adres" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Dostosuj samodzielnie każde ustawienie: redukcję szumów, tłumienie echa i wzmocnienie." @@ -3696,11 +3726,11 @@ msgstr "Zezwól na {everyoneMention} i {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Zezwól aplikacji {PRODUCT_NAME} na dostęp do kamery w obszarze {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Zezwól aplikacji {PRODUCT_NAME} na dostęp do mikrofonu w: {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Zezwól aplikacji {PRODUCT_NAME} na dostęp do mikrofonu. Sprawdź pasek adresu lub ustawienia przeglądarki." @@ -3803,7 +3833,7 @@ msgstr "Zezwól członkom bez uprawnienia „{readMessageHistoryPermissionLabel} msgid "Allow mentions for this role" msgstr "Zezwól na wzmianki dla tej roli" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Zezwól na dostęp do mikrofonu" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Autoryzacja nieudana" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Autoryzacja nie powiodła się. Spróbuj ponownie." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Automatyczne wykrywanie języka" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Automatyczne ustawianie progu aktywności" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Uprawnienia bota" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Uprawnienia bota nie mogą być ujemne." @@ -6869,6 +6899,11 @@ msgstr "Zmień pseudonim w grupie" msgid "Change icon" msgstr "Zmień ikonę" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Zmień w ustawieniach systemowych" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Dostęp do kanału" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Brak dostępu do kanału" @@ -7294,7 +7329,7 @@ msgstr "ID kanału skopiowane do schowka" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "link do kanału" @@ -7462,6 +7497,11 @@ msgstr "pole wpisywania wiadomości" msgid "Chat shortcuts" msgstr "Skróty czatu" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Sprawdź ponownie" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Wybierz okno w systemowym oknie wyboru" msgid "Choose an account" msgstr "Wybierz konto" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Wybierz akcję." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Wybierz tło" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Wybierz guild_id lub channel_id, nie oba." @@ -7990,7 +8030,7 @@ msgstr "Oczyszcza dźwięk mikrofonu, aby mowa była wyraźna." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Kliknij, aby otworzyć próbnik kolorów i wybrać nowy kolor." msgid "Click to react" msgstr "Kliknij, żeby zareagować" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Kliknij, aby nagrać" @@ -9110,7 +9150,7 @@ msgstr "Potwierdzenie" msgid "Confirmation or double-click for community voice joins" msgstr "Potwierdzenie lub dwukrotne kliknięcie przy dołączaniu do kanałów głosowych społeczności" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Konflikt z {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Podłącz kamerę lub wirtualne urządzenie do przechwytywania, a nastę msgid "Connect a display, then try again." msgstr "Podłącz wyświetlacz, a następnie spróbuj ponownie." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Podłącz mikrofon i spróbuj ponownie." @@ -9700,6 +9740,21 @@ msgstr "Kopiuj link z zaproszeniem" msgid "Copy invite URL" msgstr "Kopiuj link z zaproszeniem" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Kopiuj wiersze" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Kopiuj wiersze do hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Kopiuj wiersze do hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Niestandardowe źródło" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Własny wybór" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Własna akcja skrótu" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Opcje niestandardowych skrótów" @@ -11466,7 +11521,7 @@ msgstr "Domyślne skróty" msgid "Default translator" msgstr "Domyślny translator" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Domyślne:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Usuń zapisane multimedia" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Usuń skrót" @@ -12102,13 +12157,13 @@ msgstr "Stan dewelopera" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Narzędzia deweloperskie" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Narzędzia deweloperskie są dostępne z poziomu odznaki zespołu w nagłówku kanału." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Wyłącz korzyści Premium po stronie serwera" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Wyłącz wbudowany skrót" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Wyłączyć odłączane okna udostępniania ekranu w trybie obrazu w obrazie?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Wyłącz skrót" @@ -12888,7 +12943,7 @@ msgstr "Przekaż darowiznę zamiast tego" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Przycisk pobierania" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Pobierz aplikację komputerową" @@ -13204,6 +13259,11 @@ msgstr "Niderlandzki" msgid "e.g., YubiKey, iPhone, work computer" msgstr "np. YubiKey, iPhone, komputer służbowy" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Każdy klawisz może uruchamiać tylko jeden skrót globalny." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Edytuj link z zaproszeniem" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Edytuj skrót klawiszowy" @@ -13425,7 +13485,7 @@ msgstr "Formularz edycji zapisanych multimediów" msgid "Edit search engine" msgstr "Edytuj wyszukiwarkę" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Edytuj skrót" @@ -13753,20 +13813,16 @@ msgstr "Pusta edycja" msgid "Empty favorites" msgstr "Brak ulubionych" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Włącz" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Włącz uprawnienie {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Włącz akcelerację sprzętową i uruchom ponownie" msgid "Enable hardware acceleration?" msgstr "Włączyć akcelerację sprzętową?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Włącz dostęp do urządzeń wejściowych, aby używać skrótów globalnych w środowisku Wayland. Po zmianie tego uprawnienia całkowicie zamknij i uruchom ponownie {productName}, aby skróty mogły z niego korzystać." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Włącz rozwijanie klawiszem Shift" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Włącz skrót" @@ -14357,7 +14408,7 @@ msgstr "Dźwięk z całego systemu" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Dźwięk wejścia" @@ -14467,7 +14518,7 @@ msgstr "Wieczór" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Wyrównuje głośność mikrofonu, aby Twój głos nie był zbyt cichy." @@ -15682,6 +15733,26 @@ msgstr "Tylko dla brytyjskich oczu..." msgid "For me" msgstr "Dla mnie" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "W przypadku funkcji „Naciśnij, aby wyciszyć mikrofon” użyj pojedynczego klawisza bez Ctrl, Alt ani Shift. Przy kombinacji najpierw puść główny klawisz, inaczej mikrofon może pozostać wyciszony." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "W przypadku funkcji „Naciśnij i mów” użyj pojedynczego klawisza bez Ctrl, Alt ani Shift. Przy kombinacji najpierw puść główny klawisz, inaczej mikrofon może pozostać włączony." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "W przypadku tego skrótu użyj pojedynczego klawisza bez Ctrl, Alt ani Shift. Przy kombinacji najpierw puść główny klawisz, inaczej skrót może pozostać aktywny." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "W przypadku priorytetu aktywności głosowej użyj pojedynczego klawisza bez Ctrl, Alt ani Shift. Przy kombinacji najpierw puść główny klawisz, inaczej priorytet może pozostać włączony." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Dla Twojego bezpieczeństwa potwierdź swoją tożsamość, aby kontynuować." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Profil globalny" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Skrót globalny" @@ -17434,6 +17505,11 @@ msgstr "Przytrzymaj Shift i naciśnij X, aby wybrać aplikacje. Naciśnij {selec msgid "Hold to record. Drag up to lock, or release to send." msgstr "Przytrzymaj, żeby nagrać. Przeciągnij w górę, żeby zablokować, lub puść, żeby wysłać." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Przytrzymanie Shift, Ctrl lub Alt blokuje ten skrót. Dodaj alternatywne skróty w ustawieniach systemowych." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Hura! Powiadomienia są włączone" msgid "Hyperlink" msgstr "Hiperłącze" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland nie ma okna do ustawiania skrótów. Dodaj wiersze dla swojej wersji Hyprlanda do jego konfiguracji i zastąp KEY wybranym klawiszem." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland nie ma okna do ustawiania skrótów. Przypisz klawisze w konfiguracji Hyprlanda." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (przed Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 i nowsze)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Akceptuję <0>politykę prywatności" @@ -17576,7 +17672,7 @@ msgstr "Jednak rezygnuję" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Rozumiem" @@ -18000,7 +18096,7 @@ msgstr "W" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "W trybie „System operacyjny” powyższa lista słowników ma charakter orientacyjny. {PRODUCT_NAME} przekaże te oznaczenia języków do systemowego modułu sprawdzania pisowni, jeśli jest to obsługiwane. Nierozpoznane oznaczenia zostaną zignorowane." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "W przeglądarce funkcja „naciśnij i mów” działa tylko wtedy, gdy karta {PRODUCT_NAME} jest aktywna. Zainstaluj aplikację komputerową, aby korzystać z tej funkcji w całym systemie." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Wprowadzanie" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Nie udało się automatycznie włączyć dostępu do urządzeń wejściowych. Włącz go ręcznie, a następnie całkowicie zamknij i uruchom ponownie {productName}, aby skróty mogły z niego korzystać." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Nie udało się włączyć dostępu do urządzeń wejściowych. Spróbuj ponownie w ustawieniach {settingsMenuName}, a następnie całkowicie zamknij i uruchom ponownie {productName}." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Dostęp do urządzeń wejściowych jest wymagany do korzystania z globalnych skrótów klawiatury i myszy w środowisku Wayland. Po zmianie tego uprawnienia całkowicie zamknij i uruchom ponownie {productName}, aby skróty mogły z niego korzystać." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Zmieniono dostęp do urządzeń wejściowych. Całkowicie zamknij i uruchom ponownie {productName}, aby skróty globalne mogły z niego korzystać." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Wejście i wyjście" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Nieprawidłowe żądanie autoryzacji" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Nieprawidłowe uprawnienia bota." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Nieprawidłowy lub wygasły kod. Spróbuj ponownie." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Nieprawidłowy parametr redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Link z zaproszeniem jest ukryty podczas udostępniania:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Link z zaproszeniem ukryty podczas udostępniania" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Przejdź do {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Przejdź do {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Przejdź do bieżącej rozmowy" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Przejdź do kanału wskazanego w linku" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Przejdź do wiadomości wskazanej w linku" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Przejdź do wiadomości od {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Przejdź do wiadomości w {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Zachowaj je w bezpiecznym miejscu. Wygenerowanie nowych spowoduje przerwanie istniejących integracji." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Kontynuuj transmisję przez krótki czas po zwolnieniu przycisku." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Klawiatura" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Nie udało się uruchomić dostępu do klawiatury. Skróty działają tylko wtedy, gdy okno {productName} jest aktywne." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Pozwól społeczności na samodzielną moderację" msgid "Let the member hear others in this community again" msgstr "Ponownie zezwolono członkowi na słyszenie innych w tej społeczności" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Pozwala aplikacji {productName} bezpośrednio odczytywać klawiaturę. Twój system już na to zezwala." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Pozwala niestandardowemu CSS na wyświetlanie przezroczystych obszarów okna po ponownym uruchomieniu aplikacji komputerowej." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Styl linków" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Link niedostępny" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Wygląd i styl" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Wygląda na to, że używasz {0}. Dodatkowe przetwarzanie dźwięku przez {PRODUCT_NAME} może powodować pompowanie głośności lub zniekształcenia. Tryb „Bezpośrednie wejście” przepuszcza dźwięk bez zmian." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Układ wiadomości" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "link do wiadomości" @@ -21332,7 +21416,7 @@ msgstr "Mikrofon" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Test mikrofonu" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Różne" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Brak client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Więcej opcji" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Więcej opcji skrótów" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Brak uprawnienia „{sendMessagesPermission}”" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Brak dostępu" @@ -22984,7 +23068,7 @@ msgstr "Brak wyników" msgid "No mentions yet" msgstr "Brak wzmianek" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Nie znaleziono mikrofonu" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Nie znaleziono ustawień" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Brak skrótu" @@ -23358,6 +23442,11 @@ msgstr "To nie jest liczba całkowita" msgid "Not accepted" msgstr "Nie zaakceptowano" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Nie przypisano" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Otwórz {host} w przeglądarce." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Otwórz {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Otwórz {labelText} w ustawieniach" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "link do strony" @@ -25611,11 +25700,11 @@ msgstr "Obecność" msgid "Press {shortcut} to react" msgstr "Naciśnij {shortcut}, aby zareagować" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Naciśnij klawisz, przycisk myszy lub przycisk pada, aby go przypisać. Naciśnij Esc, aby anulować." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Naciśnij skrót…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Przycisk „naciśnij i mów”" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Naciśnij i mów" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Naciśnij i mów (ograniczone)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Funkcja „Naciśnij i mów” nie ma jeszcze globalnego klawisza. Aby go ustawić, przejdź do Ustawienia > Programy > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Funkcja „Naciśnij i mów” nie ma jeszcze globalnego klawisza. Aby go ustawić, przejdź do Ustawienia systemowe > Klawiatura > Skróty > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Funkcja „Naciśnij i mów” nie ma jeszcze globalnego klawisza. Aby go ustawić, otwórz ustawienia skrótów klawiszowych w swoim środowisku graficznym." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Funkcja „naciśnij i mów” jest ignorowana w trybie „Bezpośrednie wejście”. Aby z niej korzystać, przełącz profil na „Wyrazisty głos” lub „Niestandardowy”." @@ -26293,21 +26397,51 @@ msgstr "Funkcja „naciśnij i mów” jest ignorowana w trybie „Bezpośrednie msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Funkcja „naciśnij i mów” jest włączona. Przytrzymaj {pushToTalkHint}, aby mówić." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Funkcja „Naciśnij i mów” jest włączona. Przytrzymaj klawisz „Naciśnij i mów”, aby mówić." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Funkcja „Naciśnij i mów” działa tylko wtedy, gdy okno {productName} jest aktywne, ponieważ środowisko graficzne nie skonfigurowało skrótów globalnych." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Dopóki nie włączysz uprawnienia „{permissionName}”, funkcja „naciśnij i mów” działa tylko wtedy, gdy {productName} jest aktywny. Po zmianie uprawnienia całkowicie zamknij i uruchom ponownie {productName}, aby skróty globalne mogły z niego korzystać." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Funkcja „Naciśnij i mów” działa tylko wtedy, gdy okno {productName} jest aktywne, dopóki nie skonfigurujesz skrótów globalnych." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Naciśnij i mów działa tylko, gdy karta przeglądarki jest aktywna." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Funkcja „Naciśnij i mów” użyje klawisza ustawionego w {productName}. Najpierw go ustaw, inaczej mikrofon będzie działał w trybie aktywności głosowej." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Funkcja „naciśnij i mów” nie włączy mikrofonu, gdy masz wyłączony dźwięk. Włącz dźwięk, aby rozmawiać." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Funkcja „Naciśnij i mów” działa poza {productName} dopiero po przypisaniu klawisza w konfiguracji Hyprlanda." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Funkcja „Naciśnij i mów” działa poza {productName} dopiero po przypisaniu klawisza w konfiguracji Hyprlanda. Dodaj wiersz dla swojej wersji Hyprlanda i zastąp KEY wybranym klawiszem." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Surowy JSON osadzonej treści" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Włącz ponownie wbudowany skrót" @@ -26565,13 +26699,6 @@ msgstr "Ostatnie wzmianki" msgid "Recent searches" msgstr "Ostatnie wyszukiwania" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Sprawdź ponownie" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Zalecane" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Zalecane. Oczyszcza dźwięk z mikrofonu, aby mowa była wyraźna." @@ -26600,6 +26727,11 @@ msgstr "Połącz ponownie" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Połączono ponownie. Wybierz ekran jeszcze raz, jeśli chcesz wznowić poprzednią transmisję." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Ponowne łączenie ze środowiskiem graficznym…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Ponowne łączenie…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Nagraj skrót" @@ -26851,7 +26983,7 @@ msgstr "Względny" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Opóźnienie zwolnienia" @@ -27739,13 +27871,13 @@ msgstr "Wysłano ponownie" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Resetuj" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Przywróć wbudowaną wartość „{label}”" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Przywróć rzeczywiste wartości" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Przywróć wbudowane" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Wybierz konto, na które chcesz się zalogować na nowym urządzeniu." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Wybierz tryb przetwarzania głosu" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Wysyła surowy dźwięk z mikrofonu bez przetwarzania." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Wysyła dźwięk bez zmian. Najlepiej, jeśli używasz zewnętrznego oprogramowania audio." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Ustawione przez preset {preset}. Wybranie wartości tutaj przełączy na Niestandardowe." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Ustaw niestandardowy skrót" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Ustaw niestandardowy skrót dla „{label}”" @@ -30066,6 +30198,11 @@ msgstr "Ustawiono region głosowy na {region}" msgid "Set the voice region to automatic" msgstr "Ustawiono region głosowy na automatyczny" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Skonfiguruj" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Ustawienia" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "link do ustawień" @@ -30330,8 +30467,8 @@ msgstr "Krótkie kody" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Przełącz na wygodny układ" msgid "Switch to dense layout" msgstr "Przełącz na zwarty układ" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Przełącz na bezpośrednie wejście" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Instalacja zarządzana przez system" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Skróty globalne" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Nie udało się skonfigurować skrótów globalnych." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Globalnie: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Globalnie: nie przypisano" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Strona, której szukasz, nie istnieje lub została przeniesiona." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Podany adres redirect_uri nie jest zarejestrowany dla tej aplikacji." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Podany adres redirect_uri musi używać protokołu HTTP lub HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Ten plik animacji jest za długi lub za duży, aby go przyciąć. Spróbuj użyć krótszego lub mniejszego pliku." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Ta aplikacja" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Ten załącznik wygasł" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Ten bot nie jest publiczny i nie może być dodawany przez innych użytkowników." @@ -34081,6 +34233,16 @@ msgstr "Ta rozmowa jest już niedostępna." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Ta wersja aplikacji komputerowej nie pozwala instalować aktualizacji wewnątrz {productName}. Pobierz najnowszy instalator aplikacji komputerowej." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "To środowisko graficzne nie obsługuje skrótów globalnych. Funkcja „Naciśnij i mów” działa tylko wtedy, gdy okno {productName} jest aktywne." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "To środowisko graficzne nie obsługuje skrótów globalnych. Skróty nadal działają, gdy okno {productName} jest aktywne." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "To Twój ostatni klucz dostępu, a klucze dostępu są Twoim drugim czyn msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "To Twój ostatni klucz dostępu. Po jego usunięciu nie będzie można używać klucza dostępu jako drugiego czynnika uwierzytelniania." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Ten klawisz działa, gdy okno {productName} jest aktywne. Klawisz globalny ustawia środowisko graficzne, w sekcji {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Ten kanał z linkami jest oznaczony jako zawierający treści dla dorosłych i może otwierać materiały, które mogą być nieodpowiednie dla niektórych użytkowników." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Ten link jest niedostępny dla Twojego konta." @@ -34778,6 +34945,21 @@ msgstr "Pasek tytułowy" msgid "Title bar platform" msgstr "Platforma paska tytułu" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Aby zmienić klawisze, przejdź do Ustawienia > Programy > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Aby zmienić klawisze, przejdź do Ustawienia systemowe > Klawiatura > Skróty > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Aby zmienić klawisze, otwórz ustawienia skrótów klawiszowych w swoim środowisku graficznym." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Aby utworzyć webhook, otwórz <0>{channelWebhooksSettingsPath}. Tutaj możesz edytować i organizować wszystkie istniejące webhooki." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Nieznany" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Nieznana aplikacja, wygasła sesja lub nieprawidłowe żądanie autoryzacji." @@ -36410,6 +36592,11 @@ msgstr "Do 4K/60 kl./s" msgid "Up to 720p at 30 FPS" msgstr "Do 720p przy 30 kl./s" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Zaktualizuj {productName}, aby używać skrótów globalnych w sesji Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Używaj ciemnego motywu w całej aplikacji" msgid "Use default" msgstr "Użyj domyślnych" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Używaj bezpośredniego dostępu do urządzeń wejściowych" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Uczestnicy rozmowy głosowej" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Przetwarzanie głosu" @@ -38264,7 +38456,7 @@ msgstr "Włączenie dźwięku" msgid "Voice unmute" msgstr "Włączenie mikrofonu" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "W tej przeglądarce rozmowy głosowe korzystają z systemowego urządzenia wyjściowego. Zmień je w ustawieniach systemu." @@ -38278,7 +38470,7 @@ msgstr "Anulowano" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Oczekiwanie na koniec odstępu między wiadomościami wymaganego przez t msgid "Waiting for you to..." msgstr "Czekam, aż..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Oczekiwanie na środowisko graficzne…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Które rozmowy" msgid "Which rule was broken?" msgstr "Która zasada została złamana?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Gdy funkcja „Naciśnij i mów” jest włączona, skrót steruje mikrofonem, więc przycisk wyciszenia jest nieaktywny." @@ -39094,6 +39291,11 @@ msgstr "Bez tego uprawnienia wymagane jest używanie funkcji „Naciśnij i mów msgid "Wordmark" msgstr "Logotyp" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Działa, gdy okno {productName} jest aktywne" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Zmieniono Twój tag w trakcie korzystania z {PREMIUM_PRODUCT_NAME}. Zostanie on losowo zmieniony po wygaśnięciu subskrypcji {PREMIUM_PRODUCT_NAME}, chyba że ją odnowisz." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Nie masz dostępu do kanału, na który wysłano tę wiadomość." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Twoja subskrypcja {premiumProductName} wygasła. Wszystkie korzyści z {premiumProductName} zostały wyłączone. Aktywuj subskrypcję ponownie, aby odzyskać dostęp." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Twoja sesja {productName} wygasła. Zaloguj się ponownie, aby kontynuować autoryzację tej aplikacji." @@ -40144,6 +40346,16 @@ msgstr "Twoja społeczność musi mieć co najmniej {minMemberCount, plural, one msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Twoja karta kredytowa została zweryfikowana. Jeśli karta jest ważna, treści dla dorosłych zostaną wkrótce odblokowane. Możesz zamknąć tę kartę i wrócić do aplikacji." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Środowisko graficzne nie skonfigurowało skrótów globalnych." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Środowisko graficzne uruchamia te skróty, nawet gdy okno {productName} nie jest aktywne." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/pt-BR/messages.po b/fluxer_app/src/features/i18n/locales/pt-BR/messages.po index f9bbce5d7..163239861 100644 --- a/fluxer_app/src/features/i18n/locales/pt-BR/messages.po +++ b/fluxer_app/src/features/i18n/locales/pt-BR/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# canal segue} other {# canais seguem}} este canal. msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, =0 {# comunidades encontradas} one {# comunidade encontrada} other {# comunidades encontradas}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, =0 {# comunidades estão temporariamente indisponíveis devido a um defeito no capacitor de fluxo.} one {# comunidade está temporariamente indisponível devido a um defeito no capacitor de fluxo.} other {# comunidades estão temporariamente indisponíveis devido a um defeito no capacitor de fluxo.}}" @@ -1708,6 +1708,31 @@ msgstr "O {productName} evita enviar notificações por push para seus dispositi msgid "{productName} Bug Hunter" msgstr "Caçador de bugs do {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "O {productName} pode pedir ao seu ambiente gráfico para executar os atalhos dele mesmo quando o {productName} não estiver em foco. Seu ambiente gráfico pedirá sua confirmação e deixará você escolher as teclas." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "O {productName} não consegue mais ler seu teclado, então passa a detectar os atalhos pelo X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "O {productName} não consegue mais ler seu teclado, então os atalhos do seu ambiente gráfico são usados no lugar." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "O {productName} não consegue mais ler seu teclado. Os atalhos só funcionam enquanto o {productName} estiver em foco." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "O {productName} pode registrar os atalhos dele no Hyprland para que funcionem mesmo quando o {productName} não estiver em foco. Depois, você define as teclas na configuração do Hyprland." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "O {productName} precisa de acesso à câmera para listar seus dispositivos." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "O {productName} precisa de acesso ao microfone para listar seus dispositivos." @@ -1837,6 +1862,11 @@ msgstr "O {productName} agora está em {host}. Instale-o de lá." msgid "{productName} Partner" msgstr "Parceiro {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "O {productName} registrou os atalhos dele no Hyprland. Defina as teclas na configuração do Hyprland para usá-los quando o {productName} não estiver em foco." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Um perfil que se destaca" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Um redirect_uri é obrigatório quando o escopo do bot não é o único escopo." @@ -2847,7 +2877,7 @@ msgstr "Somente nas conversas por mensagem direta abertas" msgid "Across every community you're in" msgstr "Em todas as comunidades de que você participa" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Ação" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Compartilhamento de atividade" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Limite de atividade" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Adicione uma camada extra de segurança à sua conta" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Adicionar outro atalho personalizado" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Endereço" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Ajuste cada configuração: supressão de ruído, cancelamento de eco e ganho." @@ -3696,11 +3726,11 @@ msgstr "Permitir {everyoneMention} e {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Permita que o {PRODUCT_NAME} acesse sua câmera em {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Permita que o {PRODUCT_NAME} acesse seu microfone em {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Permita que o {PRODUCT_NAME} acesse seu microfone. Verifique a barra de endereço ou as configurações do seu navegador." @@ -3803,7 +3833,7 @@ msgstr "Permitir que membros sem a permissão {readMessageHistoryPermissionLabel msgid "Allow mentions for this role" msgstr "Permitir menções a este cargo" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Permitir acesso ao microfone" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Falha na autorização" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Falha na autorização. Tente novamente." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Detectar idioma automaticamente" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Definir limite de atividade automaticamente" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Permissões do bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "As permissões do bot não podem ser negativas." @@ -6869,6 +6899,11 @@ msgstr "Alterar apelido do grupo" msgid "Change icon" msgstr "Alterar ícone" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Alterar nas configurações do sistema" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Acesso ao canal" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Acesso ao canal negado" @@ -7294,7 +7329,7 @@ msgstr "ID do canal copiado para a área de transferência" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "link do canal" @@ -7462,6 +7497,11 @@ msgstr "entrada do chat" msgid "Chat shortcuts" msgstr "Atalhos de chat" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Verificar novamente" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Escolha uma janela no seletor do sistema" msgid "Choose an account" msgstr "Escolha uma conta" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Escolha uma ação." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Escolher plano de fundo" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Escolha guild_id ou channel_id, não ambos." @@ -7990,7 +8030,7 @@ msgstr "Melhora o áudio do microfone para uma fala mais nítida." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Clique para abrir o seletor de cores e escolher uma nova cor." msgid "Click to react" msgstr "Clique para reagir" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Clique para gravar" @@ -9110,7 +9150,7 @@ msgstr "Confirmação" msgid "Confirmation or double-click for community voice joins" msgstr "Confirmação ou duplo clique para entrar em canais de voz da comunidade" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Conflita com {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Conecte uma câmera ou dispositivo de captura virtual e tente novamente. msgid "Connect a display, then try again." msgstr "Conecte um monitor e tente novamente." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Conecte um microfone e tente novamente." @@ -9700,6 +9740,21 @@ msgstr "Copiar link de convite" msgid "Copy invite URL" msgstr "Copiar link de convite" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Copiar linhas" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Copiar linhas para hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Copiar linhas para hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Personalizado" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Seleção personalizada" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Ação de atalho personalizada" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Opções de atalho personalizado" @@ -11466,7 +11521,7 @@ msgstr "Atalhos padrão" msgid "Default translator" msgstr "Tradutor padrão" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Padrão:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Excluir mídia salva" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Excluir atalho" @@ -12102,13 +12157,13 @@ msgstr "Estado do desenvolvedor" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Ferramentas do desenvolvedor" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "As ferramentas de desenvolvedor estão disponíveis no selo da equipe no cabeçalho do canal." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Desativar benefícios premium do back-end" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Desativar atalho integrado" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Desativar janelas flutuantes de compartilhamento de tela?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Desativar atalho" @@ -12888,7 +12943,7 @@ msgstr "Doe em vez disso" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Botão de download" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Baixar app para computador" @@ -13204,6 +13259,11 @@ msgstr "Holandês" msgid "e.g., YubiKey, iPhone, work computer" msgstr "ex: YubiKey, iPhone, computador do trabalho" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Cada tecla pode executar apenas um atalho global." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Editar link de convite" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Editar atalho do teclado" @@ -13425,7 +13485,7 @@ msgstr "Formulário de edição de mídia salva" msgid "Edit search engine" msgstr "Editar buscador" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Editar atalho" @@ -13753,20 +13813,16 @@ msgstr "Edição vazia" msgid "Empty favorites" msgstr "Favoritos vazios" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Ativar" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Ativar a permissão {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Ativar aceleração de hardware e reiniciar" msgid "Enable hardware acceleration?" msgstr "Ativar aceleração de hardware?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Ative o acesso de entrada para usar atalhos em todo o sistema no Wayland. Depois de alterar essa permissão, feche completamente e reinicie o {productName} para aplicá-la aos atalhos." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Ativar Shift para expandir" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Ativar atalho" @@ -14357,7 +14408,7 @@ msgstr "Áudio do sistema inteiro" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Som de entrada" @@ -14467,7 +14518,7 @@ msgstr "Noite" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Equilibra o volume do microfone para que sua voz não fique baixa demais." @@ -15682,6 +15733,26 @@ msgstr "Apenas para britânicos..." msgid "For me" msgstr "Para mim" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Para \"Apertar para silenciar\", use uma única tecla sem Ctrl, Alt ou Shift. Com uma combinação, solte primeiro a tecla principal ou o microfone pode continuar silenciado." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Para \"Apertar para falar\", use uma única tecla sem Ctrl, Alt ou Shift. Com uma combinação, solte primeiro a tecla principal ou o microfone pode continuar ligado." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Para este atalho, use uma única tecla sem Ctrl, Alt ou Shift. Com uma combinação, solte primeiro a tecla principal ou o atalho pode permanecer ativo." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Para \"Prioridade de atividade de voz\", use uma única tecla sem Ctrl, Alt ou Shift. Com uma combinação, solte primeiro a tecla principal ou a prioridade pode continuar ativa." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Para sua segurança, confirme que é você para continuar." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Perfil global" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Atalho global" @@ -17434,6 +17505,11 @@ msgstr "Mantenha Shift pressionado e pressione X para selecionar aplicativos. Pr msgid "Hold to record. Drag up to lock, or release to send." msgstr "Segure para gravar. Arraste para cima para travar ou solte para enviar." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Segurar Shift, Ctrl ou Alt bloqueia este atalho. Adicione alternativas nas configurações do sistema." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Oba! As notificações estão ativadas" msgid "Hyperlink" msgstr "Hiperlink" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "O Hyprland não tem uma janela de atalhos. Adicione as linhas da sua versão do Hyprland à configuração do Hyprland e substitua KEY pela tecla desejada." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "O Hyprland não tem uma janela de atalhos. Defina as teclas na configuração do Hyprland." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (antes do Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 ou posterior)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Eu concordo com a <0>Política de privacidade" @@ -17576,7 +17672,7 @@ msgstr "Mudei de ideia" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Entendi" @@ -18000,7 +18096,7 @@ msgstr "Em" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "No modo \"Sistema operacional\", os dicionários listados acima são apenas sugestões. O {PRODUCT_NAME} passará essas tags de idioma para o corretor ortográfico do seu SO quando compatível. As tags que ele não reconhece são ignoradas." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "No navegador, \"Apertar para falar\" só funciona enquanto a aba do {PRODUCT_NAME} estiver em foco. Instale o aplicativo para computador para usar esse recurso em todo o sistema." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Entrada" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Não foi possível ativar o acesso de entrada automaticamente. Altere a permissão manualmente, feche completamente o {productName} e reinicie-o para que os atalhos possam usá-la." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Não foi possível ativar o acesso de entrada. Tente novamente nas configurações de {settingsMenuName}, feche completamente o {productName} e reinicie-o." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "O acesso de entrada é necessário para atalhos de teclado e mouse em todo o sistema no Wayland. Depois de alterar essa permissão, feche completamente e reinicie o {productName} para que os atalhos possam usá-la." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "O acesso de entrada foi alterado. Feche completamente e reinicie o {productName} para aplicar a permissão aos atalhos de todo o sistema." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Entrada e saída" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Solicitação de autorização inválida" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Permissões de bot inválidas." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Código inválido ou expirado. Tente novamente." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "redirect_uri inválido" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "O link de convite está oculto enquanto você compartilha:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Link de convite mascarado durante o compartilhamento" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Ir para {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Ir para {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Ir para a chamada atual" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Ir para o canal vinculado" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Ir para a mensagem vinculada" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Ir para a mensagem de {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Ir para a mensagem em {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Mantenha-os em segurança. Gerar novamente vai quebrar as integrações existentes." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Continue transmitindo por um breve período após soltar o botão." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Teclado" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Não foi possível iniciar o acesso ao teclado. Os atalhos só funcionam enquanto o {productName} estiver em foco." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Deixar a comunidade se moderar" msgid "Let the member hear others in this community again" msgstr "Permitiu que o membro ouvisse os outros nesta comunidade novamente" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Permite que o {productName} leia seu teclado diretamente. Seu sistema já permite isso." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Permitir que o CSS personalizado revele regiões transparentes da janela após reiniciar o aplicativo para computador." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Estilo de link" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Link indisponível" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Aparência" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Parece que você está usando {0}. Usar o processamento do {PRODUCT_NAME} em conjunto pode causar bombeamento ou distorção. A entrada direta passa seu áudio sem alterações." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Layout da mensagem" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "link da mensagem" @@ -21332,7 +21416,7 @@ msgstr "Microfone" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Teste de microfone" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Diversos" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "client_id ausente" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Mais opções" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Mais opções de atalho" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Sem a permissão {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Sem acesso" @@ -22984,7 +23068,7 @@ msgstr "Nenhum resultado encontrado" msgid "No mentions yet" msgstr "Nenhuma menção ainda" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Nenhum microfone encontrado" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Nenhuma configuração encontrada" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Nenhum atalho definido" @@ -23358,6 +23442,11 @@ msgstr "Não é um número inteiro" msgid "Not accepted" msgstr "Não aceito" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Não atribuído" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Abra {host} no seu navegador." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Abrir {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Abrir {labelText} nas configurações" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "link da página" @@ -25611,11 +25700,11 @@ msgstr "Presença" msgid "Press {shortcut} to react" msgstr "Pressione {shortcut} para reagir" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Pressione uma tecla, botão do mouse ou botão do gamepad para vincular. Esc cancela." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Pressione um atalho…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Tecla para apertar e falar" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Apertar para falar" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Apertar para falar (limitado)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "\"Apertar para falar\" ainda não tem uma tecla global. Para definir uma, vá em Configurações > Aplicativos > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "\"Apertar para falar\" ainda não tem uma tecla global. Para definir uma, vá em Configurações do sistema > Teclado > Atalhos > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "\"Apertar para falar\" ainda não tem uma tecla global. Para definir uma, abra as configurações de atalhos de teclado do seu ambiente gráfico." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "\"Apertar para falar\" é ignorado no modo Entrada direta. Mude para Voz focada ou Personalizado para usar o recurso." @@ -26293,21 +26397,51 @@ msgstr "\"Apertar para falar\" é ignorado no modo Entrada direta. Mude para Voz msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "\"Apertar para falar\" está ativado. Mantenha {pushToTalkHint} pressionado para falar." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "\"Apertar para falar\" está ativado. Mantenha sua tecla de \"Apertar para falar\" pressionada para falar." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "\"Apertar para falar\" só funciona enquanto o {productName} estiver em foco porque seu ambiente gráfico não configurou os atalhos globais." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "\"Apertar para falar\" só funciona enquanto o {productName} estiver em foco até que a permissão {permissionName} seja ativada. Depois de alterar a permissão, feche completamente e reinicie o {productName} para aplicá-la aos atalhos de todo o sistema." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "\"Apertar para falar\" só funciona enquanto o {productName} estiver em foco até você configurar os atalhos globais." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "O \"Apertar para falar\" só funciona enquanto a aba do navegador estiver em foco." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "\"Apertar para falar\" usará a tecla definida no {productName}. Defina uma primeiro ou seu microfone usará a atividade de voz." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "\"Apertar para falar\" não ativará seu microfone enquanto o áudio estiver desativado. Reative o áudio para falar." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "\"Apertar para falar\" só funciona fora do {productName} depois que você definir a tecla na configuração do Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "\"Apertar para falar\" só funciona fora do {productName} depois que você definir a tecla na configuração do Hyprland. Adicione a linha da sua versão do Hyprland e substitua KEY pela tecla desejada." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "JSON bruto do conteúdo incorporado" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Reativar atalho integrado" @@ -26565,13 +26699,6 @@ msgstr "Menções recentes" msgid "Recent searches" msgstr "Pesquisas recentes" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Verificar novamente" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Recomendado" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Recomendado. Melhora o áudio do microfone para uma fala mais nítida." @@ -26600,6 +26727,11 @@ msgstr "Reconectar" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Reconexão bem-sucedida. Escolha uma tela novamente se quiser retomar sua transmissão anterior." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Reconectando ao seu ambiente gráfico…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Reconectando…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Gravar atalho" @@ -26851,7 +26983,7 @@ msgstr "Relativo" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Atraso após soltar a tecla" @@ -27739,13 +27871,13 @@ msgstr "Reenviado" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Redefinir" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Redefinir \"{label}\" para o padrão" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Redefinir para os valores reais" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Redefinir para o padrão" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Selecione a conta com a qual você deseja entrar no seu novo dispositivo." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Selecionar modo de processamento de voz" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Envia o áudio bruto do microfone sem processamento." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Envia seu áudio sem alterações. Ideal se você estiver usando um software de áudio externo." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Definido pelo preset {preset}. Escolher um valor aqui muda para Personalizado." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Definir atalho personalizado" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Definir atalho personalizado para \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "Definiu a região de voz para {region}" msgid "Set the voice region to automatic" msgstr "Definiu a região de voz como automática" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Configurar" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Configurações" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "link de configurações" @@ -30330,8 +30467,8 @@ msgstr "Códigos curtos" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Mudar para o layout confortável" msgid "Switch to dense layout" msgstr "Mudar para layout compacto" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Mudar para entrada direta" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Instalação gerenciada pelo sistema" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Atalhos do sistema" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Não foi possível configurar os atalhos globais." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Global: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Global: não atribuído" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "A página que você está procurando não existe ou foi movida." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "O redirect_uri fornecido não está registrado para este aplicativo." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "O redirect_uri fornecido precisa usar HTTP ou HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Esta animação é muito longa ou grande para ser cortada. Tente um arquivo menor ou mais curto." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Este aplicativo" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Este anexo expirou" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Este bot não é público e não pode ser adicionado por outros usuários." @@ -34081,6 +34233,16 @@ msgstr "Esta conversa não está mais disponível." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Esta versão para computador não pode instalar atualizações dentro do {productName}. Baixe o instalador mais recente em vez disso." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Este ambiente gráfico não oferece atalhos globais. \"Apertar para falar\" só funciona enquanto o {productName} estiver em foco." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Este ambiente gráfico não oferece atalhos globais. Os atalhos continuam funcionando enquanto o {productName} estiver em foco." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Esta é sua última chave de acesso, e as chaves de acesso são seu segu msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Esta é sua última chave de acesso. Depois que ela for removida, você não poderá mais usar uma chave de acesso como seu segundo fator." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Esta tecla funciona enquanto o {productName} estiver em foco. A tecla global é definida pelo seu ambiente gráfico, em {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Este canal de link está marcado para conteúdo adulto e pode abrir material que talvez seja impróprio para alguns usuários." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Este link não está disponível para sua conta." @@ -34778,6 +34945,21 @@ msgstr "Barra de título" msgid "Title bar platform" msgstr "Plataforma da barra de título" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Para alterar as teclas, vá em Configurações > Aplicativos > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Para alterar as teclas, vá em Configurações do sistema > Teclado > Atalhos > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Para alterar as teclas, abra as configurações de atalhos de teclado do seu ambiente gráfico." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Para criar um webhook, abra <0>{channelWebhooksSettingsPath}. Você ainda pode editar e organizar todos os webhooks existentes aqui." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Desconhecido" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Aplicativo desconhecido, sessão expirada ou solicitação de autorização inválida." @@ -36410,6 +36592,11 @@ msgstr "Até 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "Até 720p a 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Atualize o {productName} para usar atalhos globais no Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Usar tema escuro em todo o aplicativo" msgid "Use default" msgstr "Usar padrão" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Usar acesso direto ao dispositivo de entrada" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Participantes da chamada de voz" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Processamento de voz" @@ -38264,7 +38456,7 @@ msgstr "Áudio reativado" msgid "Voice unmute" msgstr "Microfone reativado" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Neste navegador, as chamadas de voz usam o dispositivo de saída do sistema. Altere-o nas configurações do sistema." @@ -38278,7 +38470,7 @@ msgstr "Anulado" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Aguardando o modo lento expirar em um ou mais canais selecionados." msgid "Waiting for you to..." msgstr "Esperando você..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Aguardando seu ambiente gráfico…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Quais conversas" msgid "Which rule was broken?" msgstr "Qual regra foi quebrada?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Enquanto apertar para falar estiver ativado, seu atalho controlará o microfone, por isso o botão de silenciar ficará desativado." @@ -39094,6 +39291,11 @@ msgstr "Sem essa permissão, o modo \"apertar para falar\" é obrigatório." msgid "Wordmark" msgstr "Logotipo com o nome da marca" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Funciona enquanto o {productName} estiver em foco" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Você mudou sua tag enquanto usava o {PREMIUM_PRODUCT_NAME}. Ela será alterada aleatoriamente quando seu {PREMIUM_PRODUCT_NAME} expirar, a menos que seja renovado." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Você não tem acesso ao canal onde esta mensagem foi enviada." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Sua assinatura {premiumProductName} expirou. Você perdeu todos os benefícios do {premiumProductName}. Reative sua assinatura para ter acesso novamente." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Sua sessão do {productName} expirou. Faça login novamente para continuar autorizando este aplicativo." @@ -40144,6 +40346,16 @@ msgstr "Sua comunidade precisa de pelo menos {minMemberCount, plural, one {# mem msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Seu cartão de crédito foi verificado. Se o cartão for válido, o conteúdo adulto será desbloqueado em breve. Você pode fechar esta aba e voltar para o aplicativo." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Seu ambiente gráfico não configurou os atalhos globais." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Seu ambiente gráfico executa estes atalhos mesmo quando o {productName} não está em foco." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/ro/messages.po b/fluxer_app/src/features/i18n/locales/ro/messages.po index d0f3d4682..fbc42a0c0 100644 --- a/fluxer_app/src/features/i18n/locales/ro/messages.po +++ b/fluxer_app/src/features/i18n/locales/ro/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# canal urmărește} other {# canale urmăresc}} ac msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# comunitate găsită} few {# comunități găsite} other {# de comunități găsite}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# comunitate este temporar indisponibilă din cauza unei defecțiuni a condensatorului de flux.} few {# comunități sunt temporar indisponibile din cauza unei defecțiuni a condensatorului de flux.} other {# de comunități sunt temporar indisponibile din cauza unei defecțiuni a condensatorului de flux.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} evită trimiterea de notificări push către dispozitivele msgid "{productName} Bug Hunter" msgstr "Vânător de buguri {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} poate cere mediului desktop să-i ruleze comenzile rapide cât timp {productName} nu este în prim-plan. Mediul desktop îți va cere confirmarea și te va lăsa să alegi tastele." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} nu îți mai poate citi tastatura, așa că ascultă comenzile rapide prin X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} nu îți mai poate citi tastatura, așa că sunt folosite comenzile rapide ale mediului desktop." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} nu îți mai poate citi tastatura. Comenzile rapide funcționează doar cât timp {productName} este în prim-plan." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} își poate înregistra comenzile rapide în Hyprland, ca să funcționeze cât timp {productName} nu este în prim-plan. Apoi atribui tastele în configurația Hyprland." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} are nevoie de acces la cameră pentru a-ți afișa dispozitivele." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} are nevoie de acces la microfon pentru a-ți afișa dispozitivele." @@ -1837,6 +1862,11 @@ msgstr "{productName} se află acum la adresa {host}. Instalează-l de acolo." msgid "{productName} Partner" msgstr "Partener {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} și-a înregistrat comenzile rapide în Hyprland. Atribuie-le taste în configurația Hyprland ca să le folosești cât timp {productName} nu este în prim-plan." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Un profil care iese în evidență" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Este necesar un redirect_uri atunci când domeniul bot nu este singurul domeniu." @@ -2847,7 +2877,7 @@ msgstr "Doar în toate DM-urile deschise" msgid "Across every community you're in" msgstr "În toate comunitățile în care ești membru" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Acțiune" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Partajarea activității" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Prag de activitate" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Adaugă un nivel suplimentar de securitate pentru contul tău" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Adaugă o altă comandă rapidă personalizată" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Adresă" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Ajustează manual fiecare setare: suprimarea zgomotului, anularea ecoului și amplificarea." @@ -3696,11 +3726,11 @@ msgstr "Permite {everyoneMention} și {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Permite aplicației {PRODUCT_NAME} să îți acceseze camera în {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Permite aplicației {PRODUCT_NAME} să îți acceseze microfonul în {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Permite aplicației {PRODUCT_NAME} să îți acceseze microfonul. Verifică bara de adrese sau setările browserului." @@ -3803,7 +3833,7 @@ msgstr "Permite membrilor fără permisiunea {readMessageHistoryPermissionLabel} msgid "Allow mentions for this role" msgstr "Permite menționări pentru acest rol" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Permite accesul la microfon" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Autorizarea a eșuat" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Autorizarea a eșuat. Încearcă din nou." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Detectare automată a limbii" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Setează automat pragul de activitate" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Permisiuni bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Permisiunile botului nu pot fi negative." @@ -6869,6 +6899,11 @@ msgstr "Schimbă pseudonimul în grup" msgid "Change icon" msgstr "Schimbă pictograma" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Modifică în setările sistemului" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Acces la canal" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Acces la canal refuzat" @@ -7294,7 +7329,7 @@ msgstr "ID-ul canalului a fost copiat în clipboard" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "link canal" @@ -7462,6 +7497,11 @@ msgstr "câmp de chat" msgid "Chat shortcuts" msgstr "Comenzi rapide pentru chat" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Verifică din nou" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Alege o fereastră din selectorul sistemului tău" msgid "Choose an account" msgstr "Alege un cont" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Alege o acțiune." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Alege fundalul" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Alege fie guild_id, fie channel_id, nu ambele." @@ -7990,7 +8030,7 @@ msgstr "Curăță microfonul pentru o vorbire clară." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Dă clic pentru a deschide selectorul de culori și a alege o culoare no msgid "Click to react" msgstr "Dă clic pentru a reacționa" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Dă clic pentru a înregistra" @@ -9110,7 +9150,7 @@ msgstr "Confirmare" msgid "Confirmation or double-click for community voice joins" msgstr "Confirmare sau dublu clic pentru a intra în canalele vocale ale comunităților" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Intră în conflict cu {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Conectează o cameră sau un dispozitiv de captură virtual, apoi încea msgid "Connect a display, then try again." msgstr "Conectează un ecran, apoi încearcă din nou." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Conectează un microfon și încearcă din nou." @@ -9700,6 +9740,21 @@ msgstr "Copiază linkul de invitație" msgid "Copy invite URL" msgstr "Copiază URL-ul invitației" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Copiază liniile" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Copiază liniile pentru hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Copiază liniile pentru hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Personalizat" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Selecție personalizată" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Acțiune pentru comanda rapidă personalizată" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Opțiuni pentru comanda rapidă personalizată" @@ -11466,7 +11521,7 @@ msgstr "Comenzi rapide implicite" msgid "Default translator" msgstr "Traducător implicit" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Implicit:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Șterge media salvată" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Șterge scurtătura" @@ -12102,13 +12157,13 @@ msgstr "Stare dezvoltator" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Instrumente pentru dezvoltatori" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Instrumentele pentru dezvoltatori sunt disponibile din insigna de personal din antetul canalului." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Dezactivează beneficiile premium de backend" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Dezactivează scurtătura încorporată" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Dezactivezi ferestrele imagine în imagine pentru partajarea ecranului?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Dezactivează scurtătura" @@ -12888,7 +12943,7 @@ msgstr "Donează în schimb" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Buton de descărcare" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Descarcă aplicația pentru desktop" @@ -13204,6 +13259,11 @@ msgstr "Olandeză" msgid "e.g., YubiKey, iPhone, work computer" msgstr "de ex.: YubiKey, iPhone, computer de serviciu" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Fiecare tastă poate rula o singură comandă rapidă la nivel de sistem." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Editează linkul de invitație" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Editează scurtătura de la tastatură" @@ -13425,7 +13485,7 @@ msgstr "Formular de editare a elementului media salvat" msgid "Edit search engine" msgstr "Editează motorul de căutare" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Editează scurtătura" @@ -13753,20 +13813,16 @@ msgstr "Editare goală" msgid "Empty favorites" msgstr "Listă de favorite goală" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Activează" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Activează permisiunea {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Activează accelerarea hardware și repornește" msgid "Enable hardware acceleration?" msgstr "Activezi accelerarea hardware?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Activează accesul la intrare pentru a utiliza scurtăturile la nivel de sistem pe Wayland. După modificarea accesului la intrare, închide complet și repornește {productName} pentru ca scurtăturile să-l poată utiliza." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Activează Shift pentru a extinde" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Activează scurtătura" @@ -14357,7 +14408,7 @@ msgstr "Sunetul întregului sistem" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Sunet de intrare" @@ -14467,7 +14518,7 @@ msgstr "Seara" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Nivelează volumul microfonului, ca să nu se audă prea încet." @@ -15682,6 +15733,26 @@ msgstr "Doar pentru ochii britanicilor..." msgid "For me" msgstr "Pentru mine" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Pentru Push-to-mute, folosește o singură tastă, fără Ctrl, Alt sau Shift. Dacă folosești o combinație, eliberează mai întâi tasta principală, altfel microfonul poate rămâne dezactivat." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Pentru Push-to-talk, folosește o singură tastă, fără Ctrl, Alt sau Shift. Dacă folosești o combinație, eliberează mai întâi tasta principală, altfel microfonul poate rămâne activat." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Pentru această comandă rapidă, folosește o singură tastă, fără Ctrl, Alt sau Shift. Dacă folosești o combinație, eliberează mai întâi tasta principală, altfel comanda rapidă poate rămâne activă." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Pentru prioritatea activității vocale, folosește o singură tastă, fără Ctrl, Alt sau Shift. Dacă folosești o combinație, eliberează mai întâi tasta principală, altfel prioritatea poate rămâne activă." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Pentru siguranța ta, te rugăm să confirmi că ești tu pentru a continua." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Profil global" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Comandă rapidă globală" @@ -17434,6 +17505,11 @@ msgstr "Ține apăsată tasta Shift și apasă X pentru a selecta aplicații. Ap msgid "Hold to record. Drag up to lock, or release to send." msgstr "Ține apăsat pentru a înregistra. Trage în sus pentru a bloca sau eliberează pentru a trimite." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Dacă ții apăsat Shift, Ctrl sau Alt, această comandă rapidă nu funcționează. Adaugă alternative în setările sistemului." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Ura! Notificările sunt activate" msgid "Hyperlink" msgstr "Hiperlink" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland nu are o fereastră pentru comenzi rapide. Adaugă în configurația Hyprland liniile pentru versiunea ta de Hyprland și înlocuiește KEY cu tasta dorită." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland nu are o fereastră pentru comenzi rapide. Atribuie tastele în configurația Hyprland." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (înainte de Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 și versiuni ulterioare)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Sunt de acord cu <0>Politica de confidențialitate" @@ -17576,7 +17672,7 @@ msgstr "M-am răzgândit" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Am înțeles" @@ -18000,7 +18096,7 @@ msgstr "În" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "În modul „Sistem de operare”, dicționarele enumerate mai sus sunt consultative. {PRODUCT_NAME} va transmite aceste etichete de limbă verificatorului ortografic al sistemului tău de operare, atunci când este acceptat. Etichetele pe care nu le recunoaște sunt ignorate." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "Într-un browser, push-to-talk funcționează doar când fila {PRODUCT_NAME} este activă. Instalează aplicația desktop pentru push-to-talk la nivel de sistem." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Introducere" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Accesul la intrare nu a putut fi activat automat. Modifică-l manual, apoi închide complet și repornește {productName} pentru ca scurtăturile să-l poată utiliza." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Accesul la intrare nu a putut fi activat. Încearcă din nou din setările {settingsMenuName}, apoi închide complet și repornește {productName}." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Este necesar accesul la intrare pentru scurtăturile de la tastatură și mouse la nivel de sistem pe Wayland. După modificarea accesului la intrare, închide complet și repornește {productName} pentru ca scurtăturile să-l poată utiliza." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Accesul la intrare a fost modificat. Închide complet și repornește {productName} pentru ca scurtăturile la nivel de sistem să-l poată utiliza." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Intrare și ieșire" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Solicitare de autorizare nevalidă" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Permisiuni bot nevalide." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Cod nevalid sau expirat. Încearcă din nou." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "redirect_uri nevalid" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Linkul de invitație este ascuns în timpul partajării:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Linkul de invitație este mascat în timpul partajării" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Salt la {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Salt la {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Salt la apelul curent" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Salt la canalul asociat" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Salt la mesajul din link" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Salt la mesajul de la {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Salt la mesajul din {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Păstrează-le în siguranță. Regenerarea va întrerupe integrările existente." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Continuă transmisia pentru scurt timp după eliberare." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Tastatură" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Accesul la tastatură nu a putut porni. Comenzile rapide funcționează doar cât timp {productName} este în prim-plan." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Permite comunității să se automodereze" msgid "Let the member hear others in this community again" msgstr "A permis membrului să îi audă din nou pe ceilalți în această comunitate" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Permite aplicației {productName} să-ți citească direct tastatura. Sistemul tău permite deja acest lucru." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Permite CSS-ului personalizat să afișeze zone transparente ale ferestrei după repornirea aplicației desktop." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Stil link" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Link indisponibil" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Aspect și stil" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Se pare că folosești {0}. Suprapunerea procesării audio de la {PRODUCT_NAME} poate cauza distorsiuni sau efecte de pompare. Intrarea directă transmite sunetul netratat." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Aspect mesaje" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "link mesaj" @@ -21332,7 +21416,7 @@ msgstr "Microfon" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Test microfon" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Diverse" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Lipsește client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Mai multe opțiuni" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Mai multe opțiuni pentru comanda rapidă" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Fără permisiunea {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Fără acces" @@ -22984,7 +23068,7 @@ msgstr "Niciun rezultat găsit" msgid "No mentions yet" msgstr "Încă nu există mențiuni" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Nu s-a găsit niciun microfon" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Nu s-au găsit setări" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Nicio comandă rapidă setată" @@ -23358,6 +23442,11 @@ msgstr "Nu este un număr întreg" msgid "Not accepted" msgstr "Neadmis" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Neatribuit" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Deschide {host} în browserul tău." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Deschide {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Deschide {labelText} în setări" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "link pagină" @@ -25611,11 +25700,11 @@ msgstr "Prezență" msgid "Press {shortcut} to react" msgstr "Apasă {shortcut} pentru a reacționa" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Apasă o tastă, un buton de mouse sau un buton de gamepad pentru a asocia. Escape anulează." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Apasă o scurtătură…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Tastă push-to-talk" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Push-to-talk" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Push-to-talk (limitat)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Push-to-talk nu are încă o tastă la nivel de sistem. Pentru a seta una, accesează Setări > Aplicații > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Push-to-talk nu are încă o tastă la nivel de sistem. Pentru a seta una, accesează Setări sistem > Tastatură > Scurtături > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Push-to-talk nu are încă o tastă la nivel de sistem. Pentru a seta una, deschide setările pentru comenzile rapide de la tastatură ale mediului desktop." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Push-to-talk este ignorat în modul intrare directă. Comută pe voce focalizată sau pe profilul personalizat ca să îl poți folosi." @@ -26293,21 +26397,51 @@ msgstr "Push-to-talk este ignorat în modul intrare directă. Comută pe voce fo msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Push-to-talk este activat. Ține apăsat {pushToTalkHint} ca să vorbești." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Push-to-talk este activat. Ține apăsată tasta push-to-talk ca să vorbești." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Push-to-talk funcționează doar cât timp {productName} este în prim-plan, deoarece mediul desktop nu a configurat comenzile rapide la nivel de sistem." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Push-to-talk funcționează doar când {productName} este în prim-plan, până la activarea {permissionName}. După modificarea permisiunii, închide complet și repornește {productName} pentru ca tastele rapide la nivel de sistem să o poată utiliza." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Push-to-talk funcționează doar cât timp {productName} este în prim-plan, până când configurezi comenzile rapide la nivel de sistem." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Push-to-talk funcționează doar când fila browserului este activă." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Push-to-talk va folosi tasta setată în {productName}. Setează mai întâi una, altfel microfonul va folosi activitatea vocală." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Push-to-talk nu îți reactivează microfonul cât timp ai sunetul dezactivat. Anulează dezactivarea sunetului ca să poți vorbi." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Push-to-talk funcționează în afara aplicației {productName} doar după ce îi atribui o tastă în configurația Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Push-to-talk funcționează în afara aplicației {productName} doar după ce îi atribui o tastă în configurația Hyprland. Adaugă linia pentru versiunea ta de Hyprland și înlocuiește KEY cu tasta dorită." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "JSON brut pentru încorporare" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Reactivează scurtătura încorporată" @@ -26565,13 +26699,6 @@ msgstr "Mențiuni recente" msgid "Recent searches" msgstr "Căutări recente" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Verifică din nou" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Recomandat" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Recomandat. Îți curăță sunetul microfonului pentru o voce clară." @@ -26600,6 +26727,11 @@ msgstr "Reconectează-te" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Reconectare reușită. Alege din nou un ecran dacă vrei să reiei transmisiunea anterioară." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Se reconectează la mediul desktop…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Se reconectează…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Înregistrează scurtătură" @@ -26851,7 +26983,7 @@ msgstr "Relativ" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Întârziere la eliberare" @@ -27739,13 +27871,13 @@ msgstr "Retrimis" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Resetare" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Resetează „{label}” la scurtătura încorporată" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Resetează la valorile reale" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Resetare la scurtătura încorporată" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Selectează contul cu care vrei să te conectezi pe noul tău dispozitiv." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Selectează modul de procesare a vocii" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Trimite sunetul brut al microfonului, fără procesare." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Trimite sunetul tău nemodificat. Cel mai bine dacă folosești software audio extern." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Setat de presetarea {preset}. Alegerea unei valori aici comută la Personalizat." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Setează o comandă rapidă personalizată" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Setează o comandă rapidă personalizată pentru „{label}”" @@ -30066,6 +30198,11 @@ msgstr "Regiunea vocală a fost setată la {region}" msgid "Set the voice region to automatic" msgstr "A setat regiunea vocală la automat" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Configurează" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Setări" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "link setări" @@ -30330,8 +30467,8 @@ msgstr "Coduri scurte" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Treci la aspectul confortabil" msgid "Switch to dense layout" msgstr "Treci la aspectul compact" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Treci la intrarea directă" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Instalare gestionată de sistem" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Comenzi rapide la nivel de sistem" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Nu s-au putut configura comenzile rapide la nivel de sistem." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "La nivel de sistem: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "La nivel de sistem: neatribuit" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Pagina pe care o cauți nu există sau a fost mutată." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Adresa redirect_uri furnizată nu este înregistrată pentru această aplicație." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Adresa redirect_uri furnizată trebuie să utilizeze HTTP sau HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Animația este prea lungă sau prea mare pentru a fi decupată. Încearcă un fișier mai scurt sau mai mic." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Această aplicație" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Acest atașament a expirat" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Acest bot nu este public și nu poate fi adăugat de alți utilizatori." @@ -34081,6 +34233,16 @@ msgstr "Această conversație nu mai este disponibilă." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Această versiune pentru desktop nu poate instala actualizări în cadrul aplicației {productName}. Descarcă cea mai recentă versiune a programului de instalare pentru desktop." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Acest mediu desktop nu oferă comenzi rapide la nivel de sistem. Push-to-talk funcționează doar cât timp {productName} este în prim-plan." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Acest mediu desktop nu oferă comenzi rapide la nivel de sistem. Comenzile rapide funcționează în continuare cât timp {productName} este în prim-plan." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Aceasta este ultima ta cheie de acces, iar cheile de acces sunt al doile msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Aceasta este ultima ta cheie de acces. Odată ce o ștergi, nu vei mai putea folosi o cheie de acces ca al doilea factor." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Această tastă funcționează cât timp {productName} este în prim-plan. Tasta la nivel de sistem este setată de mediul desktop, în {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Acest canal de linkuri este marcat ca având conținut matur și poate deschide materiale care ar putea fi nepotrivite pentru unii utilizatori." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Acest link nu este disponibil pentru contul tău." @@ -34778,6 +34945,21 @@ msgstr "Bara de titlu" msgid "Title bar platform" msgstr "Platformă bară de titlu" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Pentru a schimba tastele, accesează Setări > Aplicații > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Pentru a schimba tastele, accesează Setări sistem > Tastatură > Scurtături > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Pentru a schimba tastele, deschide setările pentru comenzile rapide de la tastatură ale mediului desktop." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Pentru a crea un webhook, deschide <0>{channelWebhooksSettingsPath}. Poți edita și organiza în continuare toate webhook-urile existente aici." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Necunoscut" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Aplicație necunoscută, sesiune expirată sau solicitare de autorizare nevalidă." @@ -36410,6 +36592,11 @@ msgstr "Până la 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "Până la 720p la 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Actualizează {productName} ca să folosești comenzi rapide la nivel de sistem pe Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Folosește tema întunecată în toată aplicația" msgid "Use default" msgstr "Folosește valoarea implicită" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Folosește accesul direct la dispozitivele de intrare" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Participanți la apelul vocal" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Procesarea vocii" @@ -38264,7 +38456,7 @@ msgstr "Activare sunet" msgid "Voice unmute" msgstr "Activare microfon" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "În acest browser, apelurile vocale folosesc dispozitivul de ieșire al sistemului. Schimbă-l din setările sistemului." @@ -38278,7 +38470,7 @@ msgstr "Anulată" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Se așteaptă expirarea modului lent în unul sau mai multe canale selec msgid "Waiting for you to..." msgstr "Te așteaptă să..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Se așteaptă mediul desktop…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Ce conversații" msgid "Which rule was broken?" msgstr "Ce regulă a fost încălcată?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Cât timp push-to-talk este activat, microfonul este controlat de scurtătură, așa că butonul de dezactivare a microfonului nu este disponibil." @@ -39094,6 +39291,11 @@ msgstr "Fără această permisiune, este necesar push-to-talk." msgid "Wordmark" msgstr "Logotip" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Funcționează cât timp {productName} este în prim-plan" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Ți-ai schimbat tagul cât timp aveai {PREMIUM_PRODUCT_NAME}. Acesta se va reinițializa aleatoriu când {PREMIUM_PRODUCT_NAME} expiră, dacă nu se reînnoiește." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Nu ai acces la canalul unde a fost trimis acest mesaj." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Abonamentul tău {premiumProductName} a expirat. Ai pierdut toate beneficiile {premiumProductName}. Reactivează-ți abonamentul pentru a recăpăta accesul." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Sesiunea ta {productName} a expirat. Conectează-te din nou pentru a continua autorizarea acestei aplicații." @@ -40144,6 +40346,16 @@ msgstr "Comunitatea ta are nevoie de cel puțin {minMemberCount, plural, one {# msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Cardul tău de credit a fost verificat. Dacă este valid, conținutul pentru adulți se va debloca în scurt timp. Poți închide această filă și reveni la aplicație." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Mediul desktop nu a configurat comenzile rapide la nivel de sistem." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Mediul desktop rulează aceste comenzi rapide chiar și când {productName} nu este în prim-plan." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/ru/messages.po b/fluxer_app/src/features/i18n/locales/ru/messages.po index 1cfa53d93..155cd82bc 100644 --- a/fluxer_app/src/features/i18n/locales/ru/messages.po +++ b/fluxer_app/src/features/i18n/locales/ru/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# канал подписан} few {# канала msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {Найдено # сообщество} few {Найдено # сообщества} many {Найдено # сообществ} other {Найдено # сообщества}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# сообщество временно недоступно из-за неисправности потокового накопителя.} few {# сообщества временно недоступны из-за неисправности потокового накопителя.} many {# сообществ временно недоступны из-за неисправности потокового накопителя.} other {# сообществ временно недоступны из-за неисправности потокового накопителя.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} не отправляет push-уведомления на msgid "{productName} Bug Hunter" msgstr "Охотник за ошибками {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} может попросить систему выполнять его сочетания клавиш, даже когда {productName} не в фокусе. Система попросит подтвердить это и позволит выбрать клавиши." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} больше не может считывать клавиатуру, поэтому отслеживает сочетания клавиш через X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} больше не может считывать клавиатуру, поэтому используются системные сочетания клавиш." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} больше не может считывать клавиатуру. Сочетания клавиш работают, только когда {productName} в фокусе." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} может зарегистрировать свои сочетания клавиш в Hyprland, чтобы они работали, даже когда {productName} не в фокусе. Затем назначьте клавиши в конфигурации Hyprland." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} требуется доступ к камере, чтобы показать список ваших устройств." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} требуется доступ к микрофону, чтобы показать список ваших устройств." @@ -1837,6 +1862,11 @@ msgstr "{productName} теперь находится по адресу {host}. msgid "{productName} Partner" msgstr "Партнёр {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} зарегистрировал свои сочетания клавиш в Hyprland. Назначьте для них клавиши в конфигурации Hyprland, чтобы использовать их, когда {productName} не в фокусе." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Профиль, который выделяется" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Параметр redirect_uri обязателен, если область действия bot не единственная." @@ -2847,7 +2877,7 @@ msgstr "Только во всех открытых личных чатах" msgid "Across every community you're in" msgstr "Во всех сообществах, в которых вы состоите" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Действие" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Показ активности" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Порог активности" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Добавьте дополнительный уровень защиты аккаунта" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Добавить ещё одно сочетание клавиш" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Адрес" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Настройте каждый параметр самостоятельно: шумоподавление, эхоподавление и усиление." @@ -3696,11 +3726,11 @@ msgstr "Разрешить {everyoneMention} и {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Разрешите {PRODUCT_NAME} доступ к вашей камере в {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Разрешите {PRODUCT_NAME} доступ к микрофону в {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Разрешите {PRODUCT_NAME} доступ к микрофону. Проверьте адресную строку или настройки браузера." @@ -3803,7 +3833,7 @@ msgstr "Разрешить участникам без разрешения «{r msgid "Allow mentions for this role" msgstr "Разрешить упоминания для этой роли" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Разрешить доступ к микрофону" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Не удалось авторизоваться" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Не удалось авторизоваться. Повторите попытку." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Автоопределение языка" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Автоматически устанавливать порог активности" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Разрешения бота" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Разрешения бота не могут быть отрицательными." @@ -6869,6 +6899,11 @@ msgstr "Изменить никнейм в группе" msgid "Change icon" msgstr "Изменить значок" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Изменить в системных настройках" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Доступ к каналам" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Доступ к каналу запрещён" @@ -7294,7 +7329,7 @@ msgstr "ID канала скопирован в буфер обмена" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "ссылка на канал" @@ -7462,6 +7497,11 @@ msgstr "поле ввода" msgid "Chat shortcuts" msgstr "Быстрые клавиши в чате" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Проверить снова" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Выберите окно в системном диалоге" msgid "Choose an account" msgstr "Выберите аккаунт" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Выберите действие." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Выбрать фон" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Выберите либо guild_id, либо channel_id, но не оба сразу." @@ -7990,7 +8030,7 @@ msgstr "Очищает звук микрофона для четкой речи. #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Нажмите, чтобы открыть палитру и выбрат msgid "Click to react" msgstr "Нажмите, чтобы отреагировать" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Нажмите, чтобы записать" @@ -9110,7 +9150,7 @@ msgstr "Подтверждение" msgid "Confirmation or double-click for community voice joins" msgstr "Подтверждение или двойной щелчок для входа в голосовой канал сообщества" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Конфликтует с {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Подключите камеру или виртуальное устр msgid "Connect a display, then try again." msgstr "Подключите дисплей и попробуйте снова." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Подключите микрофон и попробуйте снова." @@ -9700,6 +9740,21 @@ msgstr "Скопировать ссылку-приглашение" msgid "Copy invite URL" msgstr "Скопировать ссылку-приглашение" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Скопировать строки" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Скопировать строки для hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Скопировать строки для hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Другой источник" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Выборочно" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Действие для пользовательского сочетания клавиш" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Настройки сочетаний клавиш" @@ -11466,7 +11521,7 @@ msgstr "Быстрые клавиши по умолчанию" msgid "Default translator" msgstr "Переводчик по умолчанию" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "По умолчанию:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Удалить сохраненные медиа" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Удалить сочетание клавиш" @@ -12102,13 +12157,13 @@ msgstr "Состояние разработчика" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Инструменты разработчика" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Инструменты разработчика доступны по значку сотрудника в заголовке канала." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Отключить привилегии Premium на сервере" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Отключить встроенное сочетание клавиш" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Отключить окна «картинка в картинке» для демонстрации экрана?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Отключить сочетание клавиш" @@ -12888,7 +12943,7 @@ msgstr "Пожертвовать вместо этого" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Кнопка «Скачать»" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Скачать приложение для компьютера" @@ -13204,6 +13259,11 @@ msgstr "Нидерландский" msgid "e.g., YubiKey, iPhone, work computer" msgstr "напр., YubiKey, iPhone, рабочий компьютер" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Одна клавиша может запускать только одно системное сочетание клавиш." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Изменить ссылку-приглашение" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Изменить сочетание клавиш" @@ -13425,7 +13485,7 @@ msgstr "Форма изменения сохраненного медиафай msgid "Edit search engine" msgstr "Изменить поисковую систему" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Изменить сочетание клавиш" @@ -13753,20 +13813,16 @@ msgstr "Пустое сообщение" msgid "Empty favorites" msgstr "Избранное пусто" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Включить" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Включить разрешение «{permissionName}»" @@ -13876,11 +13932,6 @@ msgstr "Включить аппаратное ускорение и переза msgid "Enable hardware acceleration?" msgstr "Включить аппаратное ускорение?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Включите доступ к вводу, чтобы использовать системные сочетания клавиш в Wayland. После изменения доступа к вводу полностью закройте и перезапустите {productName}, чтобы сочетания клавиш могли его использовать." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Включить Shift для разворачивания" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Включить сочетание клавиш" @@ -14357,7 +14408,7 @@ msgstr "Весь звук системы" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Звук входа" @@ -14467,7 +14518,7 @@ msgstr "Вечер" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Выравнивает громкость вашего микрофона, чтобы вас было хорошо слышно." @@ -15682,6 +15733,26 @@ msgstr "Только для британцев..." msgid "For me" msgstr "Для меня" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Для режима «Нажми и молчи» используйте одну клавишу без Ctrl, Alt или Shift. Если используете сочетание, сначала отпускайте основную клавишу, иначе микрофон может остаться выключенным." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Для режима «Нажми и говори» используйте одну клавишу без Ctrl, Alt или Shift. Если используете сочетание, сначала отпускайте основную клавишу, иначе микрофон может остаться включенным." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Для этого сочетания используйте одну клавишу без Ctrl, Alt или Shift. Если используете сочетание, сначала отпускайте основную клавишу, иначе оно может остаться активным." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Для сочетания «Приоритет голосовой активности» используйте одну клавишу без Ctrl, Alt или Shift. Если используете сочетание, сначала отпускайте основную клавишу, иначе приоритет может остаться включенным." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Для вашей безопасности подтвердите, что это вы, чтобы продолжить." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Глобальный профиль" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Глобальное сочетание клавиш" @@ -17434,6 +17505,11 @@ msgstr "Удерживайте Shift и нажмите X, чтобы выбра msgid "Hold to record. Drag up to lock, or release to send." msgstr "Нажмите и удерживайте для записи. Проведите вверх, чтобы закрепить запись, или отпустите, чтобы отправить." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Пока зажаты Shift, Ctrl или Alt, это сочетание не срабатывает. Добавьте альтернативные сочетания в настройках системы." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Ура! Уведомления включены" msgid "Hyperlink" msgstr "Гиперссылка" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "В Hyprland нет окна настройки сочетаний клавиш. Добавьте строки для вашей версии Hyprland в конфигурацию Hyprland и замените KEY на нужную клавишу." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "В Hyprland нет окна настройки сочетаний клавиш. Назначьте клавиши в конфигурации Hyprland." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (до Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 и новее)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Принимаю <0>Политику конфиденциальности" @@ -17576,7 +17672,7 @@ msgstr "Я передумал(а)" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Понятно" @@ -18000,7 +18096,7 @@ msgstr "В канале" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "В режиме «Операционная система» перечисленные выше словари являются рекомендательными. {PRODUCT_NAME} будет передавать эти языковые теги системной проверке орфографии, если она это поддерживает. Теги, которые она не распознаёт, игнорируются." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "В браузере режим «Нажми и говори» работает только при активной вкладке {PRODUCT_NAME}. Установите приложение для ПК, чтобы использовать его во всей системе." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Ввод" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Не удалось включить доступ к вводу автоматически. Измените его вручную, затем полностью закройте и перезапустите {productName}, чтобы сочетания клавиш могли его использовать." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Не удалось включить доступ к вводу. Повторите попытку в настройках «{settingsMenuName}», затем полностью закройте и перезапустите {productName}." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Для системных сочетаний клавиш и мыши в Wayland требуется доступ к вводу. После изменения доступа к вводу полностью закройте и перезапустите {productName}, чтобы сочетания клавиш могли его использовать." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Доступ к вводу изменен. Полностью закройте и перезапустите {productName}, чтобы системные сочетания клавиш могли его использовать." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Ввод и вывод" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Недействительный запрос авторизации" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Недействительные разрешения для бота." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Неверный или истёкший код. Попробуйте ещё раз." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Недействительный redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Ссылка-приглашение скрыта во время демонстрации экрана:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Ссылка-приглашение скрыта во время демонстрации экрана" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Перейти к {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Перейти к «{labelText}»" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Перейти к текущему звонку" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Перейти к каналу по ссылке" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Перейти к сообщению по ссылке" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Перейти к сообщению от {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Перейти к сообщению в {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Храните их в безопасности. Повторная генерация нарушит существующие интеграции." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Продолжает передавать звук ещё немного после отпускания клавиши." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Клавиатура" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Не удалось получить доступ к клавиатуре. Сочетания клавиш работают, только когда {productName} в фокусе." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Сообщество модерирует себя само" msgid "Let the member hear others in this community again" msgstr "Участнику снова разрешено слышать других в этом сообществе" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Позволяет {productName} считывать клавиатуру напрямую. Ваша система уже это разрешает." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Позволяет пользовательскому CSS отображать прозрачные области окна после перезапуска приложения на компьютере." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Стили ссылок" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Ссылка недоступна" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Внешний вид" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Похоже, вы используете {0}. Дополнительная обработка звука в {PRODUCT_NAME} может привести к искажениям. Прямой ввод передает ваш звук без изменений." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Вид сообщений" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "ссылка на сообщение" @@ -21332,7 +21416,7 @@ msgstr "Микрофон" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Тест микрофона" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Разное" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Отсутствует client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Другие действия" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Другие настройки сочетаний клавиш" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Нет разрешения {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Нет доступа" @@ -22984,7 +23068,7 @@ msgstr "Совпадений не найдено" msgid "No mentions yet" msgstr "Пока нет упоминаний" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Микрофон не найден" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Настройки не найдены" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Сочетание клавиш не задано" @@ -23358,6 +23442,11 @@ msgstr "Не целое число" msgid "Not accepted" msgstr "Не принято" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Не назначено" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Откройте {host} в браузере." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Открыть {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Открыть {labelText} в настройках" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "ссылка на страницу" @@ -25611,11 +25700,11 @@ msgstr "Статус в сети" msgid "Press {shortcut} to react" msgstr "Нажмите {shortcut}, чтобы отреагировать" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Нажмите клавишу, кнопку мыши или геймпада, чтобы привязать. Escape отменяет." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Нажмите сочетание клавиш…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Клавиша «Нажми и говори»" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Нажми и говори" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Нажми и говори (с ограничениями)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Для режима «Нажми и говори» еще не назначена системная клавиша. Чтобы назначить ее, откройте «Параметры» > «Приложения» > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Для режима «Нажми и говори» еще не назначена системная клавиша. Чтобы назначить ее, откройте «Параметры системы» > «Клавиатура» > «Комбинации клавиш» > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Для режима «Нажми и говори» еще не назначена системная клавиша. Чтобы назначить ее, откройте настройки сочетаний клавиш вашей системы." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Режим «Нажми и говори» игнорируется при прямом вводе. Переключитесь на «Фокусировку голоса» или «Пользовательский», чтобы использовать его." @@ -26293,21 +26397,51 @@ msgstr "Режим «Нажми и говори» игнорируется пр msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Включен режим «Нажми и говори». Удерживайте {pushToTalkHint}, чтобы говорить." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Включен режим «Нажми и говори». Удерживайте клавишу «Нажми и говори», чтобы говорить." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Режим «Нажми и говори» работает, только когда {productName} в фокусе, так как ваша система не настроила системные сочетания клавиш." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Функция «Нажми и говори» работает, только когда приложение {productName} активно, пока не будет включено разрешение {permissionName}. После изменения разрешения полностью закройте и перезапустите {productName}, чтобы системные горячие клавиши могли его использовать." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Пока вы не настроите системные сочетания клавиш, режим «Нажми и говори» работает, только когда {productName} в фокусе." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Режим «Нажми и говори» работает только при активной вкладке браузера." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Режим «Нажми и говори» будет использовать клавишу, назначенную в {productName}. Сначала назначьте ее, иначе микрофон будет работать по голосовой активности." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "«Нажми и говори» не включит микрофон, пока у вас отключён звук. Включите звук, чтобы говорить." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Режим «Нажми и говори» работает вне {productName} только после того, как вы назначите для него клавишу в конфигурации Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Режим «Нажми и говори» работает вне {productName} только после того, как вы назначите для него клавишу в конфигурации Hyprland. Добавьте строку для вашей версии Hyprland и замените KEY на нужную клавишу." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Исходный JSON встраиваемого элемента" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Включить встроенное сочетание клавиш" @@ -26565,13 +26699,6 @@ msgstr "Недавние упоминания" msgid "Recent searches" msgstr "Недавние запросы" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Проверить снова" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Рекомендуется" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Рекомендуется. Очищает звук микрофона для чёткой речи." @@ -26600,6 +26727,11 @@ msgstr "Переподключиться" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Подключение восстановлено. Выберите экран ещё раз, если хотите возобновить предыдущую трансляцию." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Переподключение к системе…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Переподключение…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Записать сочетание клавиш" @@ -26851,7 +26983,7 @@ msgstr "Относительный" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Задержка отпускания" @@ -27739,13 +27871,13 @@ msgstr "Отправлено повторно" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Сбросить" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Сбросить «{label}» до стандартного сочетания" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Сбросить до фактических значений" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Сбросить до стандартных" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Выберите аккаунт, с которым хотите войти на новом устройстве." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Выберите режим обработки голоса" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Отправляет звук с микрофона без какой-либо обработки." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Отправляет ваш звук без изменений. Лучший вариант, если вы используете внешние аудиопрограммы." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Установлено пресетом {preset}. Выбор значения здесь переключит на «Другое»." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Задать сочетание клавиш" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Задать сочетание клавиш для «{label}»" @@ -30066,6 +30198,11 @@ msgstr "Установлен голосовой регион: {region}" msgid "Set the voice region to automatic" msgstr "Установлен автоматический выбор голосового региона" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Настроить" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Настройки" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "ссылка на настройки" @@ -30330,8 +30467,8 @@ msgstr "Шорткоды" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Переключиться на просторный вид" msgid "Switch to dense layout" msgstr "Переключиться на компактный вид" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Переключиться на прямой ввод" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Установка через системный менеджер" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Системные сочетания клавиш" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Системные сочетания клавиш не удалось настроить." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "В системе: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "В системе: не назначено" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Страница, которую вы ищете, не существует или была перемещена." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Указанный redirect_uri не зарегистрирован для этого приложения." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Указанный redirect_uri должен использовать HTTP или HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Этот анимированный файл слишком длинный или большой для обрезки. Попробуйте более короткий или меньший файл." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Это приложение" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Срок действия этого вложения истёк" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Этот бот не является публичным, и другие пользователи не могут его добавить." @@ -34081,6 +34233,16 @@ msgstr "Эта переписка больше недоступна." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Эта сборка для ПК не может устанавливать обновления внутри {productName}. Вместо этого скачайте последний установщик для ПК." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Это окружение рабочего стола не поддерживает системные сочетания клавиш. Режим «Нажми и говори» работает, только когда {productName} в фокусе." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Это окружение рабочего стола не поддерживает системные сочетания клавиш. Сочетания клавиш по-прежнему работают, когда {productName} в фокусе." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Это ваш последний ключ доступа, а ключи msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Это ваш последний ключ доступа. После его удаления вы не сможете использовать ключ доступа в качестве второго фактора." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Эта клавиша работает, пока {productName} в фокусе. Системную клавишу назначает ваша система, см. «{settingsTabName}» > «{sectionName}»." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Этот канал-ссылка помечен как содержащий контент для взрослых и может открывать материалы, которые могут быть неприемлемы для некоторых пользователей." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Эта ссылка недоступна для вашего аккаунта." @@ -34778,6 +34945,21 @@ msgstr "Строка заголовка" msgid "Title bar platform" msgstr "Платформа строки заголовка" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Чтобы изменить клавиши, откройте «Параметры» > «Приложения» > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Чтобы изменить клавиши, откройте «Параметры системы» > «Клавиатура» > «Комбинации клавиш» > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Чтобы изменить клавиши, откройте настройки сочетаний клавиш вашей системы." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Чтобы создать вебхук, откройте <0>{channelWebhooksSettingsPath}. Здесь по-прежнему можно редактировать и упорядочивать все существующие вебхуки." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Неизвестно" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Неизвестное приложение, просроченная сессия или недействительный запрос авторизации." @@ -36410,6 +36592,11 @@ msgstr "До 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "До 720p при 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Обновите {productName}, чтобы использовать системные сочетания клавиш в Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Использовать темную тему во всем прило msgid "Use default" msgstr "Использовать по умолчанию" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Использовать прямой доступ к устройству ввода" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Участники голосового чата" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Обработка голоса" @@ -38264,7 +38456,7 @@ msgstr "Включение звука" msgid "Voice unmute" msgstr "Включение микрофона" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "В этом браузере для голосовых вызовов используется системное устройство вывода. Измените его в настройках системы." @@ -38278,7 +38470,7 @@ msgstr "Аннулировано" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Ожидание окончания медленного режима в msgid "Waiting for you to..." msgstr "Ждём, пока вы..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Ожидание ответа системы…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Какие чаты" msgid "Which rule was broken?" msgstr "Какое правило было нарушено?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Когда включён режим «Нажми и говори», микрофоном управляет сочетание клавиш, поэтому кнопка отключения микрофона неактивна." @@ -39094,6 +39291,11 @@ msgstr "Без этого разрешения будет работать то msgid "Wordmark" msgstr "Текстовый логотип" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Работает, пока {productName} в фокусе" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Вы изменили свой тег, пока у вас была подписка {PREMIUM_PRODUCT_NAME}. Он будет случайно изменен, когда срок действия подписки {PREMIUM_PRODUCT_NAME} истечет, если она не будет продлена." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "У вас нет доступа к каналу, в котором было отправлено это сообщение." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Срок действия вашей подписки {premiumProductName} истёк. Вы потеряли все привилегии {premiumProductName}. Чтобы восстановить доступ, продлите подписку." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Срок действия вашей сессии {productName} истёк. Войдите снова, чтобы продолжить авторизацию этого приложения." @@ -40144,6 +40346,16 @@ msgstr "Чтобы разместить сообщество в каталоге msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Ваша кредитная карта проверена. Если карта действительна, доступ к контенту для взрослых будет открыт в ближайшее время. Вы можете закрыть эту вкладку и вернуться в приложение." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Системные сочетания клавиш не были настроены." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Ваша система выполняет эти сочетания клавиш, даже когда {productName} не в фокусе." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/sv-SE/messages.po b/fluxer_app/src/features/i18n/locales/sv-SE/messages.po index d18c591da..4c515cc49 100644 --- a/fluxer_app/src/features/i18n/locales/sv-SE/messages.po +++ b/fluxer_app/src/features/i18n/locales/sv-SE/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# kanal följer} other {# kanaler följer}} den hä msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# community hittades} other {# communities hittades}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# community är tillfälligt otillgänglig på grund av ett fel i flödeskondensatorn.} other {# communities är tillfälligt otillgängliga på grund av ett fel i flödeskondensatorn.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} undviker att skicka pushaviseringar till dina mobila enhet msgid "{productName} Bug Hunter" msgstr "{productName}-buggjägare" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} kan be din skrivbordsmiljö att köra appens genvägar även när {productName} inte är i fokus. Skrivbordsmiljön ber dig bekräfta och låter dig välja tangenterna." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} kan inte längre läsa ditt tangentbord, så appen lyssnar i stället efter genvägar via X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} kan inte längre läsa ditt tangentbord, så skrivbordsmiljöns genvägar används i stället." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} kan inte längre läsa ditt tangentbord. Genvägar fungerar bara när {productName} är i fokus." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} kan registrera sina genvägar i Hyprland så att de körs även när {productName} inte är i fokus. Du binder sedan tangenterna i din Hyprland-konfiguration." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} behöver åtkomst till kameran för att visa dina enheter." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} behöver mikrofonåtkomst för att visa dina enheter." @@ -1837,6 +1862,11 @@ msgstr "{productName} finns nu på {host}. Installera det därifrån." msgid "{productName} Partner" msgstr "{productName}-partner" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} har registrerat sina genvägar i Hyprland. Bind dem i din Hyprland-konfiguration för att använda dem när {productName} inte är i fokus." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "En profil som sticker ut" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "redirect_uri krävs när bot inte är det enda begärda behörighetsomfånget." @@ -2847,7 +2877,7 @@ msgstr "Endast i alla öppna DM-konversationer" msgid "Across every community you're in" msgstr "I alla communities du är med i" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Åtgärd" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Delning av aktivitet" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Aktivitetströskel" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Lägg till ett extra säkerhetslager till ditt konto" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Lägg till en egen genväg" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Adress" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Justera varje inställning själv: brusreducering, ekoreducering och förstärkning." @@ -3696,11 +3726,11 @@ msgstr "Tillåt {everyoneMention} och {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Tillåt {PRODUCT_NAME} att komma åt din kamera i {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Tillåt att {PRODUCT_NAME} får åtkomst till din mikrofon i {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Tillåt {PRODUCT_NAME} att komma åt din mikrofon. Kontrollera webbläsarens adressfält eller inställningar." @@ -3803,7 +3833,7 @@ msgstr "Tillåt medlemmar utan behörigheten {readMessageHistoryPermissionLabel} msgid "Allow mentions for this role" msgstr "Tillåt omnämnanden för den här rollen" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Tillåt mikrofon" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Auktorisering misslyckades" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Auktoriseringen misslyckades. Försök igen." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Identifiera språk automatiskt" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Ställ in aktivitetströskel automatiskt" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Botbehörigheter" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Botbehörigheter kan inte vara negativa." @@ -6869,6 +6899,11 @@ msgstr "Ändra gruppsmeknamn" msgid "Change icon" msgstr "Ändra ikon" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Ändra i systeminställningar" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Kanalåtkomst" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Åtkomst till kanalen nekad" @@ -7294,7 +7329,7 @@ msgstr "Kanal-ID kopierat till urklipp" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "kanallänk" @@ -7462,6 +7497,11 @@ msgstr "chattfält" msgid "Chat shortcuts" msgstr "Chattgenvägar" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Kontrollera igen" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Välj ett fönster i systemväljaren" msgid "Choose an account" msgstr "Välj ett konto" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Välj en åtgärd." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Välj bakgrund" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Välj antingen guild_id eller channel_id, inte båda." @@ -7990,7 +8030,7 @@ msgstr "Rensar mikrofonsignalen för tydligt tal." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Klicka för att öppna färgväljaren och välja en ny färg." msgid "Click to react" msgstr "Klicka för att reagera" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Klicka för att spela in" @@ -9110,7 +9150,7 @@ msgstr "Bekräftelse" msgid "Confirmation or double-click for community voice joins" msgstr "Bekräftelse eller dubbelklick för att ansluta till röstkanaler i communities" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Konflikter med {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Anslut en kamera eller virtuell inspelningsenhet och försök igen." msgid "Connect a display, then try again." msgstr "Anslut en skärm och försök igen." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Anslut en mikrofon och försök igen." @@ -9700,6 +9740,21 @@ msgstr "Kopiera inbjudningslänk" msgid "Copy invite URL" msgstr "Kopiera inbjudningslänk" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Kopiera rader" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Kopiera rader för hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Kopiera rader för hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Anpassad källa" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Anpassat urval" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Anpassad genvägsåtgärd" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Anpassade genvägsalternativ" @@ -11466,7 +11521,7 @@ msgstr "Standardgenvägar" msgid "Default translator" msgstr "Standardöversättare" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Standard:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Ta bort sparat medieobjekt" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Ta bort genväg" @@ -12102,13 +12157,13 @@ msgstr "Utvecklarstatus" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Utvecklarverktyg" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Utvecklarverktyg finns via personalmärket i kanalhuvudet." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Inaktivera premiumförmåner på servern" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Inaktivera inbyggd genväg" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Inaktivera bild-i-bild-fönster för skärmdelningar?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Inaktivera genväg" @@ -12888,7 +12943,7 @@ msgstr "Donera istället" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Nedladdningsknapp" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Ladda ner datorappen" @@ -13204,6 +13259,11 @@ msgstr "Nederländska" msgid "e.g., YubiKey, iPhone, work computer" msgstr "t.ex. YubiKey, iPhone, jobbdator" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Varje tangent kan bara köra en systemövergripande genväg." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Redigera inbjudningslänk" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Redigera kortkommando" @@ -13425,7 +13485,7 @@ msgstr "Formulär för att redigera sparat medieobjekt" msgid "Edit search engine" msgstr "Redigera sökmotor" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Redigera genväg" @@ -13753,20 +13813,16 @@ msgstr "Tom redigering" msgid "Empty favorites" msgstr "Tomma favoriter" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Aktivera" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Aktivera behörigheten {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Aktivera maskinvaruacceleration och starta om" msgid "Enable hardware acceleration?" msgstr "Aktivera maskinvaruacceleration?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Aktivera inmatningsåtkomst för att använda kortkommandon i hela systemet på Wayland. Avsluta {productName} helt och starta om appen efter ändringen så att kortkommandona får tillgång till inmatningen." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Aktivera Skift för att expandera" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Aktivera genväg" @@ -14357,7 +14408,7 @@ msgstr "Hela systemljudet" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Anslutningsljud" @@ -14467,7 +14518,7 @@ msgstr "Kvällstid" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Jämnar ut din mikrofonvolym så att du inte är för tyst." @@ -15682,6 +15733,26 @@ msgstr "Bara för brittiska ögon..." msgid "For me" msgstr "För mig" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Använd en enskild tangent utan Ctrl, Alt eller Shift för ”Tryck för att tysta mikrofonen”. Släpp huvudtangenten först om du använder en kombination, annars kan mikrofonen förbli avstängd." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Använd en enskild tangent utan Ctrl, Alt eller Shift för ”Tryck för att prata”. Släpp huvudtangenten först om du använder en kombination, annars kan mikrofonen förbli på." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Använd en enskild tangent utan Ctrl, Alt eller Shift för den här genvägen. Släpp huvudtangenten först om du använder en kombination, annars kan genvägen förbli aktiv." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Använd en enskild tangent utan Ctrl, Alt eller Shift för ”Prioritet för röstaktivitet”. Släpp huvudtangenten först om du använder en kombination, annars kan prioriteten förbli på." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "För din säkerhet, bekräfta att det är du för att fortsätta." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Global profil" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Globalt kortkommando" @@ -17434,6 +17505,11 @@ msgstr "Håll ned Skift och tryck på X för att välja appar. Tryck på {select msgid "Hold to record. Drag up to lock, or release to send." msgstr "Håll in för att spela in. Dra uppåt för att låsa, eller släpp för att skicka." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Om du håller ner Shift, Ctrl eller Alt blockeras den här genvägen. Lägg till alternativ i dina systeminställningar." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Härligt! Aviseringar är aktiverade" msgid "Hyperlink" msgstr "Hyperlänk" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland har ingen genvägsdialog. Lägg till raderna för din Hyprland-version i din Hyprland-konfiguration och ersätt KEY med den tangent du vill använda." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland har ingen dialogruta för genvägar. Bind tangenterna i din Hyprland-konfiguration." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (före Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 och senare)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Jag godkänner <0>integritetspolicyn" @@ -17576,7 +17672,7 @@ msgstr "Jag ångrade mig" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Jag förstår" @@ -18000,7 +18096,7 @@ msgstr "I" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "I läget \"Operativsystem\" är ordlistorna ovan vägledande. {PRODUCT_NAME} skickar dessa språktaggar till din OS-stavningskontroll om det stöds. Taggar som inte känns igen ignoreras." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "I en webbläsare fungerar tryck för att prata bara när fliken {PRODUCT_NAME} har fokus. Installera datorappen för att använda tryck för att prata i hela systemet." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Inmatning" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Inmatningsåtkomst kunde inte aktiveras automatiskt. Ändra den manuellt, avsluta {productName} helt och starta sedan om appen så att kortkommandona får tillgång till inmatningen." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Inmatningsåtkomst kunde inte aktiveras. Försök igen i inställningarna för {settingsMenuName}, avsluta {productName} helt och starta sedan om appen." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Inmatningsåtkomst krävs för tangentbords- och musgenvägar i hela systemet på Wayland. Avsluta {productName} helt och starta om appen efter ändringen så att genvägarna får tillgång till inmatningen." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Inmatningsåtkomsten har ändrats. Avsluta {productName} helt och starta om appen så att globala kortkommandon får tillgång till inmatningen." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "In- och utdata" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Ogiltig auktoriseringsförfrågan" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Ogiltiga bot-behörigheter." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Ogiltig eller utgången kod. Försök igen." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Ogiltig redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Inbjudningslänken är dold under delning:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Inbjudningslänk maskerad under delning" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Hoppa till {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Gå till {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Gå till det aktuella samtalet" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Gå till den länkade kanalen" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Gå till det länkade meddelandet" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Gå till meddelandet från {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Hoppa till meddelandet i {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Förvara dem säkert. Om du skapar nya slutar befintliga integrationer att fungera." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Fortsätt sända en kort stund efter att knappen släppts." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Tangentbord" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Tangentbordsåtkomsten kunde inte startas. Genvägar fungerar bara när {productName} är i fokus." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Låt communityn självmoderera" msgid "Let the member hear others in this community again" msgstr "Lät medlemmen höra andra i den här communityn igen" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Låter {productName} läsa ditt tangentbord direkt. Ditt system tillåter redan detta." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Låter anpassad CSS visa transparenta fönsterregioner efter omstart av datorappen." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Länkstil" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Länk otillgänglig" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Utseende och känsla" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Det verkar som att du använder {0}. Om du lägger till ljudbehandlingen i {PRODUCT_NAME} ovanpå den kan det orsaka pumpning eller distorsion. Direktinmatning skickar ditt ljud oförändrat." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Meddelandelayout" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "meddelandelänk" @@ -21332,7 +21416,7 @@ msgstr "Mikrofon" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Mikrofontest" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Diverse" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "client_id saknas" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Fler alternativ" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Fler genvägsalternativ" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Ingen behörighet för {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Ingen åtkomst" @@ -22984,7 +23068,7 @@ msgstr "Inga träffar" msgid "No mentions yet" msgstr "Inga omnämnanden än" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Ingen mikrofon hittades" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Inga inställningar hittades" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Ingen genväg inställd" @@ -23358,6 +23442,11 @@ msgstr "Inte ett heltal" msgid "Not accepted" msgstr "Inte accepterat" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Inte tilldelad" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Öppna {host} i webbläsaren." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Öppna {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Öppna {labelText} i inställningarna" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "länksida" @@ -25611,11 +25700,11 @@ msgstr "Närvaro" msgid "Press {shortcut} to react" msgstr "Tryck på {shortcut} för att reagera" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Tryck på en tangent, musknapp eller knapp på en spelkontroll för att tilldela den. Escape avbryter." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Tryck ett kortkommando…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Tangent för tryck för att prata" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Tryck för att prata" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Tryck för att prata (begränsat)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Tryck för att prata har ingen systemövergripande tangent än. Gå till Inställningar > Appar > {productName} för att ställa in en." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Tryck för att prata har ingen systemövergripande tangent än. Gå till Systeminställningar > Tangentbord > Genvägar > {productName} för att ställa in en." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Tryck för att prata har ingen systemövergripande tangent än. Öppna inställningarna för tangentbordsgenvägar i din skrivbordsmiljö för att ställa in en." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Tryck för att prata ignoreras vid direktinmatning. Växla till fokuserat röstläge eller anpassat läge för att använda funktionen." @@ -26293,21 +26397,51 @@ msgstr "Tryck för att prata ignoreras vid direktinmatning. Växla till fokusera msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Tryck för att prata är på. Håll ned {pushToTalkHint} för att prata." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Tryck för att prata är på. Håll ned din tangent för att prata." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Tryck för att prata fungerar bara när {productName} är i fokus eftersom din skrivbordsmiljö inte ställde in några systemövergripande genvägar." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Tryck för att prata fungerar bara när {productName} har fokus tills {permissionName} har aktiverats. Avsluta {productName} helt och starta om appen efter ändringen så att globala kortkommandon kan använda behörigheten." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Tryck för att prata fungerar bara när {productName} är i fokus tills du ställer in systemövergripande genvägar." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Tryck för att prata fungerar bara när webbläsarfliken har fokus." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Tryck för att prata använder tangenten du har ställt in i {productName}. Ställ in en först, annars använder mikrofonen röstaktivitet." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Tryck för att prata slår inte på mikrofonen när ljudet är avstängt. Slå på ljudet för att prata." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Tryck för att prata fungerar utanför {productName} först när du har bundit det i din Hyprland-konfiguration." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Tryck för att prata fungerar utanför {productName} först när du har bundit det i din Hyprland-konfiguration. Lägg till raden för din Hyprland-version och ersätt KEY med den tangent du vill använda." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Rå JSON för inbäddning" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Återaktivera inbyggd genväg" @@ -26565,13 +26699,6 @@ msgstr "Senaste omnämnanden" msgid "Recent searches" msgstr "Senaste sökningar" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Kontrollera igen" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Rekommenderas" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Rekommenderas. Rensar mikrofonsignalen för tydligt tal." @@ -26600,6 +26727,11 @@ msgstr "Återanslut" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Återanslutningen lyckades. Välj en skärm igen om du vill återuppta din tidigare sändning." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Ansluter till skrivbordsmiljön igen…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Återansluter…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Spela in kortkommando" @@ -26851,7 +26983,7 @@ msgstr "Relativ" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Fördröjning efter att tangenten släpps" @@ -27739,13 +27871,13 @@ msgstr "Skickat igen" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Återställ" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Återställ ”{label}” till det inbyggda alternativet" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Återställ till faktiska värden" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Återställ till inbyggda" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Välj det konto du vill logga in med på din nya enhet." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Välj röstbehandlingsläge" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Skickar rått mikrofonljud utan bearbetning." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Skickar ditt ljud oförändrat. Bäst om du använder extern ljudprogramvara." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Ställs in av förinställningen {preset}. Att välja ett värde här växlar till Anpassad." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Ange anpassad genväg" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Ange anpassad genväg för \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "Ställde in röstregionen till {region}" msgid "Set the voice region to automatic" msgstr "Ställde in röstregionen till automatisk" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Ställ in" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Inställningar" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "inställningslänk" @@ -30330,8 +30467,8 @@ msgstr "Kortkoder" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Byt till rymlig layout" msgid "Switch to dense layout" msgstr "Växla till kompakt layout" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Byt till direktinmatning" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Systemhanterad installation" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Systemövergripande genvägar" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Systemövergripande genvägar kunde inte ställas in." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Systemövergripande: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Systemövergripande: inte tilldelad" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Sidan du letar efter finns inte eller har flyttats." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Den angivna redirect_uri är inte registrerad för den här appen." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Den angivna redirect_uri måste använda HTTP eller HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Den här animeringen är för lång eller för stor för att beskära. Prova en kortare eller mindre fil." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Den här appen" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Den här bilagan har upphört att gälla" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Den här botten är inte offentlig och kan inte läggas till av andra användare." @@ -34081,6 +34233,16 @@ msgstr "Den här konversationen är inte längre tillgänglig." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Den här versionen av datorappen kan inte installera uppdateringar inifrån {productName}. Ladda ner det senaste installationsprogrammet för datorappen i stället." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Den här skrivbordsmiljön har inte stöd för systemövergripande genvägar. Tryck för att prata fungerar bara när {productName} är i fokus." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Den här skrivbordsmiljön har inte stöd för systemövergripande genvägar. Genvägar fungerar fortfarande när {productName} är i fokus." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Det här är din sista lösennyckel, och lösennycklar är din andra fak msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Det här är din sista lösennyckel. När den är borta kan du inte längre använda en lösennyckel som din andra faktor." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Den här tangenten fungerar när {productName} är i fokus. Din skrivbordsmiljö ställer in den systemövergripande tangenten, under {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Den här länkkanalen är markerad för vuxet innehåll och kan öppna material som är olämpligt för vissa användare." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Den här länken är inte tillgänglig för ditt konto." @@ -34778,6 +34945,21 @@ msgstr "Namnlist" msgid "Title bar platform" msgstr "Plattform för namnlist" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Gå till Inställningar > Appar > {productName} för att ändra tangenterna." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Gå till Systeminställningar > Tangentbord > Genvägar > {productName} för att ändra tangenterna." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Öppna inställningarna för tangentbordsgenvägar i din skrivbordsmiljö för att ändra tangenterna." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "För att skapa en webhook, öppna <0>{channelWebhooksSettingsPath}. Du kan fortfarande redigera och organisera alla befintliga webhooks här." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Okänt" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Okänd app, utgången session eller ogiltig auktoriseringsförfrågan." @@ -36410,6 +36592,11 @@ msgstr "Upp till 4K/60 fps" msgid "Up to 720p at 30 FPS" msgstr "Upp till 720p i 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Uppdatera {productName} för att använda systemövergripande genvägar i Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Använd mörkt tema i hela appen" msgid "Use default" msgstr "Använd standard" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Använd direkt åtkomst till indataenheter" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Röstdeltagare" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Röstbearbetning" @@ -38264,7 +38456,7 @@ msgstr "Slå på ljudet" msgid "Voice unmute" msgstr "Slå på mikrofonen" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Röstsamtal använder systemets ljudutgång i den här webbläsaren. Ändra den i systeminställningarna." @@ -38278,7 +38470,7 @@ msgstr "Ogiltig" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Väntar på att väntetiden för långsamt läge ska löpa ut i en eller msgid "Waiting for you to..." msgstr "Väntar på att du ska..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Väntar på skrivbordsmiljön…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Vilka konversationer" msgid "Which rule was broken?" msgstr "Vilken regel bröts?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "När tryck för att prata är på styr kortkommandot mikrofonen, så knappen för att stänga av mikrofonen är inaktiverad." @@ -39094,6 +39291,11 @@ msgstr "Utan den här behörigheten krävs tryck för att prata." msgid "Wordmark" msgstr "Ordlogotyp" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Fungerar när {productName} är i fokus" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Du ändrade din tagg medan du hade {PREMIUM_PRODUCT_NAME}. Den slumpas om när ditt {PREMIUM_PRODUCT_NAME} går ut, om det inte förnyas." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Du har inte åtkomst till kanalen där meddelandet skickades." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Ditt {premiumProductName}-abonnemang har gått ut. Du har förlorat alla {premiumProductName}-förmåner. Återaktivera abonnemanget för att få åtkomst igen." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Din {productName}-session har upphört. Logga in igen för att fortsätta auktorisera den här appen." @@ -40144,6 +40346,16 @@ msgstr "Din community behöver minst {minMemberCount, plural, one {# medlem} oth msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Ditt kreditkort har kontrollerats. Om kortet är giltigt kommer vuxet innehåll att låsas upp inom kort. Du kan stänga den här fliken och återgå till appen." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Din skrivbordsmiljö ställde inte in några systemövergripande genvägar." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Din skrivbordsmiljö kör de här genvägarna även när {productName} inte är i fokus." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/th/messages.po b/fluxer_app/src/features/i18n/locales/th/messages.po index dd4cb8d54..7159cb664 100644 --- a/fluxer_app/src/features/i18n/locales/th/messages.po +++ b/fluxer_app/src/features/i18n/locales/th/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# ช่องทางติดตาม} other {# msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {พบ # คอมมูนิตี้} other {พบ # คอมมูนิตี้}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# คอมมูนิตี้ไม่พร้อมใช้งานชั่วคราว เนื่องจากเครื่องฟลักซ์คาปาซิเตอร์ทำงานผิดปกติ} other {# คอมมูนิตี้ไม่พร้อมใช้งานชั่วคราว เนื่องจากเครื่องฟลักซ์คาปาซิเตอร์ทำงานผิดปกติ}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} จะไม่ส่งการแจ้งเตือ msgid "{productName} Bug Hunter" msgstr "นักล่าบั๊ก {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} สามารถขอให้เดสก์ท็อปของคุณเรียกใช้ปุ่มลัดของแอปได้แม้ {productName} ไม่ได้อยู่ในโฟกัส เดสก์ท็อปจะขอให้คุณยืนยันและให้คุณเลือกปุ่มเอง" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} อ่านคีย์บอร์ดของคุณไม่ได้แล้ว จึงรับปุ่มลัดผ่าน X11 แทน" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} อ่านคีย์บอร์ดของคุณไม่ได้แล้ว จึงใช้ปุ่มลัดของเดสก์ท็อปแทน" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} อ่านคีย์บอร์ดของคุณไม่ได้แล้ว ปุ่มลัดจะทำงานเมื่อ {productName} อยู่ในโฟกัสเท่านั้น" + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} สามารถลงทะเบียนปุ่มลัดกับ Hyprland เพื่อให้ทำงานได้แม้ {productName} ไม่ได้อยู่ในโฟกัส จากนั้นให้ผูกคีย์ในไฟล์กำหนดค่า Hyprland ของคุณ" + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} ต้องการสิทธิ์เข้าถึงกล้องเพื่อแสดงรายการอุปกรณ์ของคุณ" #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} ต้องการสิทธิ์เข้าถึงไมโครโฟนเพื่อแสดงรายการอุปกรณ์ของคุณ" @@ -1837,6 +1862,11 @@ msgstr "{productName} ตอนนี้อยู่ที่ {host} แล้ msgid "{productName} Partner" msgstr "พาร์ทเนอร์ {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} ลงทะเบียนปุ่มลัดกับ Hyprland แล้ว ผูกคีย์ในไฟล์กำหนดค่า Hyprland เพื่อใช้ปุ่มลัดเหล่านี้ขณะที่ {productName} ไม่ได้อยู่ในโฟกัส" + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "โปรไฟล์ที่โดดเด่น" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "ต้องระบุ redirect_uri เมื่อขอบเขต bot ไม่ใช่ขอบเขตเดียว" @@ -2847,7 +2877,7 @@ msgstr "เฉพาะข้อความส่วนตัวทั้งห msgid "Across every community you're in" msgstr "ในทุกคอมมูนิตี้ที่คุณเป็นสมาชิก" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "การกระทำ" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "การแชร์กิจกรรม" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "เกณฑ์กิจกรรม" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "เพิ่มความปลอดภัยอีกขั้นให้กับบัญชีของคุณ" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "เพิ่มปุ่มลัดที่กำหนดเองอีก" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "ที่อยู่" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "ปรับการตั้งค่าแต่ละรายการด้วยตัวเอง: การลดเสียงรบกวน การตัดเสียงสะท้อน และเกน" @@ -3696,11 +3726,11 @@ msgstr "อนุญาต {everyoneMention} และ {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "อนุญาตให้ {PRODUCT_NAME} เข้าถึงกล้องของคุณใน {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "อนุญาตให้ {PRODUCT_NAME} เข้าถึงไมโครโฟนของคุณใน {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "อนุญาตให้ {PRODUCT_NAME} เข้าถึงไมโครโฟนของคุณ ตรวจสอบแถบที่อยู่หรือการตั้งค่าของเบราว์เซอร์ของคุณ" @@ -3803,7 +3833,7 @@ msgstr "อนุญาตให้สมาชิกที่ไม่มีส msgid "Allow mentions for this role" msgstr "อนุญาตให้กล่าวถึงบทบาทนี้" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "อนุญาตให้ใช้ไมโครโฟน" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "การอนุญาตล้มเหลว" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "การอนุญาตล้มเหลว ลองอีกครั้ง" @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "ตรวจจับภาษาอัตโนมัติ" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "ตั้งค่าเกณฑ์กิจกรรมอัตโนมัติ" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "สิทธิ์ของบอท" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "สิทธิ์ของบอทต้องไม่เป็นค่าลบ" @@ -6869,6 +6899,11 @@ msgstr "เปลี่ยนชื่อเล่นกลุ่ม" msgid "Change icon" msgstr "เปลี่ยนไอคอน" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "เปลี่ยนในการตั้งค่าระบบ" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "การเข้าถึงช่อง" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "ไม่สามารถเข้าถึงช่องได้" @@ -7294,7 +7329,7 @@ msgstr "คัดลอก ID ช่องแล้ว" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "ลิงก์ช่อง" @@ -7462,6 +7497,11 @@ msgstr "ช่องพิมพ์ข้อความ" msgid "Chat shortcuts" msgstr "ปุ่มลัดแชท" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "ตรวจสอบอีกครั้ง" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "เลือกหน้าต่างจากตัวเลือก msgid "Choose an account" msgstr "เลือกบัญชี" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "เลือกการดำเนินการ" @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "เลือกพื้นหลัง" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "เลือก guild_id หรือ channel_id อย่างใดอย่างหนึ่ง ไม่ใช่ทั้งสองอย่าง" @@ -7990,7 +8030,7 @@ msgstr "ปรับปรุงไมโครโฟนของคุณเพ #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "คลิกเพื่อเปิดตัวเลือกสีแ msgid "Click to react" msgstr "คลิกเพื่อส่งรีแอ็กชัน" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "คลิกเพื่อบันทึกปุ่มลัด" @@ -9110,7 +9150,7 @@ msgstr "การยืนยัน" msgid "Confirmation or double-click for community voice joins" msgstr "ยืนยันหรือดับเบิลคลิกเพื่อเข้าร่วมช่องเสียงของคอมมูนิตี้" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "ขัดแย้งกับ {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "เชื่อมต่อกล้องหรืออุปกรณ msgid "Connect a display, then try again." msgstr "เชื่อมต่อจอแสดงผล แล้วลองอีกครั้ง" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "เชื่อมต่อไมโครโฟนแล้วลองอีกครั้ง" @@ -9700,6 +9740,21 @@ msgstr "คัดลอกลิงก์เชิญ" msgid "Copy invite URL" msgstr "คัดลอก URL คำเชิญ" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "คัดลอกบรรทัด" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "คัดลอกบรรทัดสำหรับ hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "คัดลอกบรรทัดสำหรับ hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "กำหนดเอง" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "เลือกเอง" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "การกระทำทางลัดที่กำหนดเอง" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "ตัวเลือกทางลัดที่กำหนดเอง" @@ -11466,7 +11521,7 @@ msgstr "ปุ่มลัดเริ่มต้น" msgid "Default translator" msgstr "เครื่องมือแปลภาษาเริ่มต้น" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "ค่าเริ่มต้น:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "ลบสื่อที่บันทึกไว้" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "ลบปุ่มลัด" @@ -12102,13 +12157,13 @@ msgstr "สถานะสำหรับนักพัฒนา" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "เครื่องมือสำหรับนักพัฒนา" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "เครื่องมือสำหรับนักพัฒนาสามารถใช้งานได้จากป้ายพนักงานในส่วนหัวของช่อง" @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "ปิดสิทธิประโยชน์พรีเมียมของแบ็กเอนด์" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "ปิดปุ่มลัดในตัว" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "ต้องการปิดหน้าต่างป๊อปอัพแชร์หน้าจอใช่หรือไม่" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "ปิดใช้งานปุ่มลัด" @@ -12888,7 +12943,7 @@ msgstr "บริจาคแทน" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "ปุ่มดาวน์โหลด" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "ดาวน์โหลดแอปเดสก์ท็อป" @@ -13204,6 +13259,11 @@ msgstr "ดัตช์" msgid "e.g., YubiKey, iPhone, work computer" msgstr "เช่น YubiKey, iPhone, คอมพิวเตอร์ที่ทำงาน" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "แต่ละคีย์ใช้กับปุ่มลัดทั่วทั้งระบบได้เพียงรายการเดียว" + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "แก้ไขลิงก์เชิญ" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "แก้ไขปุ่มลัดคีย์บอร์ด" @@ -13425,7 +13485,7 @@ msgstr "แบบฟอร์มแก้ไขสื่อที่บันท msgid "Edit search engine" msgstr "แก้ไขเครื่องมือค้นหา" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "แก้ไขปุ่มลัด" @@ -13753,20 +13813,16 @@ msgstr "แก้ไขให้ว่าง" msgid "Empty favorites" msgstr "รายการโปรดว่างเปล่า" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "เปิดใช้งาน" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "เปิดใช้งานสิทธิ์ {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "เปิดใช้งานการเร่งฮาร์ดแว msgid "Enable hardware acceleration?" msgstr "เปิดใช้งานการเร่งฮาร์ดแวร์หรือไม่" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "เปิดใช้งานการเข้าถึงอินพุตเพื่อใช้ปุ่มลัดทั่วทั้งระบบบน Wayland หลังจากเปลี่ยนการเข้าถึงอินพุตแล้ว ให้ปิด {productName} ให้สนิทแล้วเปิดใหม่ เพื่อให้ปุ่มลัดใช้งานได้" - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "เปิดใช้ Shift เพื่อขยาย" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "เปิดใช้ปุ่มลัด" @@ -14357,7 +14408,7 @@ msgstr "เสียงทั้งระบบ" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "เสียงเข้าห้อง" @@ -14467,7 +14518,7 @@ msgstr "ช่วงเย็น" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "ปรับระดับเสียงไมโครโฟนของคุณให้สม่ำเสมอ เพื่อไม่ให้เสียงเบาเกินไป" @@ -15682,6 +15733,26 @@ msgstr "สำหรับชาวอังกฤษเท่านั้น... msgid "For me" msgstr "สำหรับฉัน" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "สำหรับกดเพื่อปิดไมโครโฟน ให้ใช้ปุ่มเดียวที่ไม่มี Ctrl, Alt หรือ Shift หากใช้ปุ่มผสม ให้ปล่อยปุ่มหลักก่อน มิฉะนั้นไมโครโฟนอาจยังปิดอยู่" + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "สำหรับกดเพื่อพูด ให้ใช้ปุ่มเดียวที่ไม่มี Ctrl, Alt หรือ Shift หากใช้ปุ่มผสม ให้ปล่อยปุ่มหลักก่อน มิฉะนั้นไมโครโฟนอาจยังเปิดอยู่" + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "สำหรับปุ่มลัดนี้ ให้ใช้ปุ่มเดียวที่ไม่มี Ctrl, Alt หรือ Shift หากใช้ปุ่มผสม ให้ปล่อยปุ่มหลักก่อน มิฉะนั้นปุ่มลัดอาจยังทำงานอยู่" + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "สำหรับการตรวจจับเสียงพูด (สำคัญ) ให้ใช้ปุ่มเดียวที่ไม่มี Ctrl, Alt หรือ Shift หากใช้ปุ่มผสม ให้ปล่อยปุ่มหลักก่อน มิฉะนั้นลำดับความสำคัญอาจยังเปิดอยู่" + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "เพื่อความปลอดภัย โปรดยืนยันตัวตนเพื่อดำเนินการต่อ" @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "โปรไฟล์ส่วนกลาง" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "ปุ่มลัดทั่วทั้งระบบ" @@ -17434,6 +17505,11 @@ msgstr "กด Shift ค้างไว้แล้วกด X เพื่อ msgid "Hold to record. Drag up to lock, or release to send." msgstr "กดค้างเพื่อบันทึกเสียง ลากขึ้นเพื่อล็อก หรือปล่อยเพื่อส่ง" +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "การกด Shift, Ctrl หรือ Alt ค้างไว้จะทำให้ปุ่มลัดนี้ไม่ทำงาน เพิ่มปุ่มสำรองได้ในการตั้งค่าระบบ" + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "เยี่ยม! เปิดการแจ้งเตือนแ msgid "Hyperlink" msgstr "ไฮเปอร์ลิงก์" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland ไม่มีหน้าต่างตั้งค่าปุ่มลัด เพิ่มบรรทัดสำหรับเวอร์ชัน Hyprland ของคุณลงในไฟล์กำหนดค่า Hyprland แล้วแทนที่ KEY ด้วยคีย์ที่ต้องการ" + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland ไม่มีหน้าต่างตั้งค่าปุ่มลัด ให้ผูกคีย์ในไฟล์กำหนดค่า Hyprland ของคุณ" + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (ก่อน Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 ขึ้นไป)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "ฉันยอมรับ <0>นโยบายความเป็นส่วนตัว" @@ -17576,7 +17672,7 @@ msgstr "เปลี่ยนใจแล้ว" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "ฉันเข้าใจ" @@ -18000,7 +18096,7 @@ msgstr "ใน" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "ในโหมด \"ระบบปฏิบัติการ\" พจนานุกรมที่แสดงด้านบนเป็นเพียงคำแนะนำ {PRODUCT_NAME} จะส่งแท็กภาษาเหล่านี้ไปยังโปรแกรมตรวจการสะกดของระบบปฏิบัติการของคุณ หากระบบปฏิบัติการรองรับ แท็กที่ระบบไม่รู้จักจะถูกละเว้น" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "ในเบราว์เซอร์ การกดเพื่อพูดจะทำงานเมื่อแท็บ {PRODUCT_NAME} โฟกัสอยู่เท่านั้น ติดตั้งแอปเดสก์ท็อปเพื่อใช้การกดเพื่อพูดได้ทั่วทั้งระบบ" @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "การป้อนข้อความ" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "ไม่สามารถเปิดใช้งานการเข้าถึงอินพุตได้โดยอัตโนมัติ โปรดเปลี่ยนด้วยตนเอง จากนั้นออกจาก {productName} โดยสมบูรณ์แล้วเริ่มใหม่ เพื่อให้ปุ่มลัดใช้งานได้" - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "ไม่สามารถเปิดใช้งานการเข้าถึงอินพุตได้ ลองอีกครั้งจากการตั้งค่า {settingsMenuName} จากนั้นออกจาก {productName} โดยสมบูรณ์แล้วเริ่มใหม่" - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "ต้องมีการเข้าถึงอินพุตสำหรับปุ่มลัดแป้นพิมพ์และเมาส์ทั่วทั้งระบบบน Wayland หลังจากเปลี่ยนการเข้าถึงอินพุต ให้ออกจาก {productName} โดยสมบูรณ์แล้วเริ่มใหม่ เพื่อให้ปุ่มลัดสามารถใช้งานได้" - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "การเข้าถึงอินพุตมีการเปลี่ยนแปลง โปรดออกจาก {productName} โดยสมบูรณ์แล้วเริ่มใหม่ เพื่อให้สามารถใช้ปุ่มลัดทั่วทั้งระบบได้" - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "อินพุตและเอาต์พุต" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "คำขออนุญาตไม่ถูกต้อง" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "สิทธิ์ของบอทไม่ถูกต้อง" @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "รหัสไม่ถูกต้องหรือหมดอายุแล้ว ลองอีกครั้ง" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "redirect_uri ไม่ถูกต้อง" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "ซ่อนลิงก์เชิญขณะแชร์หน้าจอ:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "ซ่อนลิงก์เชิญไว้ขณะแชร์หน้าจอ" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "ข้ามไปที่ {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "ข้ามไปที่ {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "ข้ามไปที่การโทรปัจจุบัน" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "ไปยังช่องที่ลิงก์" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "ข้ามไปยังข้อความที่ลิงก์" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "ข้ามไปที่ข้อความจาก {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "ข้ามไปที่ข้อความใน {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "เก็บข้อมูลนี้ไว้ให้ปลอดภัย การสร้างใหม่จะทำให้การเชื่อมต่อที่มีอยู่ใช้งานไม่ได้" #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "ยังคงส่งสัญญาณต่อสักครู่หลังปล่อยปุ่มค้างไว้" @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "แป้นพิมพ์" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "ไม่สามารถเริ่มการเข้าถึงคีย์บอร์ดได้ ปุ่มลัดจะทำงานเมื่อ {productName} อยู่ในโฟกัสเท่านั้น" + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "ให้คอมมูนิตี้จัดการกันเอ msgid "Let the member hear others in this community again" msgstr "ให้สมาชิกได้ยินผู้อื่นในคอมมูนิตี้นี้อีกครั้ง" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "ให้ {productName} อ่านคีย์บอร์ดของคุณโดยตรงได้ ระบบของคุณอนุญาตสิ่งนี้อยู่แล้ว" + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "ให้ CSS ที่กำหนดเองแสดงพื้นที่หน้าต่างโปร่งใสหลังจากรีสตาร์ทแอปเดสก์ท็อป" @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "สไตล์ลิงก์" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "ลิงก์ใช้งานไม่ได้" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "รูปลักษณ์และการแสดงผล" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "ดูเหมือนว่าคุณกำลังใช้ {0} การประมวลผลของ {PRODUCT_NAME} เพิ่มเติมอาจทำให้เกิดเสียงปั๊มหรือเสียงผิดเพี้ยนได้ อินพุตโดยตรงจะส่งสัญญาณเสียงของคุณไปโดยไม่มีการเปลี่ยนแปลง" @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "รูปแบบข้อความ" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "ลิงก์ข้อความ" @@ -21332,7 +21416,7 @@ msgstr "ไมค์" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "ทดสอบไมโครโฟน" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "เบ็ดเตล็ด" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "ไม่มี client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "ตัวเลือกเพิ่มเติม" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "ตัวเลือกทางลัดเพิ่มเติม" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "ไม่มีสิทธิ์ {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "ไม่สามารถเข้าถึงได้" @@ -22984,7 +23068,7 @@ msgstr "ไม่พบผลลัพธ์" msgid "No mentions yet" msgstr "ยังไม่มีการกล่าวถึง" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "ไม่พบไมโครโฟน" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "ไม่พบการตั้งค่า" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "ไม่ได้ตั้งค่าปุ่มลัด" @@ -23358,6 +23442,11 @@ msgstr "ไม่ใช่จำนวนเต็ม" msgid "Not accepted" msgstr "ไม่ยอมรับ" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "ไม่ได้กำหนด" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "เปิด {host} ในเบราว์เซอร์ของคุณ" #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "เปิด {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "เปิด {labelText} ในการตั้งค่า" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "ลิงก์หน้า" @@ -25611,11 +25700,11 @@ msgstr "สถานะออนไลน์" msgid "Press {shortcut} to react" msgstr "กด {shortcut} เพื่อแสดงรีแอ็กชัน" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "กดปุ่มบนคีย์บอร์ด เมาส์ หรือเกมแพดเพื่อผูกคำสั่ง หากต้องการยกเลิกให้กด Escape" -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "กดปุ่มลัด…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "ปุ่มกดเพื่อพูด" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "กดเพื่อพูด" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "กดเพื่อพูด (จำกัด)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "กดเพื่อพูดยังไม่มีปุ่มทั่วทั้งระบบ หากต้องการตั้งค่า ให้ไปที่ การตั้งค่า > แอป > {productName}" + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "กดเพื่อพูดยังไม่มีปุ่มทั่วทั้งระบบ หากต้องการตั้งค่า ให้ไปที่ การตั้งค่าระบบ > คีย์บอร์ด > ปุ่มลัด > {productName}" + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "กดเพื่อพูดยังไม่มีปุ่มทั่วทั้งระบบ หากต้องการตั้งค่า ให้เปิดการตั้งค่าปุ่มลัดคีย์บอร์ดของเดสก์ท็อป" + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "กดเพื่อพูดจะไม่ทำงานในโหมดอินพุตโดยตรง หากต้องการใช้งาน ให้เปลี่ยนเป็นเน้นเสียงพูดหรือกำหนดเอง" @@ -26293,21 +26397,51 @@ msgstr "กดเพื่อพูดจะไม่ทำงานในโห msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "โหมดกดเพื่อพูดเปิดอยู่ กด {pushToTalkHint} ค้างไว้เพื่อพูด" +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "กดเพื่อพูดเปิดอยู่ กดปุ่มกดเพื่อพูดค้างไว้เพื่อพูด" + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "กดเพื่อพูดจะทำงานเมื่อ {productName} อยู่ในโฟกัสเท่านั้น เนื่องจากเดสก์ท็อปของคุณไม่ได้ตั้งค่าปุ่มลัดทั่วทั้งระบบ" + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "กดเพื่อพูดจะทำงานเมื่อ {productName} อยู่ในโฟกัสเท่านั้น จนกว่าจะเปิดใช้งาน {permissionName} หลังจากเปลี่ยนสิทธิ์แล้ว ให้ออกจาก {productName} และเริ่มใหม่ทั้งหมด เพื่อให้ปุ่มลัดทั่วทั้งระบบสามารถใช้งานได้" +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "กดเพื่อพูดจะทำงานเมื่อ {productName} อยู่ในโฟกัสเท่านั้น จนกว่าคุณจะตั้งค่าปุ่มลัดทั่วทั้งระบบ" + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "กดเพื่อพูดจะทำงานเมื่อแท็บเบราว์เซอร์อยู่ในโฟกัสเท่านั้น" +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "กดเพื่อพูดจะใช้ปุ่มที่คุณตั้งไว้ใน {productName} โปรดตั้งปุ่มก่อน มิฉะนั้นไมโครโฟนจะใช้การตรวจจับเสียงพูด" + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "กดเพื่อพูดจะไม่เปิดไมโครโฟนของคุณขณะที่คุณปิดเสียงทั้งหมดอยู่ เปิดเสียงทั้งหมดของตัวเองเพื่อพูด" +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "กดเพื่อพูดจะทำงานนอก {productName} ได้หลังจากที่คุณผูกปุ่มในไฟล์กำหนดค่า Hyprland แล้วเท่านั้น" + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "กดเพื่อพูดจะทำงานนอก {productName} ได้หลังจากที่คุณผูกปุ่มในไฟล์กำหนดค่า Hyprland แล้วเท่านั้น เพิ่มบรรทัดสำหรับเวอร์ชัน Hyprland ของคุณ แล้วแทนที่ KEY ด้วยคีย์ที่ต้องการ" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "JSON ดิบของการฝัง" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "เปิดปุ่มลัดในตัวอีกครั้ง" @@ -26565,13 +26699,6 @@ msgstr "การกล่าวถึงล่าสุด" msgid "Recent searches" msgstr "การค้นหาล่าสุด" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "ตรวจสอบอีกครั้ง" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "แนะนำ" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "แนะนำ: ปรับปรุงไมโครโฟนของคุณเพื่อให้เสียงพูดชัดเจน" @@ -26600,6 +26727,11 @@ msgstr "เชื่อมต่อใหม่" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "เชื่อมต่อใหม่สำเร็จ หากต้องการสตรีมต่อ ให้เลือกหน้าจออีกครั้ง" +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "กำลังเชื่อมต่อกับเดสก์ท็อปของคุณใหม่…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "กำลังเชื่อมต่อใหม่…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "บันทึกปุ่มลัด" @@ -26851,7 +26983,7 @@ msgstr "แบบสัมพัทธ์" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "หน่วงเวลาหลังปล่อยปุ่ม" @@ -27739,13 +27871,13 @@ msgstr "ส่งอีกครั้งแล้ว" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "รีเซ็ต" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "รีเซ็ต \"{label}\" เป็นค่าเริ่มต้น" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "รีเซ็ตเป็นค่าจริง" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "รีเซ็ตเป็นค่าเริ่มต้น" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "เลือกบัญชีที่คุณต้องการลงชื่อเข้าใช้บนอุปกรณ์ใหม่ของคุณ" #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "เลือกโหมดประมวลผลเสียง" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "ส่งสัญญาณเสียงไมโครโฟนดิบโดยไม่มีการประมวลผล" #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "ส่งเสียงของคุณโดยไม่มีการปรับแต่ง เหมาะที่สุดหากคุณใช้ซอฟต์แวร์เสียงภายนอก" @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "ตั้งค่าโดยค่าที่ตั้งไว้ล่วงหน้า {preset} การเลือกค่าที่นี่จะเปลี่ยนเป็นแบบกำหนดเอง" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "ตั้งค่าปุ่มลัดเอง" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "ตั้งค่าปุ่มลัดสำหรับ \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "ตั้งค่าภูมิภาคเสียงเป็น {r msgid "Set the voice region to automatic" msgstr "ตั้งค่าภูมิภาคเสียงเป็นอัตโนมัติ" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "ตั้งค่า" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "การตั้งค่า" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "ลิงก์การตั้งค่า" @@ -30330,8 +30467,8 @@ msgstr "รหัสย่อ" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "เปลี่ยนเป็นเลย์เอาต์สบาย msgid "Switch to dense layout" msgstr "เปลี่ยนเป็นเลย์เอาต์แบบกระชับ" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "เปลี่ยนเป็นอินพุตโดยตรง" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "ติดตั้งโดยระบบ" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "ปุ่มลัดทั่วทั้งระบบ" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "ตั้งค่าปุ่มลัดทั่วทั้งระบบไม่สำเร็จ" + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "ทั่วทั้งระบบ: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "ทั่วทั้งระบบ: ไม่ได้กำหนด" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "หน้านี้ไม่มีอยู่จริงหรืออาจถูกย้ายไปแล้ว" #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "redirect_uri ที่ระบุไม่ได้ลงทะเบียนไว้สำหรับแอปพลิเคชันนี้" #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "redirect_uri ที่ระบุต้องใช้ HTTP หรือ HTTPS" @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "ภาพเคลื่อนไหวนี้ยาวหรือใหญ่เกินไปที่จะครอบตัดได้ ลองใช้ไฟล์ที่สั้นกว่าหรือเล็กกว่า" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "แอปนี้" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "ไฟล์แนบนี้หมดอายุแล้ว" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "บอทนี้ไม่เป็นสาธารณะและผู้ใช้รายอื่นไม่สามารถเพิ่มได้" @@ -34081,6 +34233,16 @@ msgstr "การสนทนานี้ไม่พร้อมใช้งา msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "บิลด์เดสก์ท็อปนี้ไม่สามารถติดตั้งการอัปเดตภายใน {productName} ได้ โปรดดาวน์โหลดตัวติดตั้งเดสก์ท็อปล่าสุดแทน" +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "เดสก์ท็อปนี้ไม่รองรับปุ่มลัดทั่วทั้งระบบ กดเพื่อพูดจะทำงานเมื่อ {productName} อยู่ในโฟกัสเท่านั้น" + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "เดสก์ท็อปนี้ไม่รองรับปุ่มลัดทั่วทั้งระบบ แต่ปุ่มลัดยังใช้งานได้เมื่อ {productName} อยู่ในโฟกัส" + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "นี่คือพาสคีย์สุดท้ายของค msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "นี่คือพาสคีย์สุดท้ายของคุณ เมื่อลบแล้ว คุณจะไม่สามารถใช้พาสคีย์เป็นการยืนยันชั้นที่สองได้" +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "ปุ่มนี้ทำงานเมื่อ {productName} อยู่ในโฟกัส เดสก์ท็อปของคุณเป็นผู้ตั้งปุ่มทั่วทั้งระบบ ที่ {settingsTabName} > {sectionName}" + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "ช่องลิงก์นี้มีเนื้อหาสำหรับผู้ใหญ่ และอาจเปิดเนื้อหาที่ไม่เหมาะสมสำหรับผู้ใช้บางราย" #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "ลิงก์นี้ไม่พร้อมใช้งานสำหรับบัญชีของคุณ" @@ -34778,6 +34945,21 @@ msgstr "แถบชื่อเรื่อง" msgid "Title bar platform" msgstr "แพลตฟอร์มแถบชื่อเรื่อง" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "หากต้องการเปลี่ยนปุ่ม ให้ไปที่ การตั้งค่า > แอป > {productName}" + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "หากต้องการเปลี่ยนปุ่ม ให้ไปที่ การตั้งค่าระบบ > คีย์บอร์ด > ปุ่มลัด > {productName}" + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "หากต้องการเปลี่ยนปุ่ม ให้เปิดการตั้งค่าปุ่มลัดคีย์บอร์ดของเดสก์ท็อปของคุณ" + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "หากต้องการสร้าง webhook ให้เปิด <0>{channelWebhooksSettingsPath} คุณยังคงแก้ไขและจัดระเบียบ webhook ที่มีอยู่ทั้งหมดได้ที่นี่" @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "ไม่ทราบ" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "แอปพลิเคชันที่ไม่รู้จัก เซสชันหมดอายุ หรือคำขอการอนุญาตไม่ถูกต้อง" @@ -36410,6 +36592,11 @@ msgstr "สูงสุด 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "สูงสุด 720p ที่ 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "อัปเดต {productName} เพื่อใช้ปุ่มลัดทั่วทั้งระบบบน Wayland" + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "ใช้ธีมมืดทั่วทั้งแอป" msgid "Use default" msgstr "ใช้ค่าเริ่มต้น" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "ใช้การเข้าถึงอุปกรณ์อินพุตโดยตรง" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "ผู้เข้าร่วมการโทรด้วยเสียง" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "การประมวลผลเสียง" @@ -38264,7 +38456,7 @@ msgstr "เลิกปิดเสียงทั้งหมด" msgid "Voice unmute" msgstr "เปิดไมโครโฟน" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "การสนทนาด้วยเสียงในเบราว์เซอร์นี้ใช้อุปกรณ์เอาต์พุตของระบบ เปลี่ยนได้ในการตั้งค่าระบบ" @@ -38278,7 +38470,7 @@ msgstr "เป็นโมฆะ" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "กำลังรอให้โหมดจำกัดเวลาใ msgid "Waiting for you to..." msgstr "กำลังรอให้คุณ..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "กำลังรอเดสก์ท็อปของคุณ…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "การสนทนาใดบ้าง" msgid "Which rule was broken?" msgstr "ละเมิดกฎข้อไหน?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "ขณะที่เปิดใช้งานการกดเพื่อพูด ปุ่มลัดจะควบคุมไมโครโฟนของคุณ ดังนั้นปุ่มปิดไมโครโฟนจะถูกปิดใช้งาน" @@ -39094,6 +39291,11 @@ msgstr "หากไม่มีสิทธิ์นี้ จะต้อง msgid "Wordmark" msgstr "เวิร์ดมาร์ก" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "ทำงานเมื่อ {productName} อยู่ในโฟกัส" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "คุณเปลี่ยนแท็กขณะใช้ {PREMIUM_PRODUCT_NAME} แท็กจะถูกสุ่มใหม่เมื่อ {PREMIUM_PRODUCT_NAME} ของคุณหมดอายุ เว้นแต่จะมีการต่ออายุ" #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "คุณไม่มีสิทธิ์เข้าถึงช่องที่มีการส่งข้อความนี้" @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "การสมัครสมาชิก {premiumProductName} ของคุณหมดอายุแล้ว คุณได้สูญเสียสิทธิประโยชน์ทั้งหมดของ {premiumProductName} ไปแล้ว เปิดใช้งานการสมัครสมาชิกของคุณอีกครั้งเพื่อเข้าถึงสิทธิประโยชน์เหล่านั้น" #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "เซสชัน {productName} ของคุณหมดอายุแล้ว โปรดลงชื่อเข้าใช้อีกครั้งเพื่อดำเนินการให้สิทธิ์แอปพลิเคชันนี้ต่อ" @@ -40144,6 +40346,16 @@ msgstr "คอมมูนิตี้ของคุณต้องมีสม msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "เราได้ตรวจสอบบัตรเครดิตของคุณแล้ว หากบัตรถูกต้อง เนื้อหาสำหรับผู้ใหญ่จะปลดล็อกในไม่ช้า คุณสามารถปิดแท็บนี้และกลับไปที่แอปได้เลย" +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "เดสก์ท็อปของคุณไม่ได้ตั้งค่าปุ่มลัดทั่วทั้งระบบ" + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "เดสก์ท็อปของคุณจะเรียกใช้ปุ่มลัดเหล่านี้แม้ {productName} ไม่ได้อยู่ในโฟกัส" + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/tr/messages.po b/fluxer_app/src/features/i18n/locales/tr/messages.po index c82df0957..346bd8175 100644 --- a/fluxer_app/src/features/i18n/locales/tr/messages.po +++ b/fluxer_app/src/features/i18n/locales/tr/messages.po @@ -993,7 +993,7 @@ msgstr "Bu kanalı {count, plural, one {# kanal} other {# kanal}} takip ediyor. msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# topluluk bulundu} other {# topluluk bulundu}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# topluluk, flux kapasitörü arızası nedeniyle geçici olarak kullanılamıyor.} other {# topluluk, flux kapasitörü arızası nedeniyle geçici olarak kullanılamıyor.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName}, bilgisayarınızın başındayken mobil cihazlarınıza a msgid "{productName} Bug Hunter" msgstr "{productName} Hata Avcısı" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName}, {productName} odaklanmamışken de kısayollarını çalıştırması için masaüstünüzden istekte bulunabilir. Masaüstünüz sizden onay ister ve tuşları seçmenize olanak tanır." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} artık klavyenizi okuyamıyor, bu yüzden kısayolları X11 üzerinden dinliyor." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} artık klavyenizi okuyamıyor, bu yüzden bunun yerine masaüstünüzün kısayolları kullanılıyor." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} artık klavyenizi okuyamıyor. Kısayollar yalnızca {productName} odaklanmışken çalışır." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName}, kısayollarını Hyprland'a kaydederek {productName} odaklanmamışken de çalışmalarını sağlayabilir. Ardından tuşları Hyprland yapılandırmanızda atarsınız." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} uygulamasının cihazlarınızı listelemek için kamera erişimine ihtiyacı var." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} uygulamasının cihazlarınızı listelemek için mikrofon erişimine ihtiyacı var." @@ -1837,6 +1862,11 @@ msgstr "{productName} artık {host} adresinde. Oradan yükleyin." msgid "{productName} Partner" msgstr "{productName} İş Ortağı" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName}, kısayollarını Hyprland'a kaydetti. {productName} odaklanmamışken kullanmak için bunlara Hyprland yapılandırmanızda tuş atayın." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Öne çıkan bir profil" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Bot kapsamı tek kapsam olmadığında bir redirect_uri gereklidir." @@ -2847,7 +2877,7 @@ msgstr "Yalnızca tüm açık DM'lerde" msgid "Across every community you're in" msgstr "Üyesi olduğunuz her toplulukta" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Eylem" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Etkinlik paylaşımı" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Etkinlik eşiği" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Hesabınıza ek bir güvenlik katmanı ekleyin" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Başka bir özel kısayol ekle" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Adres" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Gürültü engelleme, yankı giderme ve kazanç ayarlarını kendiniz yapın." @@ -3696,11 +3726,11 @@ msgstr "{everyoneMention} ve {hereMention} etiketlerine izin ver" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "{PRODUCT_NAME} uygulamasının kameranıza erişmesine izin vermek için {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME} yolunu izleyin." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "{PRODUCT_NAME} uygulamasının mikrofonunuza erişmesine izin vermek için {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME} yolunu izleyin." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "{PRODUCT_NAME} uygulamasının mikrofonunuza erişmesine izin verin. Tarayıcınızın adres çubuğunu veya ayarlarını kontrol edin." @@ -3803,7 +3833,7 @@ msgstr "{readMessageHistoryPermissionLabel} izni olmayan üyelerin belirli bir t msgid "Allow mentions for this role" msgstr "Bu rolden bahsedilmesine izin ver" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Mikrofona izin ver" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Yetkilendirme başarısız oldu" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Yetkilendirme başarısız oldu. Tekrar deneyin." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Dili otomatik algıla" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Etkinlik eşiğini otomatik ayarla" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Bot izinleri" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Bot izinleri negatif olamaz." @@ -6869,6 +6899,11 @@ msgstr "Grup takma adını değiştir" msgid "Change icon" msgstr "Simgeyi değiştir" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Sistem ayarlarında değiştir" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Kanal erişimi" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Kanala erişim reddedildi" @@ -7294,7 +7329,7 @@ msgstr "Kanal kimliği panoya kopyalandı" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "kanal bağlantısı" @@ -7462,6 +7497,11 @@ msgstr "mesaj kutusu" msgid "Chat shortcuts" msgstr "Sohbet kısayolları" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Tekrar kontrol et" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Sistem seçicinizden bir pencere seçin" msgid "Choose an account" msgstr "Bir hesap seçin" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Bir eylem seçin." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Arka plan seçin" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "guild_id veya channel_id'den birini seçin, ikisini birden değil." @@ -7990,7 +8030,7 @@ msgstr "Net konuşma için mikrofonunuzu temizler." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Renk seçiciyi açmak ve yeni bir renk seçmek için tıklayın." msgid "Click to react" msgstr "Tepki vermek için tıklayın" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Kaydetmek için tıklayın" @@ -9110,7 +9150,7 @@ msgstr "Onay" msgid "Confirmation or double-click for community voice joins" msgstr "Topluluk sesli kanallarına katılmak için onay veya çift tıklama" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "{conflictLabel} ile çakışıyor" @@ -9127,7 +9167,7 @@ msgstr "Bir kamera veya sanal yakalama cihazı bağlayın, ardından tekrar dene msgid "Connect a display, then try again." msgstr "Bir ekran bağlayın, sonra tekrar deneyin." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Mikrofon bağlayıp tekrar deneyin." @@ -9700,6 +9740,21 @@ msgstr "Davet bağlantısını kopyala" msgid "Copy invite URL" msgstr "Davet URL'sini kopyala" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Satırları kopyala" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "hyprland.conf için satırları kopyala" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "hyprland.lua için satırları kopyala" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Özel" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Özel seçim" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Özel kısayol eylemi" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Özel kısayol seçenekleri" @@ -11466,7 +11521,7 @@ msgstr "Varsayılan kısayollar" msgid "Default translator" msgstr "Varsayılan çevirmen" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Varsayılan:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Kayıtlı medyayı sil" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Kısayolu sil" @@ -12102,13 +12157,13 @@ msgstr "Geliştirici durumu" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Geliştirici araçları" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Geliştirici araçlarına kanal başlığındaki personel rozetinden erişilebilir." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Arka uç premium avantajlarını devre dışı bırak" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Yerleşik kısayolu devre dışı bırak" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Ekran paylaşımı resim içinde resim açılır pencereleri devre dışı bırakılsın mı?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Kısayolu devre dışı bırak" @@ -12888,7 +12943,7 @@ msgstr "Bunun yerine bağış yap" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "İndir düğmesi" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Masaüstü uygulamasını indir" @@ -13204,6 +13259,11 @@ msgstr "Hollandaca" msgid "e.g., YubiKey, iPhone, work computer" msgstr "örn. YubiKey, iPhone, iş bilgisayarı" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Her tuşa sistem genelinde yalnızca bir kısayol atanabilir." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Davet bağlantısını düzenle" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Klavye kısayolunu düzenle" @@ -13425,7 +13485,7 @@ msgstr "Kayıtlı medyayı düzenleme formu" msgid "Edit search engine" msgstr "Arama motorunu düzenle" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Kısayolu düzenle" @@ -13753,20 +13813,16 @@ msgstr "Boş düzenleme" msgid "Empty favorites" msgstr "Favoriler boş" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Etkinleştir" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "{permissionName} iznini etkinleştir" @@ -13876,11 +13932,6 @@ msgstr "Donanım hızlandırmayı etkinleştir ve yeniden başlat" msgid "Enable hardware acceleration?" msgstr "Donanım hızlandırma etkinleştirilsin mi?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Wayland'da sistem genelindeki kısayolları kullanmak için giriş erişimini etkinleştirin. Giriş erişimini değiştirdikten sonra, kısayolların kullanabilmesi için {productName} uygulamasını tamamen kapatıp yeniden başlatın." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Shift ile genişletmeyi etkinleştir" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Kısayolu etkinleştir" @@ -14357,7 +14408,7 @@ msgstr "Tüm sistem sesi" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Giriş sesi" @@ -14467,7 +14518,7 @@ msgstr "Akşam" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Mikrofon ses düzeyinizi dengeler, böylece sesiniz çok kısık çıkmaz." @@ -15682,6 +15733,26 @@ msgstr "Sadece İngilizlere özel..." msgid "For me" msgstr "Kendim için" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Sesi kapatmak için basılı tut özelliğinde Ctrl, Alt veya Shift olmadan tek bir tuş kullanın. Tuş kombinasyonu kullanırsanız önce ana tuşu bırakın, aksi takdirde mikrofon kapalı kalabilir." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Bas konuş için Ctrl, Alt veya Shift olmadan tek bir tuş kullanın. Tuş kombinasyonu kullanırsanız önce ana tuşu bırakın, aksi takdirde mikrofon açık kalabilir." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Bu kısayol için Ctrl, Alt veya Shift olmadan tek bir tuş kullanın. Tuş kombinasyonu kullanırsanız önce ana tuşu bırakın, aksi takdirde kısayol etkin kalabilir." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Ses etkinliği önceliği için Ctrl, Alt veya Shift olmadan tek bir tuş kullanın. Tuş kombinasyonu kullanırsanız önce ana tuşu bırakın, aksi takdirde öncelik açık kalabilir." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Güvenliğiniz için devam etmeden önce lütfen siz olduğunuzu onaylayın." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Genel profil" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Genel kısayol" @@ -17434,6 +17505,11 @@ msgstr "Uygulamaları seçmek için Shift tuşunu basılı tutup X tuşuna bası msgid "Hold to record. Drag up to lock, or release to send." msgstr "Kaydetmek için basılı tutun. Kilitlemek için yukarı sürükleyin veya göndermek için bırakın." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Shift, Ctrl veya Alt basılı tutulurken bu kısayol çalışmaz. Sistem ayarlarınızdan alternatif tuşlar ekleyin." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Yaşasın! Bildirimler etkinleştirildi" msgid "Hyperlink" msgstr "Köprü" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland'ın kısayol iletişim kutusu yok. Hyprland sürümünüz için satırları Hyprland yapılandırmanıza ekleyin ve KEY yerine istediğiniz tuşu girin." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland'ın kısayol iletişim kutusu yok. Tuşları Hyprland yapılandırmanızda atayın." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (Hyprland 0.55 öncesi)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 ve sonrası)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "<0>Gizlilik Politikasını kabul ediyorum" @@ -17576,7 +17672,7 @@ msgstr "Fikrimi değiştirdim" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Anladım" @@ -18000,7 +18096,7 @@ msgstr "İçinde" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "\"İşletim sistemi\" modunda, yukarıda listelenen sözlükler tavsiye niteliğindedir. {PRODUCT_NAME}, desteklendiğinde bu dil etiketlerini işletim sisteminizin yazım denetleyicisine iletecektir. Tanımadığı etiketler yok sayılır." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "Tarayıcıda bas-konuş özelliği yalnızca {PRODUCT_NAME} sekmesi odaklıyken çalışır. Sistem genelinde bas-konuş özelliğini kullanmak için masaüstü uygulamasını yükleyin." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Giriş" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Giriş erişimi otomatik olarak etkinleştirilemedi. Lütfen manuel olarak değiştirin, ardından kısayolların kullanabilmesi için {productName} uygulamasını tamamen kapatıp yeniden başlatın." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Giriş erişimi etkinleştirilemedi. {settingsMenuName} ayarlarından tekrar deneyin, ardından {productName} uygulamasını tamamen kapatıp yeniden başlatın." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Wayland'da sistem genelinde klavye ve fare kısayolları için giriş erişimi gereklidir. Giriş erişimini değiştirdikten sonra, kısayolların kullanabilmesi için {productName} uygulamasını tamamen kapatıp yeniden başlatın." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Giriş erişimi değiştirildi. Sistem genelindeki kısayolların kullanabilmesi için {productName} uygulamasını tamamen kapatıp yeniden başlatın." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Giriş ve çıkış" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Geçersiz yetkilendirme isteği" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Geçersiz bot izinleri." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Geçersiz veya süresi dolmuş kod. Tekrar deneyin." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Geçersiz redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Paylaşım sırasında davet bağlantısı gizlenir:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Paylaşım sırasında davet bağlantısı maskelendi" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "{displayName} adlı kişiye atla" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "{labelText} kanalına git" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Mevcut aramaya atla" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Bağlantılı kanala git" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Bağlantılı mesaja git" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "{authorNickname} kullanıcısından gelen mesaja git" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "{labelText} içindeki mesaja git" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Bunları güvende tutun. Yeniden oluşturmak mevcut entegrasyonları bozacaktır." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Tuşu bıraktıktan sonra kısa bir süre iletime devam et." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Klavye" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Klavye erişimi başlatılamadı. Kısayollar yalnızca {productName} odaklanmışken çalışır." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Topluluğun kendi kendini denetlemesine izin ver" msgid "Let the member hear others in this community again" msgstr "Üyenin bu toplulukta tekrar başkalarını duymasına izin verildi" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "{productName} uygulamasının klavyenizi doğrudan okumasını sağlar. Sisteminiz buna zaten izin veriyor." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Masaüstü uygulamasını yeniden başlattıktan sonra özel CSS'in şeffaf pencere bölgelerini göstermesine izin ver." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Bağlantı stili" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Bağlantı kullanılamıyor" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Görünüm ve his" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "{0} kullanıyor gibisiniz. Bunun üzerine bir de {PRODUCT_NAME} ses işlemesi eklemek ses dalgalanmasına veya bozulmaya neden olabilir. Doğrudan giriş, sesinizi olduğu gibi iletir." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Mesaj düzeni" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "mesaj bağlantısı" @@ -21332,7 +21416,7 @@ msgstr "Mikrofon" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Mikrofon testi" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Çeşitli" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "client_id eksik" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Diğer seçenekler" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Daha fazla kısayol seçeneği" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "{sendMessagesPermission} izni yok" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Erişim yok" @@ -22984,7 +23068,7 @@ msgstr "Eşleşme bulunamadı" msgid "No mentions yet" msgstr "Henüz bahsetme yok" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Mikrofon bulunamadı" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Ayar bulunamadı" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Kısayol ayarlanmadı" @@ -23358,6 +23442,11 @@ msgstr "Tam sayı değil" msgid "Not accepted" msgstr "Kabul edilmedi" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Atanmadı" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "{host} adresini tarayıcınızda açın." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "{labelText} sayfasını aç" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Ayarlarda {labelText} bölümünü aç" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Sayfa aşağı" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "sayfa bağlantısı" @@ -25611,11 +25700,11 @@ msgstr "Durum" msgid "Press {shortcut} to react" msgstr "Tepki vermek için {shortcut} tuşuna basın" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Atamak için bir tuşa, fare düğmesine veya oyun kumandası düğmesine basın. İptal etmek için Escape'e basın." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Kısayol tuşlarına basın…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Bas konuş tuşu" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Bas konuş" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Bas konuş (sınırlı)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Bas konuş için henüz sistem genelinde bir tuş atanmadı. Atamak için Ayarlar > Uygulamalar > {productName} bölümüne gidin." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Bas konuş için henüz sistem genelinde bir tuş atanmadı. Atamak için Sistem Ayarları > Klavye > Kısayollar > {productName} bölümüne gidin." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Bas konuş için henüz sistem genelinde bir tuş atanmadı. Atamak için masaüstünüzün klavye kısayolu ayarlarını açın." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Bas konuş özelliği doğrudan girişte yok sayılır. Kullanmak için odaklanmış sese veya özel seçeneğine geçin." @@ -26293,21 +26397,51 @@ msgstr "Bas konuş özelliği doğrudan girişte yok sayılır. Kullanmak için msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Bas konuş açık. Konuşmak için {pushToTalkHint} tuşunu basılı tutun." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Bas konuş açık. Konuşmak için bas konuş tuşunuzu basılı tutun." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Masaüstünüz sistem genelinde kısayolları ayarlamadığı için bas konuş yalnızca {productName} odaklanmışken çalışır." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "{permissionName} etkinleştirilene kadar bas-konuş özelliği yalnızca {productName} odaklıyken çalışır. İzni değiştirdikten sonra, sistem genelindeki kısayol tuşlarının kullanabilmesi için {productName} uygulamasını tamamen kapatıp yeniden başlatın." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Sistem genelinde kısayolları ayarlayana kadar bas konuş yalnızca {productName} odaklanmışken çalışır." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Bas konuş özelliği yalnızca tarayıcı sekmesi odaklıyken çalışır." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Bas konuş, {productName} uygulamasında ayarladığınız tuşu kullanacak. Önce bir tuş ayarlayın, aksi takdirde mikrofonunuz ses etkinliğini kullanır." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Sağırlaştırma açıkken bas konuş mikrofonunuzun sesini açmaz. Konuşmak için sağırlaştırmayı kaldırın." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Bas konuş, {productName} dışında yalnızca Hyprland yapılandırmanızda bir tuşa atandıktan sonra çalışır." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Bas konuş, {productName} dışında yalnızca Hyprland yapılandırmanızda bir tuşa atandıktan sonra çalışır. Hyprland sürümünüz için satırı ekleyin ve KEY yerine istediğiniz tuşu yazın." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Ham gömülü içerik JSON'u" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Yerleşik kısayolu yeniden etkinleştir" @@ -26565,13 +26699,6 @@ msgstr "Son bahsedilenler" msgid "Recent searches" msgstr "Son aramalar" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Tekrar kontrol et" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Önerilen" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Önerilen. Mikrofonunuzu temizleyerek net konuşma sağlar." @@ -26600,6 +26727,11 @@ msgstr "Yeniden bağlan" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Yeniden bağlanma başarılı. Önceki akışınıza devam etmek isterseniz lütfen tekrar bir ekran seçin." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Masaüstünüze yeniden bağlanılıyor…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Yeniden bağlanılıyor…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Kısayolu kaydet" @@ -26851,7 +26983,7 @@ msgstr "Göreceli" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Bırakma gecikmesi" @@ -27739,13 +27871,13 @@ msgstr "Tekrar gönderildi" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Sıfırla" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "\"{label}\" kısayolunu yerleşik ayarlara sıfırla" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Gerçek değerlere sıfırla" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Yerleşik ayarlara sıfırla" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Yeni cihazınızda oturum açmak istediğiniz hesabı seçin." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Ses işleme modunu seçin" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Ham mikrofon sesini işlem yapmadan gönderir." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Sesinizi olduğu gibi gönderir. Harici ses yazılımı kullanıyorsanız en iyisidir." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "{preset} ön ayarı tarafından ayarlandı. Buradaki bir değeri seçmek Özel'e geçer." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Özel kısayol ayarla" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "\"{label}\" için özel kısayol ayarla" @@ -30066,6 +30198,11 @@ msgstr "Ses bölgesi {region} olarak ayarlandı" msgid "Set the voice region to automatic" msgstr "Ses bölgesi otomatik olarak ayarlandı" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Ayarla" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Ayarlar" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "ayarlar bağlantısı" @@ -30330,8 +30467,8 @@ msgstr "Kısa kodlar" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Rahat düzene geç" msgid "Switch to dense layout" msgstr "Yoğun düzene geç" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Doğrudan girişe geç" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Sistem tarafından yönetilen kurulum" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Sistem genelindeki kısayollar" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Sistem genelinde kısayollar ayarlanamadı." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Sistem genelinde: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Sistem genelinde: Atanmadı" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Aradığınız sayfa mevcut değil veya taşınmış." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Sağlanan redirect_uri bu uygulama için kayıtlı değil." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Sağlanan redirect_uri HTTP veya HTTPS kullanmalıdır." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Bu animasyon kırpılamayacak kadar uzun veya büyük. Daha kısa veya daha küçük bir dosya deneyin." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Bu uygulama" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Bu ekin süresi doldu" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Bu bot herkese açık değil ve diğer kullanıcılar tarafından eklenemez." @@ -34081,6 +34233,16 @@ msgstr "Bu sohbet artık kullanılamıyor." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Bu masaüstü sürümü, güncellemeleri {productName} içinde yükleyemiyor. Bunun yerine en son masaüstü kurulum dosyasını indirin." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Bu masaüstü sistem genelinde kısayolları desteklemiyor. Bas konuş yalnızca {productName} odaklanmışken çalışır." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Bu masaüstü sistem genelinde kısayolları desteklemiyor. Kısayollar {productName} odaklanmışken çalışmaya devam eder." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Bu son geçiş anahtarınız ve geçiş anahtarları ikinci faktörünü msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Bu son geçiş anahtarınız. Bunu sildikten sonra ikinci faktör olarak geçiş anahtarı kullanamazsınız." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Bu tuş {productName} odaklanmışken çalışır. Sistem genelindeki tuşu masaüstünüz belirler, {settingsTabName} > {sectionName} bölümüne bakın." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Bu bağlantı kanalı yetişkinlere yönelik içerik olarak işaretlenmiştir ve bazı kullanıcılar için uygunsuz olabilecek materyaller içerebilir." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Bu bağlantı hesabınız için kullanılamıyor." @@ -34778,6 +34945,21 @@ msgstr "Başlık çubuğu" msgid "Title bar platform" msgstr "Başlık çubuğu platformu" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Tuşları değiştirmek için Ayarlar > Uygulamalar > {productName} bölümüne gidin." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Tuşları değiştirmek için Sistem Ayarları > Klavye > Kısayollar > {productName} bölümüne gidin." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Tuşları değiştirmek için masaüstünüzün klavye kısayolu ayarlarını açın." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Webhook oluşturmak için <0>{channelWebhooksSettingsPath} bölümünü açın. Mevcut tüm webhook'ları buradan düzenleyebilir ve yönetebilirsiniz." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Bilinmiyor" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Bilinmeyen uygulama, süresi dolmuş oturum veya geçersiz yetkilendirme isteği." @@ -36410,6 +36592,11 @@ msgstr "4K/60fps'ye kadar" msgid "Up to 720p at 30 FPS" msgstr "30 FPS ile 720p'ye kadar" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Wayland'da sistem genelinde kısayolları kullanmak için {productName} uygulamasını güncelleyin." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Uygulama genelinde koyu temayı kullan" msgid "Use default" msgstr "Varsayılanı kullan" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Doğrudan giriş aygıtı erişimini kullan" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Sesli sohbete katılanlar" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Ses işleme" @@ -38264,7 +38456,7 @@ msgstr "Sağırlaştırma kaldırıldı" msgid "Voice unmute" msgstr "Mikrofon açıldı" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Bu tarayıcıda sesli görüşmeler sisteminizin çıkış cihazını kullanır. Sistem ayarlarınızdan değiştirebilirsiniz." @@ -38278,7 +38470,7 @@ msgstr "İptal edildi" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Seçilen bir veya daha fazla kanalda yavaş modun süresinin dolması be msgid "Waiting for you to..." msgstr "Seni bekliyorum..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Masaüstünüz bekleniyor…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Hangi sohbetler" msgid "Which rule was broken?" msgstr "Hangi kural ihlal edildi?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Bas konuş açıkken kısayolunuz mikrofonunuzu kontrol eder, bu yüzden sesi kapatma düğmesi devre dışı olur." @@ -39094,6 +39291,11 @@ msgstr "Bu izin olmadan bas konuş özelliği gerekir." msgid "Wordmark" msgstr "Yazı logosu" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "{productName} odaklanmışken çalışır" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "{PREMIUM_PRODUCT_NAME} aboneliğiniz varken etiketinizi değiştirdiniz. Yenilenmediği takdirde, {PREMIUM_PRODUCT_NAME} aboneliğiniz sona erdiğinde etiketiniz rastgele yeniden belirlenecektir." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Bu mesajın gönderildiği kanala erişiminiz yok." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "{premiumProductName} aboneliğinizin süresi doldu. Tüm {premiumProductName} avantajlarını kaybettiniz. Erişimi yeniden kazanmak için aboneliğinizi yeniden etkinleştirin." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "{productName} oturumunuzun süresi doldu. Bu uygulamaya yetki vermeye devam etmek için tekrar oturum açın." @@ -40144,6 +40346,16 @@ msgstr "Topluluğunuzun Keşfet'te listelenebilmesi için en az {minMemberCount, msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Kredi kartınız kontrol edildi. Kart geçerliyse, yetişkinlere yönelik içerik kısa süre içinde açılacaktır. Bu sekmeyi kapatıp uygulamaya dönebilirsiniz." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Masaüstünüz sistem genelinde kısayolları ayarlamadı." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Masaüstünüz, {productName} odaklanmamışken bile bu kısayolları çalıştırır." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/uk/messages.po b/fluxer_app/src/features/i18n/locales/uk/messages.po index 76845b3ac..d5390f5bf 100644 --- a/fluxer_app/src/features/i18n/locales/uk/messages.po +++ b/fluxer_app/src/features/i18n/locales/uk/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# канал підписаний} few {# кана msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {Знайдено # спільноту} few {Знайдено # спільноти} many {Знайдено # спільнот} other {Знайдено # спільноти}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# спільнота тимчасово недоступна через несправність потокового конденсатора.} few {# спільноти тимчасово недоступні через несправність потокового конденсатора.} many {# спільнот тимчасово недоступні через несправність потокового конденсатора.} other {# спільнот тимчасово недоступні через несправність потокового конденсатора.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} не надсилає push-сповіщення на ва msgid "{productName} Bug Hunter" msgstr "Мисливець за помилками {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} може попросити ваше робоче середовище виконувати його комбінації клавіш, коли {productName} не у фокусі. Робоче середовище попросить підтвердження і дозволить вибрати клавіші." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} більше не може зчитувати вашу клавіатуру, тому відстежує комбінації клавіш через X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} більше не може зчитувати вашу клавіатуру, тому натомість використовуються комбінації клавіш вашого робочого середовища." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} більше не може зчитувати вашу клавіатуру. Комбінації клавіш працюють лише тоді, коли {productName} у фокусі." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} може зареєструвати свої комбінації клавіш у Hyprland, щоб вони працювали, коли {productName} не у фокусі. Після цього призначте клавіші в конфігурації Hyprland." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "Щоб відобразити ваші пристрої, застосунку {productName} потрібен доступ до камери." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "Щоб переглянути список пристроїв, застосунку {productName} потрібен доступ до мікрофона." @@ -1837,6 +1862,11 @@ msgstr "{productName} тепер доступний на {host}. Встанов msgid "{productName} Partner" msgstr "Партнер {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} зареєстрував свої комбінації клавіш у Hyprland. Призначте їм клавіші в конфігурації Hyprland, щоб використовувати їх, коли {productName} не у фокусі." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Профіль, що виділяється" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Потрібен redirect_uri, якщо область дії bot не єдина." @@ -2847,7 +2877,7 @@ msgstr "Лише в усіх відкритих приватних чатах" msgid "Across every community you're in" msgstr "У всіх спільнотах, у яких ви є" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Дія" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Поширення інформації про активність" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Поріг активності" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Додайте ще один рівень безпеки для свого облікового запису" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Додати ще одну власну комбінацію клавіш" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Адреса" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Налаштуйте кожну опцію самостійно: приглушення шуму, ехокомпенсація та підсилення." @@ -3696,11 +3726,11 @@ msgstr "Дозволити згадки {everyoneMention} та {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Дозвольте {PRODUCT_NAME} доступ до камери в «{MACOS_SYSTEM_SETTINGS_NAME}» → «{MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME}» → «{MACOS_CAMERA_PERMISSION_NAME}»." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Дозвольте {PRODUCT_NAME} доступ до мікрофона в «{MACOS_SYSTEM_SETTINGS_NAME}» → «{MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME}» → «{MACOS_MICROPHONE_PERMISSION_NAME}»." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Дозвольте {PRODUCT_NAME} доступ до мікрофона. Перевірте адресний рядок або налаштування свого браузера." @@ -3803,7 +3833,7 @@ msgstr "Дозволити учасникам без дозволу «{readMessa msgid "Allow mentions for this role" msgstr "Дозволити згадки для цієї ролі" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Дозволити мікрофон" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Не вдалося авторизуватися" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Не вдалося авторизуватися. Спробуйте ще раз." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Автоматично визначати мову" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Автоматично встановлювати поріг активності" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Дозволи бота" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Дозволи бота не можуть бути від'ємними." @@ -6869,6 +6899,11 @@ msgstr "Змінити псевдонім у групі" msgid "Change icon" msgstr "Змінити іконку" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Змінити в налаштуваннях системи" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Доступ до каналу" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Доступ до каналу заборонено" @@ -7294,7 +7329,7 @@ msgstr "Ідентифікатор каналу скопійовано в буф #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "посилання на канал" @@ -7462,6 +7497,11 @@ msgstr "поле введення чату" msgid "Chat shortcuts" msgstr "Комбінації клавіш у чаті" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Перевірити знову" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Виберіть вікно в системному діалозі" msgid "Choose an account" msgstr "Виберіть обліковий запис" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Оберіть дію." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Виберіть фон" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Виберіть guild_id або channel_id, але не обидва." @@ -7990,7 +8030,7 @@ msgstr "Очищає звук вашого мікрофона для чітко #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Натисніть, щоб відкрити палітру кольор msgid "Click to react" msgstr "Натисніть, щоб відреагувати" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Натисніть, щоб записати" @@ -9110,7 +9150,7 @@ msgstr "Підтвердження" msgid "Confirmation or double-click for community voice joins" msgstr "Підтвердження або подвійне клацання для приєднання до голосового каналу спільноти" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Конфліктує з {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Підключіть камеру або віртуальний прис msgid "Connect a display, then try again." msgstr "Підключіть дисплей, а потім спробуйте ще раз." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Підключіть мікрофон і спробуйте ще раз." @@ -9700,6 +9740,21 @@ msgstr "Копіювати посилання-запрошення" msgid "Copy invite URL" msgstr "Копіювати посилання-запрошення" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Копіювати рядки" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Копіювати рядки для hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Копіювати рядки для hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Власне джерело" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Вибірково" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Дія для власної комбінації клавіш" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Параметри власної комбінації клавіш" @@ -11466,7 +11521,7 @@ msgstr "Стандартні комбінації клавіш" msgid "Default translator" msgstr "Перекладач за замовчуванням" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "За замовчуванням:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Видалити збережені медіа" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Видалити комбінацію клавіш" @@ -12102,13 +12157,13 @@ msgstr "Стан розробника" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Інструменти розробника" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Інструменти розробника доступні зі значка персоналу в заголовку каналу." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Вимкнути преміум-переваги бекенду" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Вимкнути вбудовану комбінацію клавіш" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Вимкнути спливаючі вікна «картинка в картинці» для демонстрації екрана?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Вимкнути комбінацію клавіш" @@ -12888,7 +12943,7 @@ msgstr "Зробити внесок замість цього" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Кнопка завантаження" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Завантажити десктопний застосунок" @@ -13204,6 +13259,11 @@ msgstr "Нідерландська" msgid "e.g., YubiKey, iPhone, work computer" msgstr "наприклад, YubiKey, iPhone, робочий комп'ютер" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Кожна клавіша може запускати лише одну загальносистемну комбінацію клавіш." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Редагувати посилання-запрошення" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Змінити комбінацію клавіш" @@ -13425,7 +13485,7 @@ msgstr "Форма редагування збереженого медіафа msgid "Edit search engine" msgstr "Редагувати пошукову систему" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Редагувати комбінацію клавіш" @@ -13753,20 +13813,16 @@ msgstr "Порожнє редагування" msgid "Empty favorites" msgstr "Порожній список вибраного" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Увімкнути" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Увімкнути дозвіл «{permissionName}»" @@ -13876,11 +13932,6 @@ msgstr "Увімкнути апаратне прискорення та пере msgid "Enable hardware acceleration?" msgstr "Увімкнути апаратне прискорення?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Увімкніть доступ до введення, щоб використовувати системні комбінації клавіш у Wayland. Після зміни доступу до введення повністю вийдіть і перезапустіть {productName}, щоб комбінації клавіш могли його використовувати." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Увімкнути Shift для розгортання" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Увімкнути комбінацію клавіш" @@ -14357,7 +14408,7 @@ msgstr "Весь системний звук" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Звук входу" @@ -14467,7 +14518,7 @@ msgstr "Вечірній час" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Вирівнює гучність мікрофона, щоб ви не були занадто тихими." @@ -15682,6 +15733,26 @@ msgstr "Тільки для британців..." msgid "For me" msgstr "Для мене" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Для функції «Натисни і вимкни мікрофон» використовуйте одну клавішу без Ctrl, Alt або Shift. Якщо клавіш кілька, спочатку відпускайте основну, інакше мікрофон може лишитися вимкненим." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Для функції «Натисни і говори» використовуйте одну клавішу без Ctrl, Alt або Shift. Якщо клавіш кілька, спочатку відпускайте основну, інакше мікрофон може лишитися ввімкненим." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Для цієї комбінації клавіш використовуйте одну клавішу без Ctrl, Alt або Shift. Якщо клавіш кілька, спочатку відпускайте основну, інакше комбінація може лишитися активною." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Для пріоритету голосової активності використовуйте одну клавішу без Ctrl, Alt або Shift. Якщо клавіш кілька, спочатку відпускайте основну, інакше пріоритет може лишитися ввімкненим." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Для вашої безпеки підтвердьте, що це ви, щоб продовжити." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Глобальний профіль" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Глобальна комбінація клавіш" @@ -17434,6 +17505,11 @@ msgstr "Утримуйте Shift і натисніть X, щоб вибрати msgid "Hold to record. Drag up to lock, or release to send." msgstr "Натисніть і утримуйте, щоб записати. Проведіть вгору, щоб заблокувати запис, або відпустіть, щоб надіслати." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Утримування Shift, Ctrl або Alt блокує цю комбінацію. Додайте альтернативні комбінації в налаштуваннях системи." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Ура! Сповіщення ввімкнено" msgid "Hyperlink" msgstr "Гіперпосилання" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "У Hyprland немає вікна для налаштування комбінацій клавіш. Додайте рядки для вашої версії Hyprland до його конфігурації і замініть KEY на потрібну клавішу." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "У Hyprland немає вікна для налаштування комбінацій клавіш. Призначте клавіші в конфігурації Hyprland." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (до Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 і новіші)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Я погоджуюся з <0>Політикою конфіденційності" @@ -17576,7 +17672,7 @@ msgstr "Я передумав(-ла)" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Я розумію" @@ -18000,7 +18096,7 @@ msgstr "У" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "У режимі «Операційна система» наведені вище словники мають рекомендаційний характер. {PRODUCT_NAME} передаватиме ці мовні теги засобу перевірки правопису вашої ОС, якщо він це підтримує. Теги, які він не розпізнає, ігноруються." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "У браузері функція «Натисни і говори» працює лише тоді, коли вкладка {PRODUCT_NAME} активна. Установіть десктопний застосунок для використання функції «Натисни і говори» в усій системі." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Введення" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Не вдалося автоматично ввімкнути доступ до введення. Змініть його вручну, потім повністю вийдіть і перезапустіть {productName}, щоб комбінації клавіш могли його використовувати." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Не вдалося ввімкнути доступ до введення. Спробуйте ще раз у налаштуваннях «{settingsMenuName}», а потім повністю вийдіть і перезапустіть {productName}." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Для системних комбінацій клавіш і миші на Wayland потрібен доступ до введення. Після зміни доступу до введення повністю вийдіть і перезапустіть {productName}, щоб комбінації клавіш могли його використовувати." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Доступ до введення змінено. Повністю вийдіть і перезапустіть {productName}, щоб системні комбінації клавіш могли його використовувати." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Введення та виведення" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Недійсний запит авторизації" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Недійсні дозволи бота." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Недійсний або прострочений код. Спробуйте ще раз." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "Недійсний redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Посилання-запрошення приховано під час демонстрації екрана:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Посилання-запрошення приховано під час демонстрації екрана" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Перейти до {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Перейти до {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Перейти до поточного дзвінка" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Перейти до пов'язаного каналу" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Перейти до пов'язаного повідомлення" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Перейти до повідомлення від {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Перейти до повідомлення в {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Зберігайте їх у безпеці. Повторне генерування призведе до порушення наявних інтеграцій." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Ще трохи передавати звук після відпускання клавіші." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Клавіатура" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Не вдалося запустити доступ до клавіатури. Комбінації клавіш працюють лише тоді, коли {productName} у фокусі." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Дозволити спільноті модерувати себе" msgid "Let the member hear others in this community again" msgstr "Дозволено учаснику знову чути інших у цій спільноті" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Дозволяє {productName} зчитувати клавіатуру напряму. Ваша система вже це дозволяє." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Дозволяє власному CSS показувати прозорі області вікна після перезапуску десктопного застосунку." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Стиль посилань" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Посилання недоступне" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Зовнішній вигляд" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Схоже, ви використовуєте {0}. Накладання обробки {PRODUCT_NAME} може спричинити накачування або спотворення. Прямий вхід передає ваш звук без змін." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Макет повідомлення" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "посилання на повідомлення" @@ -21332,7 +21416,7 @@ msgstr "Мікрофон" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Тест мікрофона" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Різне" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Відсутній client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Більше опцій" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Більше дій для комбінації клавіш" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Немає дозволу {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Немає доступу" @@ -22984,7 +23068,7 @@ msgstr "Нічого не знайдено" msgid "No mentions yet" msgstr "Згадок поки немає" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Мікрофон не знайдено" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Налаштувань не знайдено" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Комбінацію клавіш не встановлено" @@ -23358,6 +23442,11 @@ msgstr "Не ціле число" msgid "Not accepted" msgstr "Не прийнято" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Не призначено" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Відкрийте {host} у браузері." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Відкрити {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Відкрити {labelText} у налаштуваннях" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "посилання на сторінку" @@ -25611,11 +25700,11 @@ msgstr "Присутність" msgid "Press {shortcut} to react" msgstr "Натисніть {shortcut}, щоб відреагувати" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Натисніть клавішу, кнопку миші або геймпада, щоб прив'язати. Escape скасовує." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Натисніть комбінацію клавіш…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Клавіша «Натисни і говори»" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Натисни і говори" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Натисни і говори (обмежено)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Для функції «Натисни і говори» ще не призначено загальносистемну клавішу. Щоб призначити її, перейдіть у Параметри > Програми > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Для функції «Натисни і говори» ще не призначено загальносистемну клавішу. Щоб призначити її, перейдіть у Системні параметри > Клавіатура > Скорочення > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Для функції «Натисни і говори» ще не призначено загальносистемну клавішу. Щоб призначити її, відкрийте налаштування комбінацій клавіш вашого робочого середовища." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Режим «Натисни і говори» не діє, коли вибрано обробку голосу «Прямий вхід». Перейдіть на «Чіткий голос» або «Власна», щоб скористатися ним." @@ -26293,21 +26397,51 @@ msgstr "Режим «Натисни і говори» не діє, коли ви msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Режим «Натисни і говори» увімкнено. Утримуйте {pushToTalkHint}, щоб говорити." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Режим «Натисни і говори» ввімкнено. Утримуйте клавішу «Натисни і говори», щоб говорити." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Функція «Натисни і говори» працює лише тоді, коли {productName} у фокусі, бо ваше робоче середовище не налаштувало загальносистемні комбінації клавіш." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Функція «Натисни і говори» працює, лише коли застосунок «{productName}» активний, доки не буде ввімкнено дозвіл «{permissionName}». Після зміни дозволу повністю закрийте та перезапустіть застосунок «{productName}», щоб системні гарячі клавіші могли його використовувати." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Функція «Натисни і говори» працює лише тоді, коли {productName} у фокусі, доки ви не налаштуєте загальносистемні комбінації клавіш." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Функція «Натисни і говори» працює, лише коли вкладка браузера активна." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Функція «Натисни і говори» використовуватиме клавішу, задану в {productName}. Спочатку задайте її, інакше мікрофон працюватиме за голосовою активністю." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Режим «Натисни і говори» не ввімкне ваш мікрофон, поки у вас вимкнено звук. Увімкніть звук, щоб говорити." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Функція «Натисни і говори» працює поза {productName} лише після того, як ви призначите для неї клавішу в конфігурації Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Функція «Натисни і говори» працює поза {productName} лише після того, як ви призначите для неї клавішу в конфігурації Hyprland. Додайте рядок для вашої версії Hyprland і замініть KEY на потрібну клавішу." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "Необроблений JSON вбудованого вмісту" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Увімкнути вбудовану комбінацію клавіш" @@ -26565,13 +26699,6 @@ msgstr "Недавні згадки" msgid "Recent searches" msgstr "Останні пошуки" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Перевірити ще раз" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Рекомендовано" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Рекомендовано. Очищає звук мікрофона для чіткої мови." @@ -26600,6 +26727,11 @@ msgstr "Підключитися знову" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Повторне підключення успішне. Виберіть екран ще раз, якщо хочете відновити попередню трансляцію." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Повторне підключення до робочого середовища…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Повторне підключення…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Записати комбінацію клавіш" @@ -26851,7 +26983,7 @@ msgstr "Відносний" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Затримка відпускання" @@ -27739,13 +27871,13 @@ msgstr "Надіслано повторно" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Скинути" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Скинути «{label}» до вбудованої" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Скинути до фактичних значень" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Скинути до вбудованої" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Виберіть обліковий запис, за допомогою якого ви хочете увійти на новому пристрої." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Виберіть режим обробки голосу" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Надсилає звук мікрофона без жодної обробки." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Надсилає ваш звук без змін. Найкраще, якщо ви використовуєте зовнішні аудіопрограми." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Встановлено пресетом {preset}. Вибір значення тут перемкне на \"Власні налаштування\"." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Встановити власну комбінацію клавіш" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Установити власну комбінацію клавіш для «{label}»" @@ -30066,6 +30198,11 @@ msgstr "Встановлено голосовий регіон: {region}" msgid "Set the voice region to automatic" msgstr "Встановлено автоматичний вибір голосового регіону" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Налаштувати" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Налаштування" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "посилання на налаштування" @@ -30330,8 +30467,8 @@ msgstr "Шорткоди" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Перейти до зручного вигляду" msgid "Switch to dense layout" msgstr "Перейти до щільного вигляду" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Перейти на прямий вхід" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Системна інсталяція" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Загальносистемні комбінації клавіш" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Не вдалося налаштувати загальносистемні комбінації клавіш." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Загальносистемна: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Загальносистемна: не призначено" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Сторінка, яку ви шукаєте, не існує або була переміщена." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "Наданий redirect_uri не зареєстровано для цього застосунку." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "Наданий redirect_uri має використовувати HTTP або HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Ця анімація занадто довга або занадто велика для обрізання. Спробуйте коротший або менший файл." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Цей застосунок" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Термін дії цього вкладення закінчився" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Цей бот не є публічним, і його не можуть додати інші користувачі." @@ -34081,6 +34233,16 @@ msgstr "Ця розмова більше недоступна." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Ця збірка для комп'ютера не може встановлювати оновлення всередині {productName}. Натомість завантажте найновіший інсталятор для комп'ютера." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Це робоче середовище не підтримує загальносистемні комбінації клавіш. Функція «Натисни і говори» працює лише тоді, коли {productName} у фокусі." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Це робоче середовище не підтримує загальносистемні комбінації клавіш. Комбінації клавіш і далі працюють, коли {productName} у фокусі." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Це ваш останній ключ доступу, а ключі до msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Це ваш останній ключ доступу. Після його видалення ви не зможете використовувати ключ доступу як другий фактор." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Ця клавіша працює, коли {productName} у фокусі. Загальносистемну клавішу задає ваше робоче середовище, у розділі {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Цей канал за посиланням позначено як такий, що містить контент для дорослих, і може відкривати матеріали, які можуть бути неприйнятними для деяких користувачів." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Це посилання недоступне для вашого облікового запису." @@ -34778,6 +34945,21 @@ msgstr "Рядок заголовка" msgid "Title bar platform" msgstr "Платформа рядка заголовка" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Щоб змінити клавіші, перейдіть у Параметри > Програми > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Щоб змінити клавіші, перейдіть у Системні параметри > Клавіатура > Скорочення > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Щоб змінити клавіші, відкрийте налаштування комбінацій клавіш вашого робочого середовища." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Щоб створити вебхук, відкрийте <0>{channelWebhooksSettingsPath}. Тут ви все ще можете редагувати та впорядковувати всі наявні вебхуки." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Невідомо" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Невідомий застосунок, термін дії сеансу закінчився або недійсний запит авторизації." @@ -36410,6 +36592,11 @@ msgstr "До 4K/60 кадр/с" msgid "Up to 720p at 30 FPS" msgstr "До 720p зі швидкістю 30 кадрів/сек" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Оновіть {productName}, щоб використовувати загальносистемні комбінації клавіш у Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Використовувати темну тему в усьому за msgid "Use default" msgstr "Використати типове значення" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Використовувати прямий доступ до пристроїв введення" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Учасники голосового чату" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Обробка голосу" @@ -38264,7 +38456,7 @@ msgstr "Увімкнення звуку в голосовому чаті" msgid "Voice unmute" msgstr "Увімкнення мікрофона" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "У цьому браузері голосові виклики використовують системний пристрій виведення. Змініть його в налаштуваннях системи." @@ -38278,7 +38470,7 @@ msgstr "Анульовано" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Очікування завершення повільного режи msgid "Waiting for you to..." msgstr "Чекаємо, поки ви..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Очікування робочого середовища…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Які розмови" msgid "Which rule was broken?" msgstr "Яке правило було порушено?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Коли ввімкнено режим «Натисни і говори», мікрофоном керує ваша комбінація клавіш, тому кнопка вимкнення мікрофона неактивна." @@ -39094,6 +39291,11 @@ msgstr "Без цього дозволу потрібен режим «Нати msgid "Wordmark" msgstr "Текстовий логотип" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Працює, коли {productName} у фокусі" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Ви змінили свій тег, поки у вас був {PREMIUM_PRODUCT_NAME}. Він стане випадковим, коли термін дії {PREMIUM_PRODUCT_NAME} завершиться, якщо підписку не буде поновлено." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "У вас немає доступу до каналу, куди було надіслано це повідомлення." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Термін дії вашої підписки {premiumProductName} закінчився. Ви втратили всі переваги {premiumProductName}. Поновіть підписку, щоб відновити доступ." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Термін дії вашої сесії {productName} закінчився. Увійдіть знову, щоб продовжити авторизацію цього застосунку." @@ -40144,6 +40346,16 @@ msgstr "Щоб спільноту можна було додати до Discover msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Вашу кредитну картку перевірено. Якщо картка дійсна, доступ до контенту для дорослих незабаром буде розблоковано. Можете закрити цю вкладку та повернутися до застосунку." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Ваше робоче середовище не налаштувало загальносистемні комбінації клавіш." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Ваше робоче середовище виконує ці комбінації клавіш, навіть коли {productName} не у фокусі." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/vi/messages.po b/fluxer_app/src/features/i18n/locales/vi/messages.po index 99e4c2ec3..456361040 100644 --- a/fluxer_app/src/features/i18n/locales/vi/messages.po +++ b/fluxer_app/src/features/i18n/locales/vi/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# kênh khác đang theo dõi} other {# kênh khác msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {# cộng đồng được tìm thấy} other {# cộng đồng được tìm thấy}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# cộng đồng hiện không khả dụng do lỗi bộ biến đổi dòng chảy.} other {# cộng đồng hiện không khả dụng do lỗi bộ biến đổi dòng chảy.}}" @@ -1708,6 +1708,31 @@ msgstr "{productName} sẽ không gửi thông báo đẩy đến thiết bị d msgid "{productName} Bug Hunter" msgstr "Người săn lỗi {productName}" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} có thể yêu cầu môi trường máy tính để bàn chạy phím tắt của ứng dụng khi cửa sổ {productName} không hoạt động. Môi trường máy tính để bàn sẽ yêu cầu bạn xác nhận và cho bạn chọn phím." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} không thể đọc bàn phím của bạn nữa, vì vậy ứng dụng sẽ nhận phím tắt qua X11." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} không thể đọc bàn phím của bạn nữa, vì vậy phím tắt của môi trường máy tính để bàn sẽ được dùng thay thế." + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} không thể đọc bàn phím của bạn nữa. Phím tắt chỉ hoạt động khi cửa sổ {productName} đang hoạt động." + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} có thể đăng ký phím tắt với Hyprland để chúng chạy cả khi cửa sổ {productName} không hoạt động. Sau đó, bạn gán phím trong tệp cấu hình Hyprland." + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} cần quyền truy cập camera để liệt kê thiết bị của bạn." #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} cần quyền truy cập micrô để liệt kê thiết bị của bạn." @@ -1837,6 +1862,11 @@ msgstr "{productName} giờ đã có tại {host}. Hãy cài đặt từ đó." msgid "{productName} Partner" msgstr "Đối tác {productName}" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} đã đăng ký phím tắt với Hyprland. Hãy gán phím cho chúng trong tệp cấu hình Hyprland để dùng khi cửa sổ {productName} không hoạt động." + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "Một hồ sơ nổi bật" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "Cần có redirect_uri khi phạm vi bot không phải là phạm vi duy nhất." @@ -2847,7 +2877,7 @@ msgstr "Chỉ trong tất cả các tin nhắn trực tiếp đang mở" msgid "Across every community you're in" msgstr "Trong tất cả cộng đồng bạn tham gia" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "Thao tác" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "Chia sẻ hoạt động" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "Ngưỡng hoạt động" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "Thêm một lớp bảo mật cho tài khoản của bạn" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "Thêm phím tắt tùy chỉnh khác" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "Địa chỉ" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "Tự điều chỉnh từng cài đặt: khử tiếng ồn, loại bỏ tiếng vọng và độ lợi." @@ -3696,11 +3726,11 @@ msgstr "Cho phép {everyoneMention} và {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "Cho phép {PRODUCT_NAME} truy cập camera của bạn trong {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "Cho phép {PRODUCT_NAME} truy cập micrô của bạn trong {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "Cho phép {PRODUCT_NAME} truy cập micrô của bạn. Kiểm tra thanh địa chỉ hoặc cài đặt trình duyệt của bạn." @@ -3803,7 +3833,7 @@ msgstr "Cho phép thành viên không có quyền {readMessageHistoryPermissionL msgid "Allow mentions for this role" msgstr "Cho phép nhắc đến vai trò này" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "Cho phép truy cập micro" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "Ủy quyền thất bại" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "Ủy quyền thất bại. Vui lòng thử lại." @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "Tự động phát hiện ngôn ngữ" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "Tự động đặt ngưỡng hoạt động" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "Quyền của bot" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "Quyền của bot không thể là số âm." @@ -6869,6 +6899,11 @@ msgstr "Đổi biệt danh nhóm" msgid "Change icon" msgstr "Đổi biểu tượng" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "Thay đổi trong cài đặt hệ thống" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "Quyền truy cập kênh" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "Không thể truy cập kênh" @@ -7294,7 +7329,7 @@ msgstr "Đã sao chép ID kênh vào bảng tạm" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "liên kết kênh" @@ -7462,6 +7497,11 @@ msgstr "ô nhập trò chuyện" msgid "Chat shortcuts" msgstr "Phím tắt trò chuyện" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "Kiểm tra lại" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "Chọn một cửa sổ trong trình chọn của hệ thống" msgid "Choose an account" msgstr "Chọn một tài khoản" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "Chọn một hành động." @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "Chọn hình nền" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "Chọn guild_id hoặc channel_id, không chọn cả hai." @@ -7990,7 +8030,7 @@ msgstr "Xử lý âm thanh micrô để giọng nói rõ ràng hơn." #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "Nhấn để mở bảng chọn màu và chọn màu mới." msgid "Click to react" msgstr "Nhấn để bày tỏ cảm xúc" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "Nhấn để ghi phím tắt" @@ -9110,7 +9150,7 @@ msgstr "Xác nhận" msgid "Confirmation or double-click for community voice joins" msgstr "Xác nhận hoặc nhấp đúp để tham gia kênh thoại cộng đồng" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "Xung đột với {conflictLabel}" @@ -9127,7 +9167,7 @@ msgstr "Kết nối camera hoặc thiết bị ghi hình ảo, rồi thử lại msgid "Connect a display, then try again." msgstr "Kết nối một màn hình, rồi thử lại." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "Kết nối micrô và thử lại." @@ -9700,6 +9740,21 @@ msgstr "Sao chép liên kết mời" msgid "Copy invite URL" msgstr "Sao chép URL mời" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "Sao chép dòng" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "Sao chép dòng cho hyprland.conf" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "Sao chép dòng cho hyprland.lua" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "Tùy chỉnh" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "Lựa chọn tùy chỉnh" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "Thao tác phím tắt tùy chỉnh" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "Tùy chọn phím tắt tùy chỉnh" @@ -11466,7 +11521,7 @@ msgstr "Phím tắt mặc định" msgid "Default translator" msgstr "Trình dịch mặc định" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "Mặc định:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "Xóa media đã lưu" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "Xóa phím tắt" @@ -12102,13 +12157,13 @@ msgstr "Trạng thái nhà phát triển" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "Công cụ nhà phát triển" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "Công cụ nhà phát triển có sẵn từ huy hiệu nhân viên trong tiêu đề kênh." @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "Tắt đặc quyền premium của backend" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "Tắt phím tắt tích hợp" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "Tắt cửa sổ hình trong hình khi chia sẻ màn hình?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "Tắt phím tắt" @@ -12888,7 +12943,7 @@ msgstr "Ủng hộ thay thế" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "Nút tải xuống" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "Tải ứng dụng máy tính" @@ -13204,6 +13259,11 @@ msgstr "Tiếng Hà Lan" msgid "e.g., YubiKey, iPhone, work computer" msgstr "Ví dụ: YubiKey, iPhone, máy tính làm việc" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "Mỗi phím chỉ có thể chạy một phím tắt toàn hệ thống." + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "Chỉnh sửa liên kết mời" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "Chỉnh sửa phím tắt" @@ -13425,7 +13485,7 @@ msgstr "Biểu mẫu chỉnh sửa media đã lưu" msgid "Edit search engine" msgstr "Chỉnh sửa công cụ tìm kiếm" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "Chỉnh sửa phím tắt" @@ -13753,20 +13813,16 @@ msgstr "Chỉnh sửa trống" msgid "Empty favorites" msgstr "Mục yêu thích trống" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "Bật" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "Bật quyền {permissionName}" @@ -13876,11 +13932,6 @@ msgstr "Bật tăng tốc phần cứng và khởi động lại" msgid "Enable hardware acceleration?" msgstr "Bật tăng tốc phần cứng?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Bật quyền truy cập đầu vào để sử dụng các phím tắt toàn hệ thống trên Wayland. Sau khi thay đổi quyền truy cập đầu vào, hãy thoát hoàn toàn và khởi động lại {productName} để các phím tắt có thể dùng quyền này." - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "Bật Shift để mở rộng" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "Bật phím tắt" @@ -14357,7 +14408,7 @@ msgstr "Toàn bộ âm thanh hệ thống" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "Âm thanh vào phòng" @@ -14467,7 +14518,7 @@ msgstr "Buổi tối" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "Làm đều âm lượng mic của bạn để không bị quá nhỏ." @@ -15682,6 +15733,26 @@ msgstr "Chỉ dành cho người Anh..." msgid "For me" msgstr "Dành cho tôi" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "Với Nhấn để tắt tiếng, hãy dùng một phím duy nhất, không kèm Ctrl, Alt hoặc Shift. Nếu dùng tổ hợp phím, hãy nhả phím chính trước, nếu không micrô có thể vẫn bị tắt tiếng." + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "Với Nhấn để nói, hãy dùng một phím duy nhất, không kèm Ctrl, Alt hoặc Shift. Nếu dùng tổ hợp phím, hãy nhả phím chính trước, nếu không micrô có thể vẫn bật." + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "Với phím tắt này, hãy dùng một phím duy nhất, không kèm Ctrl, Alt hoặc Shift. Nếu dùng tổ hợp phím, hãy nhả phím chính trước, nếu không phím tắt có thể vẫn hoạt động." + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "Với Ưu tiên hoạt động giọng nói, hãy dùng một phím duy nhất, không kèm Ctrl, Alt hoặc Shift. Nếu dùng tổ hợp phím, hãy nhả phím chính trước, nếu không chế độ ưu tiên có thể vẫn bật." + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "Để bảo mật, vui lòng xác nhận danh tính của bạn để tiếp tục." @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "Hồ sơ chung" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "Phím tắt toàn cục" @@ -17434,6 +17505,11 @@ msgstr "Giữ phím Shift và nhấn X để chọn ứng dụng. Nhấn {select msgid "Hold to record. Drag up to lock, or release to send." msgstr "Giữ để ghi âm. Kéo lên để khóa, hoặc thả ra để gửi." +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "Giữ Shift, Ctrl hoặc Alt sẽ chặn phím tắt này. Thêm các phím tắt thay thế trong cài đặt hệ thống của bạn." + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "Hoan hô! Đã bật thông báo" msgid "Hyperlink" msgstr "Liên kết" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland không có hộp thoại phím tắt. Thêm các dòng cho phiên bản Hyprland của bạn vào tệp cấu hình Hyprland và thay thế KEY bằng phím bạn muốn." + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland không có hộp thoại phím tắt. Hãy gán phím trong tệp cấu hình Hyprland của bạn." + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf (trước Hyprland 0.55)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua (Hyprland 0.55 trở lên)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "Tôi đồng ý với <0>Chính sách quyền riêng tư" @@ -17576,7 +17672,7 @@ msgstr "Tôi đã đổi ý" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "Tôi đã hiểu" @@ -18000,7 +18096,7 @@ msgstr "Trong" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "Ở chế độ \"Hệ điều hành\", các từ điển được liệt kê ở trên chỉ mang tính chất gợi ý. {PRODUCT_NAME} sẽ chuyển các thẻ ngôn ngữ này đến trình kiểm tra chính tả của hệ điều hành nếu được hỗ trợ. Các thẻ không được nhận dạng sẽ bị bỏ qua." -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "Trong trình duyệt, tính năng nhấn để nói chỉ hoạt động khi tab {PRODUCT_NAME} đang hoạt động. Hãy cài đặt ứng dụng máy tính để dùng nhấn để nói trên toàn hệ thống." @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "Nhập liệu" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "Không thể tự động bật quyền truy cập đầu vào. Hãy thay đổi thủ công, sau đó thoát hoàn toàn và khởi động lại {productName} để các phím tắt có thể sử dụng quyền này." - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "Không thể bật quyền truy cập đầu vào. Thử lại từ cài đặt {settingsMenuName}, sau đó thoát hoàn toàn và khởi động lại {productName}." - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "Cần có quyền truy cập đầu vào để sử dụng phím tắt toàn hệ thống trên Wayland. Sau khi thay đổi quyền truy cập đầu vào, hãy thoát hoàn toàn và khởi động lại {productName} để các phím tắt có thể sử dụng quyền này." - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "Quyền truy cập đầu vào đã thay đổi. Hãy thoát hoàn toàn và khởi động lại {productName} để các phím tắt toàn hệ thống có thể sử dụng quyền này." - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "Đầu vào và đầu ra" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "Yêu cầu ủy quyền không hợp lệ" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "Quyền của bot không hợp lệ." @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "Mã không hợp lệ hoặc đã hết hạn. Hãy thử lại." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "redirect_uri không hợp lệ" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "Liên kết mời bị ẩn khi đang chia sẻ:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "Đã ẩn liên kết mời khi đang chia sẻ" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "Chuyển đến {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "Chuyển đến {labelText}" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "Chuyển đến cuộc gọi hiện tại" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "Chuyển đến kênh được liên kết" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "Chuyển đến tin nhắn được liên kết" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "Chuyển đến tin nhắn từ {authorNickname}" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "Chuyển đến tin nhắn trong {labelText}" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "Hãy bảo mật những thông tin này. Việc tạo lại sẽ làm hỏng các tích hợp hiện có." #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "Tiếp tục truyền trong một khoảng thời gian ngắn sau khi nhả nút." @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "Bàn phím" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "Không thể bắt đầu truy cập bàn phím. Phím tắt chỉ hoạt động khi cửa sổ {productName} đang hoạt động." + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "Để cộng đồng tự kiểm duyệt" msgid "Let the member hear others in this community again" msgstr "Đã cho phép thành viên nghe lại người khác trong cộng đồng này" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "Cho phép {productName} đọc trực tiếp bàn phím của bạn. Hệ thống của bạn đã cho phép điều này." + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "Cho phép CSS tùy chỉnh hiển thị các vùng cửa sổ trong suốt sau khi khởi động lại ứng dụng trên máy tính." @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "Kiểu liên kết" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "Liên kết không khả dụng" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "Giao diện người dùng" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "Có vẻ như bạn đang dùng {0}. Việc xếp chồng xử lý của {PRODUCT_NAME} lên trên có thể gây ra hiện tượng bơm tiếng hoặc méo tiếng. Đầu vào trực tiếp sẽ truyền âm thanh của bạn mà không bị thay đổi." @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "Bố cục tin nhắn" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "liên kết tin nhắn" @@ -21332,7 +21416,7 @@ msgstr "Mic" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "Kiểm tra mic" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "Khác" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "Thiếu client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "Tùy chọn khác" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "Thêm tùy chọn phím tắt" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "Không có quyền {sendMessagesPermission}" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "Không có quyền truy cập" @@ -22984,7 +23068,7 @@ msgstr "Không tìm thấy kết quả nào" msgid "No mentions yet" msgstr "Chưa có lượt nhắc đến nào" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "Không tìm thấy micrô" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "Không tìm thấy cài đặt nào" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "Chưa đặt phím tắt" @@ -23358,6 +23442,11 @@ msgstr "Không phải là số nguyên" msgid "Not accepted" msgstr "Không chấp nhận" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "Chưa được gán" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "Mở {host} trên trình duyệt của bạn." #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "Mở {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "Mở {labelText} trong cài đặt" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "liên kết trang" @@ -25611,11 +25700,11 @@ msgstr "Trạng thái hoạt động" msgid "Press {shortcut} to react" msgstr "Nhấn {shortcut} để bày tỏ cảm xúc" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "Nhấn phím, nút chuột hoặc nút tay cầm chơi game để gán. Nhấn Escape để hủy." -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "Nhấn một phím tắt…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "Phím nhấn để nói" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "Nhấn để nói" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "Nhấn để nói (giới hạn)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "Nhấn để nói chưa có phím toàn hệ thống. Để đặt phím, hãy vào Cài đặt > Ứng dụng > {productName}." + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Nhấn để nói chưa có phím toàn hệ thống. Để đặt phím, hãy vào Cài đặt hệ thống > Bàn phím > Phím tắt > {productName}." + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "Nhấn để nói chưa có phím toàn hệ thống. Để đặt phím, hãy mở phần cài đặt phím tắt bàn phím của môi trường máy tính để bàn." + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "Nhấn để nói sẽ bị bỏ qua ở chế độ đầu vào trực tiếp. Chuyển sang giọng nói tập trung hoặc tùy chỉnh để sử dụng." @@ -26293,21 +26397,51 @@ msgstr "Nhấn để nói sẽ bị bỏ qua ở chế độ đầu vào trực msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "Đã bật chế độ nhấn để nói. Giữ {pushToTalkHint} để nói." +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "Chế độ nhấn để nói đã bật. Nhấn và giữ phím nhấn để nói." + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "Nhấn để nói chỉ hoạt động khi cửa sổ {productName} đang hoạt động vì môi trường máy tính để bàn chưa thiết lập phím tắt toàn hệ thống." + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "Nhấn để nói chỉ hoạt động khi cửa sổ {productName} đang hoạt động cho đến khi {permissionName} được bật. Sau khi thay đổi quyền, hãy thoát hoàn toàn và khởi động lại {productName} để các phím tắt toàn hệ thống có thể dùng quyền này." +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "Nhấn để nói chỉ hoạt động khi cửa sổ {productName} đang hoạt động cho đến khi bạn thiết lập phím tắt toàn hệ thống." + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "Nhấn để nói chỉ hoạt động khi tab trình duyệt đang hoạt động." +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "Nhấn để nói sẽ dùng phím bạn đặt trong {productName}. Hãy đặt phím trước, nếu không micrô sẽ dùng chế độ hoạt động thoại." + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "Tính năng nhấn để nói sẽ không bật micrô của bạn khi bạn đang tắt tiếng nghe. Hãy bật lại tiếng nghe để có thể nói." +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "Nhấn để nói chỉ hoạt động bên ngoài {productName} sau khi bạn gán phím trong tệp cấu hình Hyprland." + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "Nhấn để nói chỉ hoạt động bên ngoài {productName} sau khi bạn gán phím trong tệp cấu hình Hyprland. Hãy thêm dòng cho phiên bản Hyprland của bạn và thay KEY bằng phím bạn muốn." + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "JSON thô của nội dung nhúng" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "Bật lại phím tắt tích hợp" @@ -26565,13 +26699,6 @@ msgstr "Lượt nhắc gần đây" msgid "Recent searches" msgstr "Tìm kiếm gần đây" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "Kiểm tra lại" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "Được đề xuất" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "Được đề xuất. Xử lý âm thanh micrô để giọng nói rõ ràng hơn." @@ -26600,6 +26727,11 @@ msgstr "Kết nối lại" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "Đã kết nối lại thành công. Chọn lại màn hình nếu bạn muốn tiếp tục luồng trước đó." +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "Đang kết nối lại với môi trường máy tính để bàn…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "Đang kết nối lại…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "Ghi phím tắt" @@ -26851,7 +26983,7 @@ msgstr "Tương đối" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "Độ trễ nhả nút" @@ -27739,13 +27871,13 @@ msgstr "Đã gửi lại" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "Đặt lại" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "Đặt lại \"{label}\" về phím tắt tích hợp" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "Đặt lại về giá trị thực" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "Đặt lại về phím tắt tích hợp" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "Chọn tài khoản bạn muốn đăng nhập trên thiết bị mới của mình." #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "Chọn chế độ xử lý giọng nói" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "Gửi âm thanh mic thô không qua xử lý." #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "Gửi âm thanh của bạn nguyên bản. Tốt nhất nếu bạn đang dùng phần mềm âm thanh bên ngoài." @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "Được đặt theo cài đặt sẵn {preset}. Chọn một giá trị ở đây sẽ chuyển sang Tùy chỉnh." #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "Đặt phím tắt tùy chỉnh" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "Đặt phím tắt tùy chỉnh cho \"{label}\"" @@ -30066,6 +30198,11 @@ msgstr "Đã đặt khu vực thoại thành {region}" msgid "Set the voice region to automatic" msgstr "Đã đặt khu vực thoại thành tự động" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "Thiết lập" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "Cài đặt" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "liên kết cài đặt" @@ -30330,8 +30467,8 @@ msgstr "Mã ngắn" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "Chuyển sang bố cục thoải mái" msgid "Switch to dense layout" msgstr "Chuyển sang bố cục dày đặc" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "Chuyển sang nhập trực tiếp" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "Cài đặt do hệ thống quản lý" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "Phím tắt toàn hệ thống" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "Không thể thiết lập các phím tắt toàn hệ thống." + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "Toàn hệ thống: {trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "Toàn hệ thống: Chưa gán" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "Trang bạn tìm không tồn tại hoặc đã được di chuyển." #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "redirect_uri được cung cấp chưa đăng ký với ứng dụng này." #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "redirect_uri được cung cấp phải dùng HTTP hoặc HTTPS." @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "Ảnh động này quá dài hoặc quá lớn để cắt. Hãy thử một tệp ngắn hơn hoặc nhỏ hơn." #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "Ứng dụng này" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "Tệp đính kèm này đã hết hạn" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "Bot này không công khai nên người dùng khác không thể thêm." @@ -34081,6 +34233,16 @@ msgstr "Cuộc trò chuyện này không còn khả dụng." msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "Bản dựng máy tính này không thể cài đặt bản cập nhật bên trong {productName}. Thay vào đó, hãy tải trình cài đặt máy tính mới nhất." +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "Môi trường máy tính để bàn này không hỗ trợ phím tắt toàn hệ thống. Nhấn để nói chỉ hoạt động khi cửa sổ {productName} đang hoạt động." + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "Môi trường máy tính để bàn này không hỗ trợ phím tắt toàn hệ thống. Phím tắt vẫn hoạt động khi cửa sổ {productName} đang hoạt động." + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "Đây là khóa truy cập cuối cùng của bạn và khóa truy cập msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "Đây là khóa truy cập cuối cùng của bạn. Khi khóa này bị xóa, bạn sẽ không thể sử dụng khóa truy cập làm yếu tố thứ hai nữa." +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "Phím này hoạt động khi cửa sổ {productName} đang hoạt động. Phím toàn hệ thống do môi trường máy tính để bàn đặt, trong {settingsTabName} > {sectionName}." + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "Kênh liên kết này được đánh dấu là có nội dung người lớn và có thể mở ra nội dung không phù hợp với một số người dùng." #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "Liên kết này không khả dụng cho tài khoản của bạn." @@ -34778,6 +34945,21 @@ msgstr "Thanh tiêu đề" msgid "Title bar platform" msgstr "Nền tảng thanh tiêu đề" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "Để thay đổi các phím, hãy đi tới Cài đặt > Ứng dụng > {productName}." + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "Để thay đổi các phím, hãy vào Cài đặt hệ thống > Bàn phím > Phím tắt > {productName}." + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "Để thay đổi các phím, hãy mở phần cài đặt phím tắt bàn phím của môi trường máy tính để bàn." + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "Để tạo webhook, hãy mở <0>{channelWebhooksSettingsPath}. Bạn vẫn có thể chỉnh sửa và sắp xếp tất cả các webhook hiện có tại đây." @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "Không rõ" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "Ứng dụng không xác định, phiên đã hết hạn hoặc yêu cầu ủy quyền không hợp lệ." @@ -36410,6 +36592,11 @@ msgstr "Lên đến 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "Lên đến 720p ở 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "Cập nhật {productName} để sử dụng các phím tắt toàn hệ thống trên Wayland." + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "Dùng giao diện tối cho toàn ứng dụng" msgid "Use default" msgstr "Dùng mặc định" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "Dùng quyền truy cập trực tiếp vào thiết bị nhập" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "Người tham gia trò chuyện thoại" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "Xử lý giọng nói" @@ -38264,7 +38456,7 @@ msgstr "Bật tiếng nghe" msgid "Voice unmute" msgstr "Bật micrô" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "Trò chuyện thoại sử dụng thiết bị đầu ra của hệ thống trong trình duyệt này. Hãy thay đổi thiết bị trong cài đặt hệ thống." @@ -38278,7 +38470,7 @@ msgstr "Đã hủy" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "Đang chờ chế độ chậm trong một hoặc nhiều kênh đã ch msgid "Waiting for you to..." msgstr "Đang chờ bạn..." +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "Đang chờ môi trường máy tính để bàn…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "Những cuộc trò chuyện nào" msgid "Which rule was broken?" msgstr "Quy tắc nào đã bị vi phạm?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "Khi bật tính năng nhấn để nói, phím tắt sẽ điều khiển micrô của bạn, vì vậy nút tắt tiếng sẽ bị vô hiệu hóa." @@ -39094,6 +39291,11 @@ msgstr "Nếu không có quyền này, bạn phải dùng chế độ nhấn đ msgid "Wordmark" msgstr "Wordmark" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "Hoạt động khi cửa sổ {productName} đang hoạt động" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "Bạn đã đổi thẻ khi đang dùng {PREMIUM_PRODUCT_NAME}. Thẻ sẽ được tạo ngẫu nhiên lại khi gói {PREMIUM_PRODUCT_NAME} của bạn hết hạn, trừ khi gói được gia hạn." #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "Bạn không có quyền truy cập vào kênh chứa tin nhắn này." @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "Gói đăng ký {premiumProductName} của bạn đã hết hạn. Bạn đã mất tất cả các đặc quyền của {premiumProductName}. Vui lòng kích hoạt lại gói đăng ký để lấy lại quyền truy cập." #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "Phiên {productName} của bạn đã hết hạn. Vui lòng đăng nhập lại để tiếp tục ủy quyền cho ứng dụng này." @@ -40144,6 +40346,16 @@ msgstr "Cộng đồng của bạn cần có ít nhất {minMemberCount, plural, msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "Thẻ tín dụng của bạn đã được kiểm tra. Nếu thẻ hợp lệ, nội dung người lớn sẽ sớm được mở khóa. Bạn có thể đóng tab này và quay lại ứng dụng." +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "Môi trường máy tính để bàn chưa thiết lập phím tắt toàn hệ thống." + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "Môi trường máy tính để bàn chạy các phím tắt này ngay cả khi cửa sổ {productName} không hoạt động." + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/zh-CN/messages.po b/fluxer_app/src/features/i18n/locales/zh-CN/messages.po index 23d2694ca..7d060ca1d 100644 --- a/fluxer_app/src/features/i18n/locales/zh-CN/messages.po +++ b/fluxer_app/src/features/i18n/locales/zh-CN/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# 个频道关注此频道} other {# 个频道关 msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, other {# 个社区}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, other {# 个社区因时空穿梭器故障暂时不可用。}}" @@ -1708,6 +1708,31 @@ msgstr "当你在电脑前时,{productName} 会避免向你的移动设备发 msgid "{productName} Bug Hunter" msgstr "{productName} 捉虫猎人" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} 可以请求桌面环境在 {productName} 不在焦点时运行其快捷键。桌面环境会请你确认,并让你选择按键。" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} 无法再读取你的键盘,因此改为通过 X11 监听快捷键。" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} 无法再读取你的键盘,因此改用桌面环境的快捷键。" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} 无法再读取你的键盘。快捷键仅在 {productName} 处于焦点时可用。" + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} 可以向 Hyprland 注册其快捷键,使其在 {productName} 不在焦点时也能运行。之后你需要在 Hyprland 配置中绑定按键。" + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} 需要摄像头权限才能列出你的设备。" #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} 需要麦克风权限才能列出你的设备。" @@ -1837,6 +1862,11 @@ msgstr "{productName} 现已迁移至 {host}。请从新地址安装。" msgid "{productName} Partner" msgstr "{productName} 合作伙伴" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} 已向 Hyprland 注册其快捷键。在 Hyprland 配置中绑定后,即可在 {productName} 不在焦点时使用。" + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "一个脱颖而出的个人资料" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "当 bot 范围不是唯一范围时,需要一个 redirect_uri。" @@ -2847,7 +2877,7 @@ msgstr "仅在所有已打开的私信中" msgid "Across every community you're in" msgstr "在你所在的所有社区中" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "操作" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "活动共享" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "活动阈值" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "为你的账号添加额外的安全保护" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "再添加一个自定义快捷键" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "地址" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "自行调整各项设置:降噪、回声消除和增益。" @@ -3696,11 +3726,11 @@ msgstr "允许提及{everyoneMention}和{hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "请在“{MACOS_SYSTEM_SETTINGS_NAME}”→“{MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME}”→“{MACOS_CAMERA_PERMISSION_NAME}”中允许 {PRODUCT_NAME} 访问你的摄像头。" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "请在“{MACOS_SYSTEM_SETTINGS_NAME}”→“{MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME}”→“{MACOS_MICROPHONE_PERMISSION_NAME}”中允许 {PRODUCT_NAME} 访问你的麦克风。" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "允许 {PRODUCT_NAME} 访问你的麦克风。请检查浏览器地址栏或设置。" @@ -3803,7 +3833,7 @@ msgstr "允许没有 {readMessageHistoryPermissionLabel} 权限的成员查看 msgid "Allow mentions for this role" msgstr "允许提及此身份组" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "允许使用麦克风" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "授权失败" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "授权失败。请重试。" @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "自动检测语言" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "自动设置活动阈值" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "机器人权限" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "机器人权限不能为负数。" @@ -6869,6 +6899,11 @@ msgstr "修改群昵称" msgid "Change icon" msgstr "更改图标" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "在系统设置中更改" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "频道访问" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "无法访问频道" @@ -7294,7 +7329,7 @@ msgstr "频道 ID 已复制到剪贴板" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "频道链接" @@ -7462,6 +7497,11 @@ msgstr "聊天输入框" msgid "Chat shortcuts" msgstr "聊天快捷键" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "重新检查" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "在系统选择器中选择一个窗口" msgid "Choose an account" msgstr "选择一个账号" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "选择一个操作。" @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "选择背景" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "请选择 guild_id 或 channel_id,不能同时选择两者。" @@ -7990,7 +8030,7 @@ msgstr "优化麦克风收音,让语音更清晰。" #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "点击打开取色器,选择新颜色。" msgid "Click to react" msgstr "点击以回应" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "点击录制" @@ -9110,7 +9150,7 @@ msgstr "确认" msgid "Confirmation or double-click for community voice joins" msgstr "加入社区语音频道时需确认或双击" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "与 {conflictLabel} 冲突" @@ -9127,7 +9167,7 @@ msgstr "连接摄像头或虚拟捕捉设备后重试。" msgid "Connect a display, then try again." msgstr "连接显示器后重试。" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "连接麦克风后重试。" @@ -9700,6 +9740,21 @@ msgstr "复制邀请链接" msgid "Copy invite URL" msgstr "复制邀请链接" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "复制配置行" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "复制配置行(hyprland.conf)" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "复制配置行(hyprland.lua)" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "自定义" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "自定义选择" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "自定义快捷键操作" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "自定义快捷键选项" @@ -11466,7 +11521,7 @@ msgstr "默认快捷键" msgid "Default translator" msgstr "默认翻译器" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "默认:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "删除已保存媒体" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "删除快捷键" @@ -12102,13 +12157,13 @@ msgstr "开发者状态" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "开发者工具" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "在频道标题栏中点击员工徽章即可使用开发者工具。" @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "禁用后端高级特权" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "禁用内置快捷键" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "要禁用屏幕共享的画中画浮窗吗?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "禁用快捷键" @@ -12888,7 +12943,7 @@ msgstr "改为捐赠" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "下载按钮" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "下载桌面应用" @@ -13204,6 +13259,11 @@ msgstr "荷兰语" msgid "e.g., YubiKey, iPhone, work computer" msgstr "例如:YubiKey、iPhone、工作电脑" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "每个按键只能触发一个全局快捷键。" + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "编辑邀请链接" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "编辑键盘快捷键" @@ -13425,7 +13485,7 @@ msgstr "编辑已保存媒体表单" msgid "Edit search engine" msgstr "编辑搜索引擎" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "编辑快捷键" @@ -13753,20 +13813,16 @@ msgstr "清空编辑" msgid "Empty favorites" msgstr "收藏夹为空" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "启用" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "启用“{permissionName}”权限" @@ -13876,11 +13932,6 @@ msgstr "启用硬件加速并重启" msgid "Enable hardware acceleration?" msgstr "启用硬件加速?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "启用输入访问权限,以便在 Wayland 上使用系统范围的快捷方式。更改输入访问权限后,请完全退出并重新启动 {productName},以便快捷方式可以使用它。" - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "启用 Shift 键展开" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "启用快捷方式" @@ -14357,7 +14408,7 @@ msgstr "整个系统音频" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "入场音效" @@ -14467,7 +14518,7 @@ msgstr "晚上" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "均衡麦克风音量,避免声音过小。" @@ -15682,6 +15733,26 @@ msgstr "英国用户专属…" msgid "For me" msgstr "为自己" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "按住静音请使用不带 Ctrl、Alt 或 Shift 的单个按键。如果使用组合键,请先松开主键,否则麦克风可能会保持静音。" + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "按住说话请使用不带 Ctrl、Alt 或 Shift 的单个按键。如果使用组合键,请先松开主键,否则麦克风可能会保持开启。" + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "此快捷键请使用不带 Ctrl、Alt 或 Shift 的单个按键。如果使用组合键,请先松开主键,否则快捷键可能会保持激活。" + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "语音活动优先请使用不带 Ctrl、Alt 或 Shift 的单个按键。如果使用组合键,请先松开主键,否则优先状态可能会保持开启。" + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "为保障你的账号安全,请先验证身份。" @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "全局个人资料" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "全局快捷键" @@ -17434,6 +17505,11 @@ msgstr "按住 Shift 并按 X 键选择应用。按 {selectAllShortcutLabel} 可 msgid "Hold to record. Drag up to lock, or release to send." msgstr "按住录音。向上拖动锁定,松开发送。" +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "按住 Shift、Ctrl 或 Alt 时,此快捷键不会生效。请在系统设置中添加备用按键。" + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "太棒了!通知已开启" msgid "Hyperlink" msgstr "超链接" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland 没有快捷键对话框。请将适用于你的 Hyprland 版本的配置行添加到 Hyprland 配置中,并将 KEY 替换为你想要的按键。" + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland 没有快捷键对话框。请在 Hyprland 配置中绑定按键。" + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf(Hyprland 0.55 之前)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua(Hyprland 0.55 及更高版本)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "我同意<0>隐私政策" @@ -17576,7 +17672,7 @@ msgstr "我改变主意了" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "我知道了" @@ -18000,7 +18096,7 @@ msgstr "在" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "在“操作系统”模式下,上方列出的词典仅供参考。如果支持,{PRODUCT_NAME} 会将这些语言标签传递给你的操作系统拼写检查器。无法识别的标签将被忽略。" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "在浏览器中,按住说话功能仅在 {PRODUCT_NAME} 标签页处于焦点时可用。请安装桌面应用以使用系统范围的按住说话功能。" @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "输入" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "无法自动启用输入访问权限。请手动更改,然后完全退出并重新启动 {productName},快捷键才能使用该权限。" - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "无法启用输入访问权限。请从“{settingsMenuName}”设置中重试,然后完全退出并重新启动 {productName}。" - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "在 Wayland 上,系统级键盘和鼠标快捷键需要输入访问权限。更改输入访问权限后,请完全退出并重新启动 {productName},快捷键才能使用该权限。" - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "输入访问权限已更改。请完全退出并重新启动 {productName},系统级快捷键才能使用该权限。" - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "输入和输出" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "授权请求无效" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "机器人权限无效。" @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "配对码无效或已过期。请重试。" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "无效的 redirect_uri" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "共享时已隐藏邀请链接:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "共享时已隐藏邀请链接" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "跳转到 {displayName}" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "跳转到“{labelText}”" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "跳转到当前通话" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "跳转到链接的频道" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "跳转到链接的消息" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "跳转到 {authorNickname} 发送的消息" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "跳转到“{labelText}”中的消息" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "请妥善保管。重新生成会破坏现有集成。" #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "松开按键后继续传输一小段时间。" @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "键盘" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "无法启动键盘访问。快捷键仅在 {productName} 处于焦点时可用。" + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "让社区成员自行管理" msgid "Let the member hear others in this community again" msgstr "再次允许该成员在此社区中收听他人" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "允许 {productName} 直接读取你的键盘。你的系统已允许此操作。" + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "重启桌面应用后,让自定义 CSS 显示透明窗口区域。" @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "链接样式" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "链接无法打开" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "外观风格" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "看起来你正在使用 {0}。在此基础上再叠加 {PRODUCT_NAME} 的音频处理可能会导致抽吸或失真。直接输入会原样传递你的音频。" @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "消息布局" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "消息链接" @@ -21332,7 +21416,7 @@ msgstr "麦克风" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "麦克风测试" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "其他" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "缺少 client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "更多选项" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "更多快捷键选项" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "无 {sendMessagesPermission} 权限" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "无法访问" @@ -22984,7 +23068,7 @@ msgstr "未找到匹配项" msgid "No mentions yet" msgstr "暂无提及" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "未找到麦克风" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "未找到设置" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "未设置快捷键" @@ -23358,6 +23442,11 @@ msgstr "不是整数" msgid "Not accepted" msgstr "未接受" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "未分配" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "在浏览器中打开 {host}。" #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "打开 {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "在设置中打开 {labelText}" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "下翻页" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "页面链接" @@ -25611,11 +25700,11 @@ msgstr "在线状态" msgid "Press {shortcut} to react" msgstr "按 {shortcut} 进行回应" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "请按下要绑定的按键、鼠标键或游戏手柄按键。按 Esc 取消。" -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "按下快捷键…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "按住说话按键" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "按住说话" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "按住说话(功能受限)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "按住说话尚未设置全局按键。如需设置,请前往“设置”>“应用”>“{productName}”。" + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "按住说话尚未设置全局按键。如需设置,请前往“系统设置”>“键盘”>“快捷键”>“{productName}”。" + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "按住说话尚未设置全局按键。如需设置,请打开桌面环境的键盘快捷键设置。" + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "直接输入模式下会忽略按住说话。请切换到人声优化或自定义模式后使用。" @@ -26293,21 +26397,51 @@ msgstr "直接输入模式下会忽略按住说话。请切换到人声优化或 msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "按住说话已开启。按住 {pushToTalkHint} 讲话。" +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "按住说话已开启。按住你的说话键即可讲话。" + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "由于你的桌面环境未设置全局快捷键,按住说话仅在 {productName} 处于焦点时可用。" + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "在启用“{permissionName}”之前,按住说话仅在 {productName} 处于焦点时可用。更改权限后,请完全退出并重新启动 {productName},系统级热键才能使用此功能。" +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "在你设置全局快捷键之前,按住说话仅在 {productName} 处于焦点时可用。" + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "按住说话功能仅在浏览器标签页处于焦点时可用。" +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "按住说话将使用你在 {productName} 中设置的按键。请先设置一个按键,否则麦克风将使用语音活动。" + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "你处于拒听状态时,按住说话不会取消麦克风静音。请先取消拒听再讲话。" +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "只有在 Hyprland 配置中绑定后,按住说话才能在 {productName} 之外使用。" + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "只有在 Hyprland 配置中绑定后,按住说话才能在 {productName} 之外使用。请添加适用于你的 Hyprland 版本的配置行,并将 KEY 替换为你想要的按键。" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "原始嵌入 JSON" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "重新启用内置快捷键" @@ -26565,13 +26699,6 @@ msgstr "最近提及" msgid "Recent searches" msgstr "最近搜索" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "重新检查" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "推荐" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "推荐。优化麦克风,让语音更清晰。" @@ -26600,6 +26727,11 @@ msgstr "重新连接" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "重新连接成功。如需恢复之前的屏幕共享,请重新选择屏幕。" +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "正在重新连接桌面环境…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "正在重新连接…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "录制快捷键" @@ -26851,7 +26983,7 @@ msgstr "相对" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "松开延迟" @@ -27739,13 +27871,13 @@ msgstr "已重新发送" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "重置" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "将“{label}”重置为内置值" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "重置为实际值" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "重置为内置值" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "选择你想在新设备上登录的账号。" #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "选择语音处理模式" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "发送原始麦克风音频,不进行任何处理。" #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "直接发送你的音频,不做任何处理。如果你正在使用外部音频软件,推荐此选项。" @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "由 {preset} 预设设置。在此处选择一个值将切换到自定义。" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "设置自定义快捷键" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "为“{label}”设置自定义快捷键" @@ -30066,6 +30198,11 @@ msgstr "将语音区域设置为 {region}" msgid "Set the voice region to automatic" msgstr "将语音区域设置为自动" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "设置" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "设置" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "设置链接" @@ -30330,8 +30467,8 @@ msgstr "短代码" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "切换为舒适布局" msgid "Switch to dense layout" msgstr "切换为紧凑布局" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "切换到直接输入" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "由系统管理的安装" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "全局快捷键" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "无法设置全局快捷键。" + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "全局:{trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "全局:未分配" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "你要找的页面不存在或已被移动。" #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "提供的 redirect_uri 未在此应用中注册。" #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "提供的 redirect_uri 必须使用 HTTP 或 HTTPS。" @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "此动画过长或过大,无法裁剪。请尝试使用更短或更小的文件。" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "此应用" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "此附件已过期" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "此机器人未公开,其他用户无法添加。" @@ -34081,6 +34233,16 @@ msgstr "此对话已不可用。" msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "此桌面版本无法在 {productName} 中安装更新。请下载最新的桌面安装程序。" +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "此桌面环境不支持全局快捷键。按住说话仅在 {productName} 处于焦点时可用。" + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "此桌面环境不支持全局快捷键。快捷键在 {productName} 处于焦点时仍然可用。" + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "这是你的最后一个通行密钥,而通行密钥是你的第二重 msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "这是你的最后一个通行密钥。删除后,你将无法再使用通行密钥作为第二重验证。" +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "此按键在 {productName} 处于焦点时可用。全局按键由桌面环境设置,可在“{settingsTabName}”>“{sectionName}”中查看。" + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "此链接频道标记为成人内容,打开的内容可能不适合某些用户。" #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "你的账号无法使用此链接。" @@ -34778,6 +34945,21 @@ msgstr "标题栏" msgid "Title bar platform" msgstr "标题栏平台" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "要更改按键,请前往“设置”>“应用”>“{productName}”。" + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "要更改按键,请前往“系统设置”>“键盘”>“快捷键”>“{productName}”。" + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "要更改按键,请打开桌面环境的键盘快捷键设置。" + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "要创建 Webhook,请打开<0>{channelWebhooksSettingsPath}。你仍然可以在此处编辑和整理所有现有 Webhook。" @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "未知" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "未知应用、会话过期或授权请求无效。" @@ -36410,6 +36592,11 @@ msgstr "高达 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "最高 720p,30 帧/秒" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "请更新 {productName} 以在 Wayland 上使用全局快捷键。" + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "在整个应用中使用深色主题" msgid "Use default" msgstr "使用默认值" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "直接访问输入设备" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "语音参与者" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "语音处理" @@ -38264,7 +38456,7 @@ msgstr "语音解除拒听" msgid "Voice unmute" msgstr "语音取消静音" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "此浏览器中的语音通话使用系统输出设备。请在系统设置中更改。" @@ -38278,7 +38470,7 @@ msgstr "已作废" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "正在等待一个或多个所选频道的慢速模式结束。" msgid "Waiting for you to..." msgstr "等你来…" +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "正在等待桌面环境…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "哪些对话" msgid "Which rule was broken?" msgstr "违反了哪条规则?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "开启按住说话时,快捷键会控制你的麦克风,因此静音按钮已禁用。" @@ -39094,6 +39291,11 @@ msgstr "没有此权限时,必须使用按住说话。" msgid "Wordmark" msgstr "文字标志" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "在 {productName} 处于焦点时可用" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "你在 {PREMIUM_PRODUCT_NAME} 期间更改了标签。除非续订,否则你的 {PREMIUM_PRODUCT_NAME} 到期后,标签将随机重新生成。" #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "你无权访问发送此消息的频道。" @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "你的 {premiumProductName} 订阅已过期。你已失去所有 {premiumProductName} 福利。请重新激活订阅以恢复访问权限。" #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "你的 {productName} 会话已过期。请重新登录以继续授权此应用。" @@ -40144,6 +40346,16 @@ msgstr "你的社区至少需要 {minMemberCount, plural, other {# 位成员}} msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "你的信用卡已检查完成。如果信用卡有效,成人内容将很快解锁。你可以关闭此标签页并返回应用。" +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "桌面环境未设置全局快捷键。" + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "即使 {productName} 不在焦点,桌面环境也会运行这些快捷键。" + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/i18n/locales/zh-TW/messages.po b/fluxer_app/src/features/i18n/locales/zh-TW/messages.po index c2652e275..25ecca721 100644 --- a/fluxer_app/src/features/i18n/locales/zh-TW/messages.po +++ b/fluxer_app/src/features/i18n/locales/zh-TW/messages.po @@ -993,7 +993,7 @@ msgstr "{count, plural, one {# 個頻道正在追蹤} other {# 個頻道正在 msgid "{count, plural, one {# community found} other {# communities found}}" msgstr "{count, plural, one {找到 # 個社群} other {找到 # 個社群}}" -#: src/features/app/components/layout/GuildsLayout.tsx:1955 +#: src/features/app/components/layout/GuildsLayout.tsx:1954 msgid "{count, plural, one {# community is temporarily unavailable due to a flux capacitor malfunction.} other {# communities are temporarily unavailable due to a flux capacitor malfunction.}}" msgstr "{count, plural, one {# 個社群} other {# 個社群}}因時空電容器故障,暫時無法使用。" @@ -1708,6 +1708,31 @@ msgstr "{productName} 會在您使用電腦時,避免向您的行動裝置傳 msgid "{productName} Bug Hunter" msgstr "{productName} 錯誤獵人" +#. Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:28 +msgid "{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys." +msgstr "{productName} 可以要求桌面環境在 {productName} 不在焦點時執行其快速鍵。桌面環境會請您確認,並讓您選擇按鍵。" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:160 +msgid "{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead." +msgstr "{productName} 無法再讀取您的鍵盤,因此改為透過 X11 監聽快速鍵。" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:155 +msgid "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead." +msgstr "{productName} 無法再讀取您的鍵盤,因此改用桌面環境的快速鍵。" + +#. Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:165 +msgid "{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused." +msgstr "{productName} 無法再讀取您的鍵盤。快速鍵僅在 {productName} 處於焦點時有效。" + +#. Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:34 +msgid "{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config." +msgstr "{productName} 可以向 Hyprland 註冊其快速鍵,讓快速鍵在 {productName} 不在焦點時也能執行。之後請在 Hyprland 設定檔中綁定按鍵。" + #. Inline notice body on Windows. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #. Inline notice body on macOS. Explains that desktop audio is disabled because productName cannot exclude its own WebRTC/call playback on this OS build. #: src/features/voice/components/modals/screen_share_picker_modal/PerWindowAudioNotice.tsx:23 @@ -1813,7 +1838,7 @@ msgid "{productName} needs camera access to list your devices." msgstr "{productName} 需要相機存取權限才能列出您的裝置。" #. Voice settings notice shown when microphone permission has not been granted. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:141 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:143 msgid "{productName} needs microphone access to list your devices." msgstr "{productName} 需要麥克風存取權限才能列出您的裝置。" @@ -1837,6 +1862,11 @@ msgstr "{productName} 現在已移至 {host}。請從該處安裝。" msgid "{productName} Partner" msgstr "{productName} 合作夥伴" +#. Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:58 +msgid "{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused." +msgstr "{productName} 已向 Hyprland 註冊其快速鍵。在 Hyprland 設定檔中綁定後,即可在 {productName} 不在焦點時使用。" + #. Nagbar shown after the app restarts while the user was connected to a community voice channel. {productName} is Fluxer, {channelName} is the voice channel name, and {communityName} is the community name. #: src/features/app/components/layout/app_layout/nagbars/VoiceSessionRestoreNagbar.tsx:24 msgid "{productName} restarted while you were connected to the {channelName} channel in the {communityName} community." @@ -2586,7 +2616,7 @@ msgid "A profile that stands out" msgstr "一個脫穎而出的個人檔案" #. OAuth authorize page error shown when the bot scope requires a redirect_uri that is missing. Technical message; keep "redirect_uri" and "bot" untranslated. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:54 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:55 msgid "A redirect_uri is required when the bot scope is not the only scope." msgstr "當「bot」範圍不是唯一的範圍時,必須提供 redirect_uri。" @@ -2847,7 +2877,7 @@ msgstr "僅限所有已開啟的私訊" msgid "Across every community you're in" msgstr "您所屬的每個社群" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:110 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:112 msgid "Action" msgstr "動作" @@ -2962,7 +2992,7 @@ msgid "Activity sharing" msgstr "活動分享" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:488 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:495 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:102 msgid "Activity threshold" msgstr "語音偵測靈敏度" @@ -3091,7 +3121,7 @@ msgid "Add an extra layer of security to your account" msgstr "為您的帳號多加一層安全防護" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:41 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:43 msgid "Add another custom shortcut" msgstr "再新增一個自訂快速鍵" @@ -3427,7 +3457,7 @@ msgid "Address" msgstr "電子郵件地址" #. Description for the custom profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:99 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:101 msgid "Adjust each setting yourself: noise suppression, echo cancellation, and gain." msgstr "自行調整每個設定:雜訊抑制、回音消除和增益。" @@ -3696,11 +3726,11 @@ msgstr "允許 {everyoneMention} 和 {hereMention}" msgid "Allow {PRODUCT_NAME} to access your camera in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_CAMERA_PERMISSION_NAME}." msgstr "在「{MACOS_SYSTEM_SETTINGS_NAME}」→「{MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME}」→「{MACOS_CAMERA_PERMISSION_NAME}」中允許 {PRODUCT_NAME} 存取您的相機。" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:546 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:553 msgid "Allow {PRODUCT_NAME} to access your microphone in {MACOS_SYSTEM_SETTINGS_NAME} → {MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME} → {MACOS_MICROPHONE_PERMISSION_NAME}." msgstr "請在「{MACOS_SYSTEM_SETTINGS_NAME}」→「{MACOS_PRIVACY_AND_SECURITY_SETTINGS_NAME}」→「{MACOS_MICROPHONE_PERMISSION_NAME}」中允許 {PRODUCT_NAME} 存取您的麥克風。" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:551 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:558 msgid "Allow {PRODUCT_NAME} to access your microphone. Check your browser's address bar or settings." msgstr "允許 {PRODUCT_NAME} 存取您的麥克風。請檢查瀏覽器的網址列或設定。" @@ -3803,7 +3833,7 @@ msgstr "允許沒有「{readMessageHistoryPermissionLabel}」權限的成員查 msgid "Allow mentions for this role" msgstr "允許提及此身分組" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:571 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:578 msgid "Allow microphone" msgstr "允許麥克風" @@ -5003,7 +5033,7 @@ msgid "Authorization failed" msgstr "授權失敗" #. OAuth authorize page generic error toast shown when the authorize request fails unexpectedly. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:75 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:76 msgid "Authorization failed. Try again." msgstr "授權失敗。請再試一次。" @@ -5092,8 +5122,8 @@ msgid "Auto-detect language" msgstr "自動偵測語言" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:119 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:475 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:121 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:482 msgid "Auto-set activity threshold" msgstr "自動設定活動門檻" @@ -5998,7 +6028,7 @@ msgid "Bot permissions" msgstr "機器人權限" #. OAuth authorize page error shown when the permissions query parameter is a negative integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:67 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:68 msgid "Bot permissions cannot be negative." msgstr "機器人權限不能是負數。" @@ -6869,6 +6899,11 @@ msgstr "變更群組暱稱" msgid "Change icon" msgstr "更換圖示" +#. Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:72 +msgid "Change in system settings" +msgstr "在系統設定中變更" + #. Title of the modal where the user picks a community invite background source. Keep it concise. Keep the tone plain and specific. #: src/features/guild/components/modals/guild_tabs/guild_overview_tab/fields/GuildInviteSplashUploadField.tsx:37 msgid "Change invite background" @@ -7229,7 +7264,7 @@ msgid "Channel access" msgstr "頻道存取權" #. Title of the alert shown when clicking a message link the user does not have permission to view. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:137 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:140 msgid "Channel access denied" msgstr "頻道存取遭拒" @@ -7294,7 +7329,7 @@ msgstr "頻道 ID 已複製到剪貼簿" #. Short label in the messaging mention renderer. Keep it concise. #. Type chip suffix in tooltips for inline rendered channel mentions. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:97 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:100 #: src/features/messaging/components/markdown/renderers/MentionRenderer.tsx:39 msgid "channel link" msgstr "頻道連結" @@ -7462,6 +7497,11 @@ msgstr "聊天輸入框" msgid "Chat shortcuts" msgstr "聊天快速鍵" +#. Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:128 +msgid "Check again" +msgstr "再次檢查" + #. Tray menu item. Triggers the auto-updater to look for a new desktop build. #: src/features/platform/utils/DesktopLocaleBridge.ts:225 msgid "Check for updates" @@ -7645,7 +7685,7 @@ msgstr "在系統選取器中選擇視窗" msgid "Choose an account" msgstr "選擇帳號" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:152 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:154 msgid "Choose an action." msgstr "選擇一個動作。" @@ -7667,7 +7707,7 @@ msgid "Choose background" msgstr "選擇背景" #. OAuth authorize page error shown when a bot invite request provides both guild_id and channel_id. Keep the parameter names literal. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:87 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:88 msgid "Choose either guild_id or channel_id, not both." msgstr "請選擇 guild_id 或 channel_id,不可兩者都選。" @@ -7990,7 +8030,7 @@ msgstr "讓您的麥克風更清晰地收音。" #: src/features/app/components/setup/SetupWizardSteps.tsx:120 #: src/features/channel/components/message_search_bar/HistorySection.tsx:24 #: src/features/guild/components/modals/guild_tabs/GuildMembersDateRangeModal.tsx:74 -#: src/features/input/components/KeybindRecorder.tsx:442 +#: src/features/input/components/KeybindRecorder.tsx:482 #: src/features/theme_studio/sections/QuickCssSection.tsx:64 #: src/features/theme_studio/sections/QuickCssSection.tsx:241 msgid "Clear" @@ -8191,7 +8231,7 @@ msgstr "點擊以開啟選色器並選擇新顏色。" msgid "Click to react" msgstr "點擊以新增反應" -#: src/features/input/components/KeybindRecorder.tsx:411 +#: src/features/input/components/KeybindRecorder.tsx:451 msgid "Click to record" msgstr "點擊錄製" @@ -9110,7 +9150,7 @@ msgstr "確認" msgid "Confirmation or double-click for community voice joins" msgstr "社群語音加入時的確認或雙擊動作" -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:169 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:176 msgid "Conflicts with {conflictLabel}" msgstr "與 {conflictLabel} 衝突" @@ -9127,7 +9167,7 @@ msgstr "請連接相機或虛擬擷取裝置,然後再試一次。" msgid "Connect a display, then try again." msgstr "請連接顯示器後再試一次。" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:556 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:563 msgid "Connect a microphone and try again." msgstr "請連接麥克風後再試一次。" @@ -9700,6 +9740,21 @@ msgstr "複製邀請連結" msgid "Copy invite URL" msgstr "複製邀請網址" +#. Button that copies Hyprland config lines to the clipboard. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:109 +msgid "Copy lines" +msgstr "複製設定行" + +#. Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:118 +msgid "Copy lines for hyprland.conf" +msgstr "複製設定行(hyprland.conf)" + +#. Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:113 +msgid "Copy lines for hyprland.lua" +msgstr "複製設定行(hyprland.lua)" + #. Generic action label that copies a URL to the clipboard. #: src/features/i18n/utils/CommonMessageDescriptors.ts:750 #: src/features/user/components/modals/tabs/component_gallery_tab/ComponentGalleryTabButtonsTab.tsx:330 @@ -10845,7 +10900,7 @@ msgstr "自訂" #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. #. Voice input processing profile where the user configures processing manually. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:94 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:96 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:42 #: src/features/voice/components/VoiceSettingsMenus.tsx:70 msgctxt "voice-processing-profile" @@ -10948,12 +11003,12 @@ msgid "Custom selection" msgstr "自訂選取" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:42 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:44 msgid "Custom shortcut action" msgstr "自訂快速鍵動作" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:38 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:40 msgid "Custom shortcut options" msgstr "自訂快速鍵選項" @@ -11466,7 +11521,7 @@ msgstr "預設快速鍵" msgid "Default translator" msgstr "預設翻譯工具" -#: src/features/input/components/KeybindRecorder.tsx:419 +#: src/features/input/components/KeybindRecorder.tsx:459 msgid "Default:" msgstr "預設:" @@ -11715,7 +11770,7 @@ msgid "Delete saved media" msgstr "刪除已儲存的媒體" #. Button or menu action label in the keybinds tab. Keep it concise. Keep the tone plain and specific. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:34 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:36 msgid "Delete shortcut" msgstr "刪除快速鍵" @@ -12102,13 +12157,13 @@ msgstr "開發人員狀態" #. Short label in the channel and chat utility buttons. Keep it concise. #. Title of the info alert shown when clicking an internal developer-tools deep link. #: src/features/channel/components/channel_header_components/UtilityButtons.tsx:20 -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:149 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:152 #: src/features/ui/action_menu/items/StaffDeveloperUserControlsMenuItem.tsx:13 msgid "Developer tools" msgstr "開發人員工具" #. Body of the developer-tools info alert. Refers to the staff badge button in the channel header. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:153 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:156 msgid "Developer tools are available from the staff badge in the channel header." msgstr "開發人員工具可從頻道標題列的員工徽章開啟。" @@ -12313,7 +12368,7 @@ msgid "Disable backend premium perks" msgstr "停用後端加值福利" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:33 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:35 msgid "Disable built-in shortcut" msgstr "停用內建快速鍵" @@ -12377,7 +12432,7 @@ msgid "Disable screen share picture-in-picture popouts?" msgstr "要停用螢幕分享的子母畫面彈出視窗嗎?" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:26 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:28 msgid "Disable shortcut" msgstr "停用快速鍵" @@ -12888,7 +12943,7 @@ msgstr "捐款支持" #. Primary button in the favorite GIF import modal after a completed import. #: src/features/auth/components/pages/oauth_authorize_page/OAuthSuccessState.tsx:44 #: src/features/channel/components/pickers/gif/ImportFavoriteGifsModal.tsx:54 -#: src/features/input/components/KeybindRecorder.tsx:470 +#: src/features/input/components/KeybindRecorder.tsx:510 #: src/features/moderation/components/report_modal/IARModal.tsx:94 #: src/features/moderation/components/report/ReportBreadcrumbs.tsx:31 #: src/features/search/components/search/ChannelFilterSheet.tsx:165 @@ -12951,7 +13006,7 @@ msgid "Download button" msgstr "下載按鈕" #. Button or menu action label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:107 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:109 msgid "Download desktop app" msgstr "下載桌面版應用程式" @@ -13204,6 +13259,11 @@ msgstr "荷蘭文" msgid "e.g., YubiKey, iPhone, work computer" msgstr "例如:YubiKey、iPhone、公司電腦" +#. Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:64 +msgid "Each key can run only one system-wide shortcut." +msgstr "每個按鍵只能執行一個全系統快速鍵。" + #. Body of the alert shown when a published announcement message hit its editing limit. {duration} is a localized short duration such as "12 minutes". #: src/features/messaging/components/alerts/PublishedMessageEditLimitModal.tsx:12 msgid "Each published message allows 3 quick edits, then 1 every 20 minutes. Try again in {duration}." @@ -13350,7 +13410,7 @@ msgid "Edit invite link" msgstr "編輯邀請連結" #. Button or menu action label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:32 +#: src/features/input/components/KeybindRecorder.tsx:40 msgid "Edit keyboard shortcut" msgstr "編輯鍵盤快捷鍵" @@ -13425,7 +13485,7 @@ msgstr "編輯已儲存的媒體表單" msgid "Edit search engine" msgstr "編輯搜尋引擎" -#: src/features/input/components/KeybindRecorder.tsx:378 +#: src/features/input/components/KeybindRecorder.tsx:418 msgid "Edit shortcut" msgstr "編輯快捷鍵" @@ -13753,20 +13813,16 @@ msgstr "清空編輯" msgid "Empty favorites" msgstr "沒有最愛項目" -#. Button label in the Linux Wayland input-access nagbar. -#. Button label in the keybind settings system-wide shortcuts section. #. Button or menu action label in the invites disable invites button. Keep it concise. #. Primary button label in the enable-invites confirmation modal. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:32 #: src/features/app/components/layout/GuildLayout.tsx:54 #: src/features/invite/components/DisableInvitesButton.tsx:27 #: src/features/user/components/modals/tabs/account_security_tab/SecurityTab.tsx:360 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:23 msgid "Enable" msgstr "啟用" #. Voice settings CTA label for opening a platform permission prompt. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:127 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:129 msgid "Enable {permissionName} permission" msgstr "啟用「{permissionName}」權限" @@ -13876,11 +13932,6 @@ msgstr "啟用硬體加速並重新啟動" msgid "Enable hardware acceleration?" msgstr "啟用硬體加速?" -#. Description for a Linux Wayland input-access nagbar. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:16 -msgid "Enable input access to use system-wide shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "啟用輸入存取權,以便在 Wayland 上使用全系統捷徑。變更輸入存取權後,請完全結束並重新啟動 {productName},捷徑才能使用。" - #. Button label that re-enables invite links for a community. #: src/features/invite/components/DisableInvitesButton.tsx:40 msgid "Enable invites" @@ -13971,7 +14022,7 @@ msgid "Enable Shift to expand" msgstr "啟用 Shift 鍵展開" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:30 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:32 msgid "Enable shortcut" msgstr "啟用捷徑" @@ -14357,7 +14408,7 @@ msgstr "整個系統音訊" #. Voice menu submenu label that contains per-user entrance sound mute and volume controls. #: src/features/ui/action_menu/items/VoiceParticipantMenuData.tsx:181 #: src/features/ui/action_menu/items/VoiceParticipantMenuItems.tsx:310 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:192 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:194 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:150 msgid "Entrance sound" msgstr "進場音效" @@ -14467,7 +14518,7 @@ msgstr "晚上" #. Description for the automatic gain control toggle in the voice processing settings. #. Description for the automatic gain control toggle in the voice processing settings. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:176 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:178 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:63 msgid "Evens out your mic volume so you are not too quiet." msgstr "讓您的麥克風音量更平均,避免太小聲。" @@ -15682,6 +15733,26 @@ msgstr "限英國地區觀看…" msgid "For me" msgstr "自己使用" +#. Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:187 +msgid "For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted." +msgstr "「按住以靜音」請使用單一按鍵,不要搭配 Ctrl、Alt 或 Shift。若使用組合鍵,請先放開主要按鍵,否則麥克風可能會保持靜音。" + +#. Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:181 +msgid "For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on." +msgstr "「按住說話」請使用單一按鍵,不要搭配 Ctrl、Alt 或 Shift。若使用組合鍵,請先放開主要按鍵,否則麥克風可能會保持開啟。" + +#. Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:199 +msgid "For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active." +msgstr "此快速鍵請使用單一按鍵,不要搭配 Ctrl、Alt 或 Shift。若使用組合鍵,請先放開主要按鍵,否則快速鍵可能會保持作用中。" + +#. Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:193 +msgid "For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on." +msgstr "「語音活動優先」請使用單一按鍵,不要搭配 Ctrl、Alt 或 Shift。若使用組合鍵,請先放開主要按鍵,否則優先狀態可能會保持開啟。" + #: src/features/auth/components/modals/SudoVerificationModal.tsx:268 msgid "For your security, please confirm it's you to continue." msgstr "為了您的帳戶安全,請驗證是您本人操作,才能繼續。" @@ -16600,8 +16671,8 @@ msgid "Global profile" msgstr "全域個人檔案" #. Toggle in the keybinds tab. When on, the shortcut works even when Fluxer is not focused. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:81 -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:63 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:83 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:65 msgid "Global shortcut" msgstr "全域快捷鍵" @@ -17434,6 +17505,11 @@ msgstr "按住 Shift 並按下 X 以選取應用程式。按下 {selectAllShortc msgid "Hold to record. Drag up to lock, or release to send." msgstr "按住錄音。向上拖曳可鎖定,放開即可傳送。" +#. Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:39 +msgid "Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings." +msgstr "按住 Shift、Ctrl 或 Alt 時,此快速鍵無法使用。請在系統設定中新增替代按鍵。" + #. Action label in quick switcher for navigating to the Home tab. #: src/features/app/components/layout/MobileBottomNav.tsx:110 #: src/features/search/state/QuickSwitcherCandidateBuilder.ts:47 @@ -17537,6 +17613,26 @@ msgstr "太棒了!通知已啟用" msgid "Hyperlink" msgstr "超連結" +#. Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:86 +msgid "Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want." +msgstr "Hyprland 沒有快速鍵對話框。請將適用於您 Hyprland 版本的設定行加入 Hyprland 設定檔,並將 KEY 替換為您想要的按鍵。" + +#. Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:92 +msgid "Hyprland has no shortcut dialog. Bind the keys in your Hyprland config." +msgstr "Hyprland 沒有快速鍵對話框。請在 Hyprland 設定檔中綁定按鍵。" + +#. Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:105 +msgid "hyprland.conf (before Hyprland 0.55)" +msgstr "hyprland.conf(Hyprland 0.55 之前)" + +#. Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:101 +msgid "hyprland.lua (Hyprland 0.55 and later)" +msgstr "hyprland.lua(Hyprland 0.55 及以上版本)" + #: src/features/auth/flow/RegistrationLegalConsent.tsx:83 msgid "I agree to the <0>Privacy policy" msgstr "我同意<0>隱私權政策" @@ -17576,7 +17672,7 @@ msgstr "我改變心意了" #. Short label in the voice tab. Keep it concise. #: src/features/channel/components/MatureContentChannelGate.tsx:207 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:111 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:113 msgid "I understand" msgstr "我了解了" @@ -18000,7 +18096,7 @@ msgstr "在" msgid "In \"Operating system\" mode, dictionaries listed above are advisory. {PRODUCT_NAME} will pass these language tags to your OS spellchecker when supported. Tags it doesn't recognize are ignored." msgstr "在「作業系統」模式中,上方列出的字典僅供參考。如果支援,{PRODUCT_NAME} 會將這些語言標籤傳遞給您的作業系統拼字檢查工具。無法辨識的標籤將會被忽略。" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:322 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:324 msgid "In a browser, push-to-talk only works while the {PRODUCT_NAME} tab is focused. Install the desktop app for system-wide push-to-talk." msgstr "在瀏覽器中,「按住說話」只有在 {PRODUCT_NAME} 分頁為焦點時才能運作。請安裝桌面版應用程式,以使用全系統的按住說話功能。" @@ -18268,30 +18364,8 @@ msgctxt "chat-settings-section" msgid "Input" msgstr "輸入" -#. Status text after Linux input access setup failed. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:35 -msgid "Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it." -msgstr "無法自動啟用輸入存取權。請手動變更,然後完全結束並重新啟動 {productName},快速鍵才能使用。" - -#. Description for a Linux Wayland input-access nagbar after automatic permission setup failed. {settingsMenuName} is the shared user settings tab label for shortcuts and {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:26 -msgid "Input access could not be enabled. Try again from {settingsMenuName} settings, then fully quit and restart {productName}." -msgstr "無法啟用輸入存取權。請從「{settingsMenuName}」設定中再試一次,然後完全結束並重新啟動 {productName}。" - -#. Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:18 -msgid "Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it." -msgstr "在 Wayland 上,全系統的鍵盤和滑鼠快速鍵需要輸入存取權。變更輸入存取權後,請完全結束並重新啟動 {productName},快速鍵才能使用。" - -#. Description for a Linux Wayland input-access nagbar after access was changed. {productName} is the app name. -#. Status text after Linux input access was changed. {productName} is the app name. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:21 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:31 -msgid "Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it." -msgstr "輸入存取權已變更。請完全結束並重新啟動 {productName},全系統快速鍵才能使用。" - #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:180 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:182 msgid "Input and output" msgstr "輸入與輸出" @@ -18450,7 +18524,7 @@ msgid "Invalid authorization request" msgstr "無效的授權請求" #. OAuth authorize page error shown when the permissions query parameter is not a valid integer. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:63 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:64 msgid "Invalid bot permissions." msgstr "無效的機器人權限。" @@ -18493,7 +18567,7 @@ msgid "Invalid or expired code. Try again." msgstr "代碼無效或已過期。請再試一次。" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:79 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:80 msgid "Invalid redirect_uri" msgstr "redirect_uri 無效" @@ -18597,7 +18671,7 @@ msgid "Invite link is hidden while sharing:" msgstr "分享時隱藏邀請連結:" #. Inline replacement text shown for invite links while streaming privacy is active. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:165 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:168 msgid "Invite link masked while sharing" msgstr "分享時已隱藏邀請連結" @@ -19011,7 +19085,7 @@ msgid "Jump to {displayName}" msgstr "跳到「{displayName}」" #. Accessible label for an inline channel mention link. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:111 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:114 msgid "Jump to {labelText}" msgstr "跳到「{labelText}」" @@ -19068,12 +19142,12 @@ msgid "Jump to the current call" msgstr "跳到目前的通話" #. Fallback accessible label for an inline channel mention when the target name is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:115 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:118 msgid "Jump to the linked channel" msgstr "跳到連結的頻道" #. Fallback accessible label for an inline message link when the target channel is not known. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:107 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:110 msgid "Jump to the linked message" msgstr "跳到連結的訊息" @@ -19083,7 +19157,7 @@ msgid "Jump to the message from {authorNickname}" msgstr "跳到 {authorNickname} 的訊息" #. Accessible label for an inline message link that jumps to a specific message in a channel. labelText is the channel display name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:102 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:105 msgid "Jump to the message in {labelText}" msgstr "跳到「{labelText}」中的訊息" @@ -19167,7 +19241,7 @@ msgid "Keep these safe. Regenerating will break existing integrations." msgstr "請妥善保管。重新產生金鑰會導致現有的整合服務中斷。" #. Description for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:82 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:84 msgid "Keep transmitting briefly after release." msgstr "放開按鍵後仍會短暫繼續傳送。" @@ -19204,6 +19278,11 @@ msgctxt "accessibility-settings-section" msgid "Keyboard" msgstr "鍵盤" +#. Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:176 +msgid "Keyboard access couldn't start. Shortcuts only work while {productName} is focused." +msgstr "無法啟動鍵盤存取。快速鍵僅在 {productName} 處於焦點時有效。" + #. Toggle label and settings search entry for controlling tooltip keyboard shortcut hints. #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:46 msgid "Keyboard hints" @@ -19577,6 +19656,11 @@ msgstr "讓社群自行管理" msgid "Let the member hear others in this community again" msgstr "讓該成員能再次在此社群中聽到其他人說話" +#. Description for the Linux direct input device access switch. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:151 +msgid "Lets {productName} read your keyboard directly. Your system already allows this." +msgstr "讓 {productName} 直接讀取您的鍵盤。您的系統已允許此操作。" + #: src/features/theme_studio/sections/ThemeStudioSettingsSection.tsx:259 msgid "Lets custom CSS reveal transparent window regions after restarting the desktop app." msgstr "重新啟動桌面版應用程式後,讓自訂 CSS 顯示透明視窗區域。" @@ -19760,7 +19844,7 @@ msgid "Link styling" msgstr "連結樣式" #. Title of the alert shown when clicking an internal link that is not available to the current account. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:157 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:160 msgid "Link unavailable" msgstr "連結無法使用" @@ -20036,7 +20120,7 @@ msgid "Look and feel" msgstr "外觀和風格" #. placeholder {0}: externalProcessor.name -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:671 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:678 msgid "Looks like you're using {0}. Stacking {PRODUCT_NAME}'s processing on top can cause pumping or distortion. Direct input passes your audio through untouched." msgstr "看來您正在使用 {0}。在此之上再疊加 {PRODUCT_NAME} 的處理,可能會導致音量忽大忽小或失真。直接輸入會原封不動地傳送您的音訊。" @@ -21144,7 +21228,7 @@ msgid "Message layout" msgstr "訊息版面配置" #. Type chip suffix in tooltips for inline rendered message links. Lowercase because it follows the channel name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:92 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:95 msgid "message link" msgstr "訊息連結" @@ -21332,7 +21416,7 @@ msgstr "麥克風" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:188 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:190 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:142 msgid "Mic test" msgstr "麥克風測試" @@ -21472,7 +21556,7 @@ msgid "Miscellaneous" msgstr "其他" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:37 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:38 msgid "Missing client_id" msgstr "缺少 client_id" @@ -21690,7 +21774,7 @@ msgid "More options" msgstr "更多選項" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:59 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:61 msgid "More shortcut options" msgstr "更多快速鍵選項" @@ -22672,7 +22756,7 @@ msgid "No {sendMessagesPermission} permission" msgstr "沒有 {sendMessagesPermission} 權限" #. Inline text shown inside a custom message link pill when the linked message cannot be accessed. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:145 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:148 msgid "No access" msgstr "無法存取" @@ -22984,7 +23068,7 @@ msgstr "沒有找到符合的項目" msgid "No mentions yet" msgstr "還沒有被提及的訊息" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:541 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:548 msgid "No microphone found" msgstr "找不到麥克風" @@ -23134,7 +23218,7 @@ msgid "No settings found" msgstr "找不到設定" #. Empty-state text in the keyboard shortcuts keybind recorder. -#: src/features/input/components/KeybindRecorder.tsx:36 +#: src/features/input/components/KeybindRecorder.tsx:44 msgid "No shortcut set" msgstr "未設定快速鍵" @@ -23358,6 +23442,11 @@ msgstr "不是整數" msgid "Not accepted" msgstr "未接受" +#. Shown instead of a key when the desktop has no key assigned to a system-wide shortcut. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:68 +msgid "Not assigned" +msgstr "未指定" + #. Fallback value when a saved billing payment method has no expiration date. #. Screen reader text for a cross in the Plutonium page comparison table. #: src/features/app/components/dialogs/components/plutonium/PaymentMethodList.tsx:18 @@ -23858,12 +23947,12 @@ msgid "Open {host} in your browser." msgstr "在瀏覽器中開啟 {host}。" #. Accessible label for an inline chat link pill that opens an app page such as Discovery. labelText is the page name. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:124 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:127 msgid "Open {labelText}" msgstr "開啟 {labelText}" #. Accessible label for an inline settings deep-link pill. labelText is the resolved settings tab and section breadcrumb. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:119 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:122 msgid "Open {labelText} in settings" msgstr "在設定中開啟 {labelText}" @@ -24611,7 +24700,7 @@ msgid "Page down" msgstr "Page Down" #. Role description announced for an inline chat link pill that opens an app page. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:129 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:132 msgid "page link" msgstr "頁面連結" @@ -25611,11 +25700,11 @@ msgstr "上線狀態" msgid "Press {shortcut} to react" msgstr "按下 {shortcut} 以新增反應" -#: src/features/input/components/KeybindRecorder.tsx:381 +#: src/features/input/components/KeybindRecorder.tsx:421 msgid "Press a key, mouse button, or gamepad button to bind. Escape cancels." msgstr "按下按鍵、滑鼠按鍵或遊戲手把按鈕即可綁定。按 Esc 取消。" -#: src/features/input/components/KeybindRecorder.tsx:407 +#: src/features/input/components/KeybindRecorder.tsx:447 msgid "Press a shortcut…" msgstr "按下快速鍵…" @@ -26273,18 +26362,33 @@ msgid "Push to talk key" msgstr "按住說話按鍵" #. Switch label in the voice tab toggling push-to-talk mode. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:115 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:361 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:117 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:360 #: src/features/voice/components/VoiceSettingsMenus.tsx:419 msgid "Push-to-talk" msgstr "按住說話" #. Short label in the voice tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:103 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:105 msgid "Push-to-talk (limited)" msgstr "按住說話(受限)" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:701 +#. Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:226 +msgid "Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}." +msgstr "「按住說話」尚未設定全系統按鍵。若要設定,請前往「設定」>「應用程式」>「{productName}」。" + +#. Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:220 +msgid "Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "「按住說話」尚未設定全系統按鍵。若要設定,請前往「系統設定」>「鍵盤」>「快捷鍵」>「{productName}」。" + +#. Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:231 +msgid "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings." +msgstr "「按住說話」尚未設定全系統按鍵。若要設定,請開啟桌面環境的鍵盤快速鍵設定。" + +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:708 msgid "Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it." msgstr "直接輸入模式會忽略按住說話。請切換為語音聚焦或自訂才能使用。" @@ -26293,21 +26397,51 @@ msgstr "直接輸入模式會忽略按住說話。請切換為語音聚焦或自 msgid "Push-to-talk is on. Hold {pushToTalkHint} to speak." msgstr "已開啟「按住說話」。按住 {pushToTalkHint} 即可說話。" +#. Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it. +#: src/features/input/utils/PushToTalkHint.ts:11 +msgid "Push-to-talk is on. Hold your push-to-talk key to speak." +msgstr "已開啟「按住說話」。按住您的說話鍵即可說話。" + +#. Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:209 +msgid "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts." +msgstr "由於您的桌面環境未設定全系統快速鍵,「按住說話」僅在 {productName} 處於焦點時有效。" + #. Voice settings warning shown when macOS Input Monitoring is required for system-wide push-to-talk. {productName} is the app name and {permissionName} is the macOS permission name. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:135 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:137 msgid "Push-to-talk only works while {productName} is focused until {permissionName} is enabled. After changing the permission, fully quit and restart {productName} so system-wide hotkeys can use it." msgstr "在啟用「{permissionName}」之前,按住說話僅在 {productName} 視窗使用中時有效。變更權限後,請完全結束並重新啟動 {productName},系統層級的快速鍵才能使用此功能。" +#. Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:205 +msgid "Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts." +msgstr "在您設定全系統快速鍵之前,「按住說話」僅在 {productName} 處於焦點時有效。" + #. Voice settings warning shown when push-to-talk is enabled in a browser. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:131 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:133 msgid "Push-to-talk only works while the browser tab is focused." msgstr "按住說話僅在瀏覽器分頁使用中時有效。" +#. Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:170 +msgid "Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity." +msgstr "「按住說話」將使用您在 {productName} 中設定的按鍵。請先設定按鍵,否則麥克風將改用語音活動。" + #. Modal description shown when a push-to-talk shortcut is pressed while the user is self-deafened. #: src/features/app/keybindings/keybind_manager/shared.ts:22 msgid "Push-to-talk won't unmute your microphone while you're deafened. Undeafen yourself to talk." msgstr "您處於耳機靜音狀態時,按住說話不會解除麥克風靜音。請先解除耳機靜音才能說話。" +#. Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:242 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config." +msgstr "必須先在 Hyprland 設定檔中綁定,「按住說話」才能在 {productName} 以外使用。" + +#. Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:236 +msgid "Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want." +msgstr "必須先在 Hyprland 設定檔中綁定,「按住說話」才能在 {productName} 以外使用。請新增適用於您 Hyprland 版本的設定行,並將 KEY 替換為您想要的按鍵。" + #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label in the theme studio. Keep it concise. #: src/features/theme_studio/ThemeStudio.tsx:52 @@ -26406,7 +26540,7 @@ msgid "Raw embed JSON" msgstr "原始嵌入 JSON" #. Button or menu action label in the keybinds tab. Turns a disabled built-in shortcut back on. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:50 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:52 msgid "Re-enable built-in shortcut" msgstr "重新啟用內建快速鍵" @@ -26565,13 +26699,6 @@ msgstr "最近被提及的訊息" msgid "Recent searches" msgstr "最近的搜尋" -#. Button label in the Linux Wayland input-access nagbar after the user changes OS permissions. -#. Button label in the keybind settings system-wide shortcuts section. -#: src/features/app/components/layout/app_layout/nagbars/LinuxInputAccessNagbar.tsx:36 -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:27 -msgid "Recheck" -msgstr "重新檢查" - #. placeholder {0}: ChannelUtils.getIcon(channel, {size: 16, className: styles.channelIcon}) #. placeholder {1}: channel.name #: src/features/invite/components/modals/InviteModal.tsx:360 @@ -26586,7 +26713,7 @@ msgid "Recommended" msgstr "推薦" #. Description for the focused voice profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:86 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:88 msgid "Recommended. Cleans up your mic for clear speech." msgstr "建議使用。讓您的麥克風更清晰地收音。" @@ -26600,6 +26727,11 @@ msgstr "重新連線" msgid "Reconnect succeeded. Pick a screen again if you want to resume your previous stream." msgstr "重新連線成功。如要繼續上次的串流,請再選取一次螢幕。" +#. Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:48 +msgid "Reconnecting to your desktop…" +msgstr "正在重新連線至桌面環境…" + #. Overlay status in the voice call view while reconnecting after a drop. Trailing ellipsis indicates in-progress. #: src/features/voice/components/VoiceCallView.tsx:94 msgid "Reconnecting..." @@ -26611,7 +26743,7 @@ msgid "Reconnecting…" msgstr "重新連線中…" #. Short label in the keyboard shortcuts keybind recorder. Keep it concise. -#: src/features/input/components/KeybindRecorder.tsx:28 +#: src/features/input/components/KeybindRecorder.tsx:36 msgid "Record shortcut" msgstr "錄製快速鍵" @@ -26851,7 +26983,7 @@ msgstr "相對" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Short label for push-to-talk release delay. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:78 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:80 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:94 msgid "Release delay" msgstr "放開後延遲" @@ -27739,13 +27871,13 @@ msgstr "已重新傳送" #: src/features/app/components/dialogs/shared/UnsavedChangesBannerContent.tsx:51 #: src/features/app/components/floating/ColorPickerPopover.tsx:190 -#: src/features/input/components/KeybindRecorder.tsx:459 +#: src/features/input/components/KeybindRecorder.tsx:499 #: src/features/messaging/components/modals/ImageCropModal.tsx:1022 msgid "Reset" msgstr "重設" #. Button or menu action label in the keybinds tab. Keep it concise. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:45 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:47 msgid "Reset \"{label}\" to built-in" msgstr "將「{label}」重設為內建設定" @@ -27866,7 +27998,7 @@ msgid "Reset to actual values" msgstr "重設為實際值" #. Button or menu action label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:37 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:39 msgid "Reset to built-in" msgstr "重設為內建" @@ -29509,7 +29641,7 @@ msgid "Select the account you want to sign in with on your new device." msgstr "選擇您想在新裝置上登入的帳號。" #. Aria label for the voice processing radio group in the voice settings tab. Keep it concise. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:125 msgid "Select voice processing mode" msgstr "選取語音處理模式" @@ -29814,7 +29946,7 @@ msgid "Sends raw mic audio with no processing." msgstr "傳送未經處理的原始麥克風音訊。" #. Description for the direct input profile option in the voice tab. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:90 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:92 msgid "Sends your audio untouched. Best if you're using external audio software." msgstr "直接傳送您的音訊。如果您使用外部音訊軟體,此為最佳選擇。" @@ -29913,12 +30045,12 @@ msgid "Set by the {preset} preset. Picking a value here switches to Custom." msgstr "由 {preset} 預設值決定。選擇此處的值將切換至「自訂」。" #. Short label in the keybinds tab. Keep it concise. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:29 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:31 msgid "Set custom shortcut" msgstr "設定自訂快速鍵" #. Label in the keybinds tab. Preserve {label}; it is inserted by code. -#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:55 +#: src/features/user/components/modals/tabs/keybinds_tab/DefaultShortcutRow.tsx:57 msgid "Set custom shortcut for \"{label}\"" msgstr "為「{label}」設定自訂快速鍵" @@ -30066,6 +30198,11 @@ msgstr "已將語音區域設為 {region}" msgid "Set the voice region to automatic" msgstr "將語音區域設為自動" +#. Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:40 +msgid "Set up" +msgstr "設定" + #. Title for the self-host setup wizard modal. #: src/features/app/components/setup/SelfHostedSetupWizardGate.tsx:74 msgid "Set up {productName}" @@ -30118,7 +30255,7 @@ msgid "Settings" msgstr "設定" #. Type chip suffix in tooltips for inline rendered fluxer://settings/user deep links. Lowercase. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:133 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:136 msgid "settings link" msgstr "設定連結" @@ -30330,8 +30467,8 @@ msgstr "短代碼" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Settings search synonym. Used to match this term when the user types it in the settings search bar. -#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:121 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:376 +#: src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx:123 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:375 #: src/features/user/components/settings_utils/search_index/KeybindsIndex.ts:38 #: src/features/user/components/settings_utils/section_registry/VoiceVideoSections.ts:110 msgid "Shortcut" @@ -32759,7 +32896,7 @@ msgstr "切換成舒適版面配置" msgid "Switch to dense layout" msgstr "切換成緊湊版面配置" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:666 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:673 msgid "Switch to direct input" msgstr "切換為直接輸入" @@ -33027,10 +33164,25 @@ msgid "System-managed install" msgstr "由系統管理的安裝" #. Settings subsection title for keyboard and mouse shortcuts that work outside the app. -#: src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx:14 +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:24 msgid "System-wide shortcuts" msgstr "全系統快速鍵" +#. Linux settings text when setting up system-wide shortcuts failed. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:137 +msgid "System-wide shortcuts could not be set up." +msgstr "無法設定全系統快速鍵。" + +#. Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:30 +msgid "System-wide: {trigger}" +msgstr "全系統:{trigger}" + +#. Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:35 +msgid "System-wide: Not assigned" +msgstr "全系統:未指定" + #. Description text in the theme tab content. #: src/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent.tsx:58 msgid "System: dark theme (automatically sync with your system's dark/light preference)" @@ -33612,12 +33764,12 @@ msgid "The page you're looking for doesn't exist or has been moved." msgstr "您要找的頁面不存在,或可能已移動。" #. OAuth authorize page error shown when the redirect_uri is not registered for this OAuth app. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:59 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:60 msgid "The provided redirect_uri is not registered for this application." msgstr "提供的 redirect_uri 未在此應用程式中註冊。" #. Body text in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:50 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:51 msgid "The provided redirect_uri must use HTTP or HTTPS." msgstr "提供的 redirect_uri 必須使用 HTTP 或 HTTPS。" @@ -33850,7 +34002,7 @@ msgid "This animation is too long or too large to crop. Try a shorter or smaller msgstr "這段動畫太長或太大,無法裁切。請嘗試較短或較小的檔案。" #. Short label in the authentication authorize flow. Keep the tone plain and specific. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:83 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:84 msgid "This application" msgstr "這個應用程式" @@ -33860,7 +34012,7 @@ msgid "This attachment has expired" msgstr "此附件已過期" #. OAuth authorize page error shown when a private bot cannot be added by non-owners. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:71 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:72 msgid "This bot is not public and cannot be added by other users." msgstr "此機器人非公開,其他使用者無法新增。" @@ -34081,6 +34233,16 @@ msgstr "此對話已無法使用。" msgid "This desktop build cannot install updates inside {productName}. Download the latest desktop installer instead." msgstr "這個桌面版本無法在 {productName} 內安裝更新。請改為下載最新的桌面安裝程式。" +#. Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:215 +msgid "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused." +msgstr "此桌面環境不支援全系統快速鍵。「按住說話」僅在 {productName} 處於焦點時有效。" + +#. Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:133 +msgid "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused." +msgstr "此桌面環境不支援全系統快速鍵。快速鍵在 {productName} 處於焦點時仍可使用。" + #. Desktop updater modal body for development or unpackaged builds. #: src/features/updater/commands/UpdaterModalCommands.tsx:65 msgid "This development build cannot update itself. Download a desktop installer to test the update flow." @@ -34294,6 +34456,11 @@ msgstr "這是您最後一個通行金鑰,而通行金鑰是您的第二道驗 msgid "This is your last passkey. Once it's gone you won't be able to use a passkey as your second factor." msgstr "這是您最後一個通行金鑰。刪除後,您將無法再以通行金鑰作為第二道驗證。" +#. Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:24 +msgid "This key works while {productName} is focused. Your desktop sets the system-wide key, under {settingsTabName} > {sectionName}." +msgstr "此按鍵在 {productName} 處於焦點時有效。全系統按鍵由桌面環境設定,可在「{settingsTabName}」>「{sectionName}」中查看。" + #. Extra file-too-large alert sentence shown when premium upload-limit upgrades are unavailable. #: src/features/messaging/components/alerts/FileSizeTooLargeModal.tsx:39 msgid "This limit is configured by your instance administrator." @@ -34305,7 +34472,7 @@ msgid "This link channel is marked for mature content and may open material that msgstr "此連結頻道已標示為成人內容,可能會開啟不適合部分使用者的內容。" #. Body of the link-unavailable alert. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:161 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:164 msgid "This link is not available for your account." msgstr "您的帳號無法使用此連結。" @@ -34778,6 +34945,21 @@ msgstr "標題列" msgid "Title bar platform" msgstr "標題列平台" +#. Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:81 +msgid "To change the keys, go to Settings > Apps > {productName}." +msgstr "若要變更按鍵,請前往「設定」>「應用程式」>「{productName}」。" + +#. Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:76 +msgid "To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}." +msgstr "若要變更按鍵,請前往「系統設定」>「鍵盤」>「快捷鍵」>「{productName}」。" + +#. Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:97 +msgid "To change the keys, open your desktop's keyboard shortcut settings." +msgstr "若要變更按鍵,請開啟桌面環境的鍵盤快速鍵設定。" + #: src/features/guild/components/modals/guild_tabs/GuildWebhooksTab.tsx:125 msgid "To create a webhook, open <0>{channelWebhooksSettingsPath}. You can still edit and organize all existing webhooks here." msgstr "若要建立 Webhook,請開啟<0>{channelWebhooksSettingsPath}。您仍然可以在這裡編輯和整理所有現有的 Webhook。" @@ -35872,7 +36054,7 @@ msgid "Unknown" msgstr "不明" #. OAuth authorize page error shown when the application, session, or request is invalid. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:46 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:47 msgid "Unknown application, expired session, or invalid authorization request." msgstr "不明應用程式、工作階段已過期或授權請求無效。" @@ -36410,6 +36592,11 @@ msgstr "最高 4K/60fps" msgid "Up to 720p at 30 FPS" msgstr "最高 720p 30 FPS" +#. Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:141 +msgid "Update {productName} to use system-wide shortcuts on Wayland." +msgstr "請更新 {productName},以在 Wayland 上使用全系統快速鍵。" + #. Message placeholder shown when the app cannot render a newer message type. #: src/features/channel/components/UnknownMessage.tsx:23 msgid "Update {productName} to view this message." @@ -36851,6 +37038,11 @@ msgstr "在整個應用程式中使用深色主題" msgid "Use default" msgstr "使用預設值" +#. Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:146 +msgid "Use direct input device access" +msgstr "直接存取輸入裝置" + #. Permission description in the channel-scoped permissions editor for the Use External Emoji permission. "Other communities" means custom emoji uploaded in a different Fluxer community. #: src/features/permissions/utils/PermissionLabelDescriptors.ts:260 msgid "Use emoji from other communities in this channel." @@ -38218,7 +38410,7 @@ msgid "Voice participants" msgstr "語音參與者" #. Voice settings radio group label for microphone processing profile. -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:661 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:668 #: src/features/voice/components/voice_connection_status/AudioProcessingModal.tsx:55 msgid "Voice processing" msgstr "語音處理" @@ -38264,7 +38456,7 @@ msgstr "語音解除耳機靜音" msgid "Voice unmute" msgstr "語音取消靜音" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:590 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:597 msgid "Voice uses your system output device in this browser. Change it in your system settings." msgstr "此瀏覽器中的語音通話使用系統輸出裝置。請在系統設定中變更。" @@ -38278,7 +38470,7 @@ msgstr "已作廢" #. Settings search synonym. Used to match this term when the user types it in the settings search bar. #. Subsection title in the voice tab. Keep it concise. #: src/features/i18n/utils/CommonMessageDescriptors.ts:383 -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:184 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:186 #: src/features/user/components/settings_utils/search_index/NotificationsIndex.ts:86 #: src/features/user/components/settings_utils/section_registry/SharedDescriptors.ts:183 msgid "Volume" @@ -38345,6 +38537,11 @@ msgstr "正在等待一個或多個所選頻道的慢速模式結束。" msgid "Waiting for you to..." msgstr "等您來…" +#. Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:44 +msgid "Waiting for your desktop…" +msgstr "正在等待桌面環境…" + #: src/features/input/utils/KeybindUtils.ts:107 msgctxt "keyboard-key-name" msgid "Wake up" @@ -38926,7 +39123,7 @@ msgstr "哪些對話" msgid "Which rule was broken?" msgstr "違反了哪一條規則?" -#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:370 +#: src/features/user/components/modals/tabs/UserVoiceTab.tsx:369 msgid "While push-to-talk is on, your shortcut controls your microphone, so the mute button is disabled." msgstr "啟用「按住說話」時,您的快速鍵會控制麥克風,因此靜音按鈕會停用。" @@ -39094,6 +39291,11 @@ msgstr "如果沒有這項權限,就必須使用「按住說話」。" msgid "Wordmark" msgstr "文字標誌" +#. Hint under a shortcut on Linux when the desktop owns system-wide shortcuts. The key shown here only works inside the app. {productName} is the app name. +#: src/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint.tsx:19 +msgid "Works while {productName} is focused" +msgstr "在 {productName} 處於焦點時有效" + #. Short label in the channel and chat textual attachment preview bottom sheet. Keep it concise. #. Short label in the channel and chat textual preview context menu. Keep it concise. #: src/features/channel/components/embeds/attachments/TextualAttachmentPreviewBottomSheet.tsx:23 @@ -39521,7 +39723,7 @@ msgid "You changed your tag while on {PREMIUM_PRODUCT_NAME}. It will reroll rand msgstr "您在訂閱 {PREMIUM_PRODUCT_NAME} 期間變更了標籤。除非續訂,否則您的 {PREMIUM_PRODUCT_NAME} 到期時,標籤將會隨機重新產生。" #. Body of the access-denied alert for clicked message links. -#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:141 +#: src/features/messaging/components/markdown/renderers/LinkRenderer.tsx:144 msgid "You do not have access to the channel where this message was sent." msgstr "您沒有權限存取傳送此訊息的頻道。" @@ -40056,7 +40258,7 @@ msgid "Your {premiumProductName} subscription has expired. You have lost all {pr msgstr "您的 {premiumProductName} 訂閱已到期。您已失去所有 {premiumProductName} 福利。請重新啟用訂閱以重新取得存取權。" #. OAuth authorize page error shown when the session expired mid-authorization. Keep plain. Preserve {productName}; it is inserted by code. -#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:41 +#: src/features/auth/components/pages/oauth_authorize_page/state/useAuthorizeFlow.ts:42 msgid "Your {productName} session expired. Sign in again to continue authorizing this application." msgstr "您的 {productName} 工作階段已過期。請重新登入以繼續授權此應用程式。" @@ -40144,6 +40346,16 @@ msgstr "您的社群至少需要 {minMemberCount, plural, one {# 位成員} othe msgid "Your credit card has been checked. If the card is valid, mature content will unlock shortly. You can close this tab and return to the app." msgstr "您的信用卡已完成檢查。如果卡片有效,成人內容將很快解鎖。您可以關閉此分頁並返回應用程式。" +#. Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:123 +msgid "Your desktop didn't set up system-wide shortcuts." +msgstr "桌面環境未設定全系統快速鍵。" + +#. Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name. +#: src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx:53 +msgid "Your desktop runs these shortcuts even while {productName} is not focused." +msgstr "即使 {productName} 不在焦點,桌面環境也會執行這些快速鍵。" + #. Body of the modal that asks the user to save an updated passkey. name is the name the user gave the passkey. Keep plain. #: src/features/auth/passkey_migration/PasskeyUpdateModal.tsx:28 msgid "Your device will ask you to save an updated passkey for {name}. It replaces the old one." diff --git a/fluxer_app/src/features/input/components/KeybindRecorder.tsx b/fluxer_app/src/features/input/components/KeybindRecorder.tsx index 8ee98bd0c..a2eefb0d1 100644 --- a/fluxer_app/src/features/input/components/KeybindRecorder.tsx +++ b/fluxer_app/src/features/input/components/KeybindRecorder.tsx @@ -4,15 +4,23 @@ import KeybindManager from '@app/features/app/keybindings/KeybindManager'; import styles from '@app/features/input/components/KeybindRecorder.module.css'; import { beginGlobalKeyCapture, + beginShortcutCapture, globalKeyEventToCombo, - isGlobalKeyEventModifierKey, + isModifierKeyCode, + shortcutCaptureKeyToCombo, + shortcutCaptureMouseToCombo, } from '@app/features/input/components/KeybindRecorderCapture'; +import GlobalShortcuts, {getGlobalShortcutsApi} from '@app/features/input/state/GlobalShortcuts'; import type {KeybindCommand, KeyCombo} from '@app/features/input/state/InputKeybind'; import {isGamepadButtonPressed} from '@app/features/input/utils/GamepadButtonUtils'; import {isKeybindModifierKey} from '@app/features/input/utils/KeybindComboUtils'; import {formatKeyCombo} from '@app/features/input/utils/KeybindUtils'; import {isKeyboardActivationKey} from '@app/features/input/utils/KeyboardUtils'; -import type {GlobalKeyEvent} from '@app/features/platform/types/Electron'; +import type { + GlobalKeyEvent, + GlobalShortcutCaptureEvent, + GlobalShortcutsApi, +} from '@app/features/platform/types/Electron'; import {remFromPx} from '@app/features/theme/layout/RemFromPx'; import {Button} from '@app/features/ui/button/Button'; import FocusRing from '@app/features/ui/focus_ring/FocusRing'; @@ -76,6 +84,8 @@ const MODIFIER_BOTH_SIDES_PAIRS: ReadonlyArray = [ ['MetaLeft', 'MetaRight'], ]; const RECORDABLE_MOUSE_BUTTONS = new Set([0, 1, 2, 3, 4]); +const CAPTURE_ONLY_MOUSE_BUTTONS = new Set([1, 3, 4]); +const HOOK_BACKENDS = new Set(['windows', 'macos', 'x11', 'evdev']); const normalizeKeyForCombo = (key: string): string => { if (key === 'Spacebar') return ' '; if (key === 'Break') return 'Pause'; @@ -134,6 +144,12 @@ const modifierOnlyCombo = (event: KeyboardEvent): KeyCombo => ({ modifierOnly: true, }); +function canCaptureFromHook(api: GlobalShortcutsApi): boolean { + if (typeof api.startCapture !== 'function') return false; + const backend = GlobalShortcuts.backend; + return backend !== null && HOOK_BACKENDS.has(backend) && GlobalShortcuts.hookError === null; +} + interface KeybindEditorPopoutProps { value: KeyCombo; defaultValue: KeyCombo | null; @@ -182,6 +198,10 @@ const KeybindEditorPopout: React.FC = ({ setPreviewCombo(null); setRecording(true); }, []); + const onSaveRef = useRef(onSave); + onSaveRef.current = onSave; + const globalRef = useRef(value.global); + globalRef.current = value.global; useEffect(() => { if (!recording) return; let committed = false; @@ -194,10 +214,10 @@ const KeybindEditorPopout: React.FC = ({ committed = true; const savedCombo: KeyCombo = { ...combo, - global: value.global, + global: globalRef.current, enabled: true, }; - onSave(savedCombo); + onSaveRef.current(savedCombo); finishRecording(savedCombo); }; const handleKeyDown = (event: KeyboardEvent) => { @@ -250,17 +270,16 @@ const KeybindEditorPopout: React.FC = ({ } commit(combo); }; - const handleGlobalKeyEvent = (event: GlobalKeyEvent) => { + const handleGlobalKey = (type: 'keydown' | 'keyup', baseCombo: KeyCombo) => { if (committed) return; - const baseCombo = globalKeyEventToCombo(event); - if (!baseCombo) return; const comboCode = baseCombo.code ?? baseCombo.key; - if (event.type === 'keydown') { + const isModifier = isModifierKeyCode(baseCombo.code); + if (type === 'keydown') { if (baseCombo.key === 'Escape') { cancelRecording(); return; } - if (isGlobalKeyEventModifierKey(event)) { + if (isModifier) { heldModifierCodes.add(comboCode); seenModifierCodes.add(comboCode); lastModifierCombo = {...baseCombo, modifierOnly: true}; @@ -277,12 +296,11 @@ const KeybindEditorPopout: React.FC = ({ commit(combo); return; } - if (!isGlobalKeyEventModifierKey(event)) return; + if (!isModifier) return; if (sawNonModifier) return; heldModifierCodes.delete(comboCode); if (heldModifierCodes.size > 0) return; - const combo = lastModifierCombo ?? globalKeyEventToCombo(event, {modifierOnly: true}); - if (!combo) return; + const combo = lastModifierCombo ?? {...baseCombo, modifierOnly: true}; if (!combo.key && !combo.code) return; const modifierFlagCount = (combo.shift ? 1 : 0) + @@ -300,6 +318,25 @@ const KeybindEditorPopout: React.FC = ({ } commit(combo); }; + const handleGlobalKeyEvent = (event: GlobalKeyEvent) => { + const baseCombo = globalKeyEventToCombo(event); + if (baseCombo) handleGlobalKey(event.type, baseCombo); + }; + const handleCaptureEvent = (event: GlobalShortcutCaptureEvent) => { + if (committed) return; + if (event.type === 'mousedown') { + if (event.button === null || !CAPTURE_ONLY_MOUSE_BUTTONS.has(event.button)) return; + if (GlobalShortcuts.status?.supportsMouseButtons !== true) return; + const combo = shortcutCaptureMouseToCombo(event); + if (!combo) return; + sawNonModifier = true; + commit(combo); + return; + } + if (event.type === 'mouseup') return; + const baseCombo = shortcutCaptureKeyToCombo(event); + if (baseCombo) handleGlobalKey(event.type, baseCombo); + }; const handleMouseDown = (event: MouseEvent) => { if (!RECORDABLE_MOUSE_BUTTONS.has(event.button)) return; event.preventDefault(); @@ -347,22 +384,25 @@ const KeybindEditorPopout: React.FC = ({ rafId = requestAnimationFrame(pollGamepads); }; rafId = requestAnimationFrame(pollGamepads); - const cancelGlobalCapture = beginGlobalKeyCapture(getElectronAPI(), (event) => { - handleGlobalKeyEvent(event); - }); + const globalShortcutsApi = getGlobalShortcutsApi(); + const cancelGlobalCapture = globalShortcutsApi + ? canCaptureFromHook(globalShortcutsApi) + ? beginShortcutCapture(globalShortcutsApi, handleCaptureEvent) + : null + : beginGlobalKeyCapture(getElectronAPI(), handleGlobalKeyEvent); window.addEventListener('keydown', handleKeyDown, true); window.addEventListener('keyup', handleKeyUp, true); window.addEventListener('mousedown', handleMouseDown, true); window.addEventListener('contextmenu', handleContextMenu, true); return () => { - cancelGlobalCapture(); + cancelGlobalCapture?.(); window.removeEventListener('keydown', handleKeyDown, true); window.removeEventListener('keyup', handleKeyUp, true); window.removeEventListener('mousedown', handleMouseDown, true); window.removeEventListener('contextmenu', handleContextMenu, true); cancelAnimationFrame(rafId); }; - }, [recording, onSave, cancelRecording, finishRecording, value.global]); + }, [recording, cancelRecording, finishRecording]); const handleClear = () => { setPreviewCombo(null); onClear?.(); diff --git a/fluxer_app/src/features/input/components/KeybindRecorderCapture.ts b/fluxer_app/src/features/input/components/KeybindRecorderCapture.ts index d0b570124..0834f7a09 100644 --- a/fluxer_app/src/features/input/components/KeybindRecorderCapture.ts +++ b/fluxer_app/src/features/input/components/KeybindRecorderCapture.ts @@ -1,7 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import type {KeyCombo} from '@app/features/input/state/InputKeybind'; -import type {GlobalKeyEvent} from '@app/features/platform/types/Electron'; +import type { + GlobalKeyEvent, + GlobalShortcutCaptureEvent, + GlobalShortcutsApi, +} from '@app/features/platform/types/Electron'; const GLOBAL_KEY_NAME_ALIASES: Readonly> = { Esc: 'Escape', @@ -198,23 +202,21 @@ const modifierStateToCombo = ( }; }; -export const isGlobalKeyEventModifierKey = (event: Pick): boolean => { - const code = codeForGlobalKeyName(event.keyName); - return ( - code === 'ShiftLeft' || - code === 'ShiftRight' || - code === 'ControlLeft' || - code === 'ControlRight' || - code === 'AltLeft' || - code === 'AltRight' || - code === 'MetaLeft' || - code === 'MetaRight' - ); -}; +const MODIFIER_CODES: ReadonlySet = new Set([ + 'ShiftLeft', + 'ShiftRight', + 'ControlLeft', + 'ControlRight', + 'AltLeft', + 'AltRight', + 'MetaLeft', + 'MetaRight', +]); + +export const isModifierKeyCode = (code: string | undefined): boolean => code !== undefined && MODIFIER_CODES.has(code); export const globalKeyEventToCombo = ( event: Pick, - options: {modifierOnly?: boolean} = {}, ): KeyCombo | null => { const code = codeForGlobalKeyName(event.keyName); if (!code) return null; @@ -222,7 +224,56 @@ export const globalKeyEventToCombo = ( key: keyForGlobalCode(code, event.shiftKey), code, ...modifierStateToCombo(event), - modifierOnly: options.modifierOnly || undefined, + }; +}; + +const captureModifierState = ( + event: GlobalShortcutCaptureEvent, +): Pick => ({ + altKey: event.alt, + ctrlKey: event.ctrl, + metaKey: event.meta, + shiftKey: event.shift, +}); + +export const shortcutCaptureKeyToCombo = (event: GlobalShortcutCaptureEvent): KeyCombo | null => { + const code = event.code ?? (event.key ? codeForGlobalKeyName(event.key) : null); + if (!code) return null; + const key = event.key !== null && event.key.length === 1 ? event.key : keyForGlobalCode(code, event.shift); + return {key, code, ...modifierStateToCombo(captureModifierState(event))}; +}; + +export const shortcutCaptureMouseToCombo = (event: GlobalShortcutCaptureEvent): KeyCombo | null => { + if (event.button === null) return null; + return {key: '', mouseButton: event.button, ...modifierStateToCombo(captureModifierState(event))}; +}; + +const beginCapture = ( + start: () => Promise, + stop: (token: T) => Promise, + subscribe: (callback: (event: E) => void) => () => void, + onEvent: (event: E) => void, +): (() => void) => { + let cancelled = false; + let unsubscribe: (() => void) | null = subscribe(onEvent); + const releaseSubscription = (): void => { + unsubscribe?.(); + unsubscribe = null; + }; + const started = start().then( + (token) => token, + () => null, + ); + void started.then((token) => { + if (token === null) releaseSubscription(); + }); + return () => { + if (cancelled) return; + cancelled = true; + releaseSubscription(); + void started.then((token) => { + if (token !== null) void stop(token); + }); }; }; @@ -236,29 +287,25 @@ export const beginGlobalKeyCapture = ( api: GlobalKeyCaptureApi | null | undefined, onEvent: (event: GlobalKeyEvent) => void, ): (() => void) => { - if (!api?.globalKeyHookStart || !api.globalKeyHookStop || !api.onGlobalKeyEvent) { + const {globalKeyHookStart, globalKeyHookStop, onGlobalKeyEvent} = api ?? {}; + if (!globalKeyHookStart || !globalKeyHookStop || !onGlobalKeyEvent) { return () => {}; } - const stopHook = api.globalKeyHookStop; - let cancelled = false; - let unsubscribe: (() => void) | null = api.onGlobalKeyEvent(onEvent); - const releaseSubscription = (): void => { - unsubscribe?.(); - unsubscribe = null; - }; - const started = api.globalKeyHookStart().then( - (ok) => ok, - () => false, + return beginCapture( + async () => ((await globalKeyHookStart()) ? true : null), + () => globalKeyHookStop(), + onGlobalKeyEvent, + onEvent, ); - void started.then((ok) => { - if (!ok) releaseSubscription(); - }); - return () => { - if (cancelled) return; - cancelled = true; - releaseSubscription(); - void started.then((ok) => { - if (ok) void stopHook(); - }); - }; }; + +export const beginShortcutCapture = ( + api: Pick, + onEvent: (event: GlobalShortcutCaptureEvent) => void, +): (() => void) => + beginCapture( + () => api.startCapture(), + (captureId) => api.stopCapture(captureId), + (callback) => api.onCapture(callback), + onEvent, + ); diff --git a/fluxer_app/src/features/input/state/GlobalShortcuts.ts b/fluxer_app/src/features/input/state/GlobalShortcuts.ts new file mode 100644 index 000000000..e0d8291a4 --- /dev/null +++ b/fluxer_app/src/features/input/state/GlobalShortcuts.ts @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import AppStorage from '@app/features/platform/state/PersistentStorage'; +import {Logger} from '@app/features/platform/utils/AppLogger'; +import {getElectronAPI} from '@app/features/ui/utils/NativeUtils'; +import type { + GlobalShortcutsApi, + GlobalShortcutsBackend, + GlobalShortcutsLinuxStatus, + GlobalShortcutsPortalState, + GlobalShortcutsPortalStatus, + GlobalShortcutsStatus, +} from '@app/types/electron.d'; +import {makeAutoObservable, runInAction} from 'mobx'; + +const KNOWN_BACKENDS: ReadonlySet = new Set([ + 'portal', + 'x11', + 'evdev', + 'windows', + 'macos', + 'none', +]); +const KNOWN_PORTAL_STATES: ReadonlySet = new Set([ + 'unknown', + 'probing', + 'unsupported', + 'not-set-up', + 'binding', + 'bound', + 'declined', + 'error', +]); + +export type GlobalShortcutsPendingAction = 'set-up' | 'configure' | 'direct-input' | 'recheck'; + +const PORTAL_ASSIGNED_ACTIONS_KEY = 'GlobalShortcuts:portalAssignedActions:v1'; + +const logger = new Logger('GlobalShortcuts'); + +function isTransientPortal(portal: GlobalShortcutsPortalStatus): boolean { + return portal.recovering === true || portal.state === 'probing' || portal.state === 'binding'; +} + +function settledPortalAssignment(status: GlobalShortcutsStatus): ReadonlyArray | null { + if (status.platform !== 'linux' || status.linux === null) return []; + const portal = status.linux.portal; + if (portal === null) return []; + if (portal.recovering === true) return null; + if (status.backend !== 'portal' && !(status.backend === 'none' && portal.state === 'unsupported')) return []; + switch (portal.state) { + case 'bound': + return portal.shortcuts + .filter((entry) => status.linux?.desktop === 'hyprland' || entry.triggerDescription !== null) + .map((entry) => entry.action); + case 'declined': + case 'unsupported': + return []; + default: + return null; + } +} + +function readStoredPortalAssignment(): ReadonlyArray { + try { + const stored = AppStorage.getJSON(PORTAL_ASSIGNED_ACTIONS_KEY); + if (!Array.isArray(stored)) return []; + return stored.filter((action): action is string => typeof action === 'string'); + } catch (error) { + logger.warn('Failed to read the stored system-wide shortcut assignment', error); + return []; + } +} + +function storePortalAssignment(actions: ReadonlyArray): void { + try { + AppStorage.setJSON(PORTAL_ASSIGNED_ACTIONS_KEY, actions); + } catch (error) { + logger.warn('Failed to store the system-wide shortcut assignment', error); + } +} + +function sameActions(a: ReadonlyArray, b: ReadonlyArray): boolean { + return a.length === b.length && a.every((action) => b.includes(action)); +} + +function findPortalTrigger(portal: GlobalShortcutsPortalStatus, action: string): string | null { + return portal.shortcuts.find((entry) => entry.action === action)?.triggerDescription ?? null; +} + +export function getGlobalShortcutsApi(): GlobalShortcutsApi | null { + const api = getElectronAPI()?.globalShortcuts; + if (!api || typeof api.sync !== 'function') return null; + return api; +} + +class GlobalShortcuts { + status: GlobalShortcutsStatus | null = null; + legacyWaylandNeedsUpdate = false; + pendingAction: GlobalShortcutsPendingAction | null = null; + private settledPortal: GlobalShortcutsPortalStatus | null = null; + private portalAssignedActions: ReadonlyArray = []; + private attached = false; + private statusRevision = 0; + private statusUnsubscribe: (() => void) | null = null; + + constructor() { + makeAutoObservable( + this, + {attached: false, statusRevision: false, statusUnsubscribe: false}, + {autoBind: true}, + ); + } + + attach(): void { + if (this.attached) return; + this.attached = true; + const api = getGlobalShortcutsApi(); + if (!api) { + void this.probeLegacyDesktop(); + return; + } + this.portalAssignedActions = readStoredPortalAssignment(); + this.statusUnsubscribe = api.onStatus((status) => { + this.statusRevision += 1; + this.applyStatus(status); + }); + const revision = this.statusRevision; + void api.getStatus().then( + (status) => { + if (!this.attached || this.statusRevision !== revision) return; + this.applyStatus(status); + }, + (error) => { + logger.warn('Failed to read global shortcuts status', error); + }, + ); + } + + detach(): void { + if (!this.attached) return; + this.attached = false; + this.statusRevision += 1; + this.statusUnsubscribe?.(); + this.statusUnsubscribe = null; + } + + private applyStatus(status: GlobalShortcutsStatus): void { + runInAction(() => { + this.status = status; + const portal = status.platform === 'linux' ? status.linux?.portal : null; + if (!portal || !KNOWN_PORTAL_STATES.has(portal.state) || portal.state === 'unknown') { + this.settledPortal = null; + } else if (!isTransientPortal(portal) || (this.settledPortal === null && portal.state === 'bound')) { + this.settledPortal = portal; + } + const assigned = settledPortalAssignment(status); + if (assigned === null || sameActions(assigned, this.portalAssignedActions)) return; + this.portalAssignedActions = assigned; + storePortalAssignment(assigned); + }); + } + + private async probeLegacyDesktop(): Promise { + const electronApi = getElectronAPI(); + if (!electronApi) return; + try { + const desktopInfo = await electronApi.getDesktopInfo(); + if (!desktopInfo.waylandSession) return; + const evdevStatus = await electronApi.linuxEvdevStatus?.(); + const needsUpdate = evdevStatus?.hasAccess !== true; + runInAction(() => { + this.legacyWaylandNeedsUpdate = needsUpdate; + }); + } catch (error) { + logger.warn('Failed to read legacy desktop input status', error); + } + } + + get backend(): GlobalShortcutsBackend | null { + const backend = this.status?.backend; + if (backend === undefined || !KNOWN_BACKENDS.has(backend)) return null; + return backend; + } + + get hooksActive(): boolean { + return this.status?.hooksActive === true; + } + + get hookError(): 'permission' | 'start-failed' | null { + return this.status?.hookError ?? null; + } + + get linux(): GlobalShortcutsLinuxStatus | null { + if (this.status?.platform !== 'linux') return null; + return this.status.linux; + } + + private get livePortal(): GlobalShortcutsPortalStatus | null { + const portal = this.linux?.portal; + if (!portal || !KNOWN_PORTAL_STATES.has(portal.state)) return null; + return portal; + } + + get portal(): GlobalShortcutsPortalStatus | null { + const portal = this.livePortal; + if (portal === null) return null; + if ((portal.recovering === true || portal.state === 'probing') && this.settledPortal !== null) { + return this.settledPortal; + } + return portal; + } + + get portalRecovering(): boolean { + return this.livePortal?.recovering === true; + } + + get isPortalBackend(): boolean { + return this.backend === 'portal' && this.portal !== null; + } + + getPortalTrigger(action: string): string | null { + const portal = this.portal; + if (!this.isPortalBackend || portal?.state !== 'bound') return null; + return findPortalTrigger(portal, action); + } + + isPortalActionAssigned(action: string): boolean { + return this.portalAssignedActions.includes(action); + } + + async setUp(): Promise { + await this.runAction('set-up', (api) => api.setUp()); + } + + async configure(): Promise { + await this.runAction('configure', async (api) => { + await api.configure(); + return null; + }); + } + + async setDirectInputEnabled(enabled: boolean): Promise { + await this.runAction('direct-input', (api) => api.setDirectInputEnabled(enabled)); + } + + async recheck(): Promise { + await this.runAction('recheck', (api) => api.recheck()); + } + + private async runAction( + action: GlobalShortcutsPendingAction, + run: (api: GlobalShortcutsApi) => Promise, + ): Promise { + const api = getGlobalShortcutsApi(); + if (!api || this.pendingAction !== null) return; + runInAction(() => { + this.pendingAction = action; + }); + try { + const status = await run(api); + if (status && this.attached) { + this.statusRevision += 1; + this.applyStatus(status); + } + } catch (error) { + logger.error(`Global shortcuts action ${action} failed`, error); + } finally { + runInAction(() => { + this.pendingAction = null; + }); + } + } +} + +export default new GlobalShortcuts(); diff --git a/fluxer_app/src/features/input/state/HoldSources.test.ts b/fluxer_app/src/features/input/state/HoldSources.test.ts new file mode 100644 index 000000000..d65bd9d51 --- /dev/null +++ b/fluxer_app/src/features/input/state/HoldSources.test.ts @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {HoldSources} from '@app/features/input/state/HoldSources'; +import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'; + +const RELEASE_DELAY_MS = 250; + +describe('HoldSources', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('keeps push-to-talk open while a portal source is held after the DOM source is released, for example on window blur', () => { + const sources = new HoldSources(() => RELEASE_DELAY_MS); + const onReleased = vi.fn(); + expect(sources.press('voice_push_to_talk', 'custom:a')).toBe(true); + expect(sources.press('voice_push_to_talk', 'portal:voice_push_to_talk')).toBe(false); + sources.release('voice_push_to_talk', 'custom:a', onReleased); + vi.advanceTimersByTime(RELEASE_DELAY_MS * 4); + expect(onReleased).not.toHaveBeenCalled(); + sources.release('voice_push_to_talk', 'portal:voice_push_to_talk', onReleased); + vi.advanceTimersByTime(RELEASE_DELAY_MS - 1); + expect(onReleased).not.toHaveBeenCalled(); + vi.advanceTimersByTime(1); + expect(onReleased).toHaveBeenCalledTimes(1); + }); + + it('cancels the pending release when a source is pressed again', () => { + const sources = new HoldSources(() => RELEASE_DELAY_MS); + const onReleased = vi.fn(); + sources.press('voice_push_to_talk', 'custom:a'); + sources.release('voice_push_to_talk', 'custom:a', onReleased); + expect(sources.press('voice_push_to_talk_priority', 'custom:b')).toBe(true); + expect(sources.prioritySpeakerHeld).toBe(true); + vi.advanceTimersByTime(RELEASE_DELAY_MS * 4); + expect(onReleased).not.toHaveBeenCalled(); + sources.release('voice_push_to_talk_priority', 'custom:b', onReleased); + expect(sources.prioritySpeakerHeld).toBe(false); + vi.advanceTimersByTime(RELEASE_DELAY_MS); + expect(onReleased).toHaveBeenCalledTimes(1); + }); + + it('ignores releases for sources that were never pressed', () => { + const sources = new HoldSources(() => RELEASE_DELAY_MS); + const onReleased = vi.fn(); + sources.release('voice_push_to_talk', 'custom:unknown', onReleased); + sources.release('voice_push_to_mute', 'custom:unknown', onReleased); + sources.press('voice_push_to_talk', 'custom:a'); + sources.release('voice_push_to_talk', 'custom:unknown', onReleased); + vi.advanceTimersByTime(RELEASE_DELAY_MS * 4); + expect(onReleased).not.toHaveBeenCalled(); + }); + + it('clears held sources and the pending release on reset', () => { + const sources = new HoldSources(() => RELEASE_DELAY_MS); + const onReleased = vi.fn(); + sources.press('voice_push_to_talk', 'custom:a'); + sources.press('voice_push_to_talk', 'custom:b'); + sources.release('voice_push_to_talk', 'custom:a', onReleased); + sources.release('voice_push_to_talk', 'custom:b', onReleased); + sources.resetPushToTalk(); + vi.advanceTimersByTime(RELEASE_DELAY_MS * 4); + expect(onReleased).not.toHaveBeenCalled(); + sources.press('voice_push_to_talk', 'custom:a'); + sources.resetPushToTalk(); + sources.release('voice_push_to_talk', 'custom:a', onReleased); + vi.advanceTimersByTime(RELEASE_DELAY_MS * 4); + expect(onReleased).not.toHaveBeenCalled(); + expect(sources.press('voice_push_to_talk', 'custom:a')).toBe(true); + }); + + it('releases push-to-mute immediately when its last source is released', () => { + const sources = new HoldSources(() => RELEASE_DELAY_MS); + const onReleased = vi.fn(); + expect(sources.press('voice_push_to_mute', 'custom:a')).toBe(true); + expect(sources.press('voice_push_to_mute', 'gamepad:a')).toBe(false); + sources.release('voice_push_to_mute', 'custom:a', onReleased); + expect(onReleased).not.toHaveBeenCalled(); + sources.release('voice_push_to_mute', 'gamepad:a', onReleased); + expect(onReleased).toHaveBeenCalledTimes(1); + sources.press('voice_push_to_mute', 'custom:a'); + sources.resetPushToMute(); + sources.release('voice_push_to_mute', 'custom:a', onReleased); + expect(onReleased).toHaveBeenCalledTimes(1); + }); +}); diff --git a/fluxer_app/src/features/input/state/HoldSources.ts b/fluxer_app/src/features/input/state/HoldSources.ts new file mode 100644 index 000000000..a34555ce6 --- /dev/null +++ b/fluxer_app/src/features/input/state/HoldSources.ts @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import type {HoldAction} from '@app/features/app/keybindings/utils/RuntimeKeybinds'; + +export class HoldSources { + private readonly pttSources = new Set(); + private readonly prioritySources = new Set(); + private readonly ptmSources = new Set(); + private readonly priorityVadSources = new Set(); + private releaseTimer: ReturnType | null = null; + + constructor(private readonly getReleaseDelay: () => number) {} + + get prioritySpeakerHeld(): boolean { + return this.prioritySources.size > 0 || this.priorityVadSources.size > 0; + } + + press(action: HoldAction, sourceId: string): boolean { + switch (action) { + case 'voice_push_to_talk': + case 'voice_push_to_talk_priority': { + if (this.pttSources.has(sourceId)) return false; + const wasEmpty = this.pttSources.size === 0; + this.pttSources.add(sourceId); + if (action === 'voice_push_to_talk_priority') this.prioritySources.add(sourceId); + this.clearReleaseTimer(); + return wasEmpty; + } + case 'voice_push_to_mute': + return this.addSource(this.ptmSources, sourceId); + case 'voice_priority_vad': + return this.addSource(this.priorityVadSources, sourceId); + } + } + + release(action: HoldAction, sourceId: string, onReleased?: () => void): void { + switch (action) { + case 'voice_push_to_talk': + case 'voice_push_to_talk_priority': { + if (!this.pttSources.delete(sourceId)) return; + this.prioritySources.delete(sourceId); + if (this.pttSources.size > 0) return; + this.clearReleaseTimer(); + this.releaseTimer = setTimeout(() => { + this.releaseTimer = null; + onReleased?.(); + }, this.getReleaseDelay()); + return; + } + case 'voice_push_to_mute': + if (this.removeSource(this.ptmSources, sourceId)) onReleased?.(); + return; + case 'voice_priority_vad': + if (this.removeSource(this.priorityVadSources, sourceId)) onReleased?.(); + return; + } + } + + resetPushToTalk(): void { + this.pttSources.clear(); + this.prioritySources.clear(); + this.clearReleaseTimer(); + } + + resetPushToMute(): void { + this.ptmSources.clear(); + this.priorityVadSources.clear(); + } + + private addSource(sources: Set, sourceId: string): boolean { + if (sources.has(sourceId)) return false; + sources.add(sourceId); + return sources.size === 1; + } + + private removeSource(sources: Set, sourceId: string): boolean { + if (!sources.delete(sourceId)) return false; + return sources.size === 0; + } + + private clearReleaseTimer(): void { + if (this.releaseTimer === null) return; + clearTimeout(this.releaseTimer); + this.releaseTimer = null; + } +} diff --git a/fluxer_app/src/features/input/state/InputKeybind.ts b/fluxer_app/src/features/input/state/InputKeybind.ts index d5c1a3c3b..cdb9936c1 100644 --- a/fluxer_app/src/features/input/state/InputKeybind.ts +++ b/fluxer_app/src/features/input/state/InputKeybind.ts @@ -1,6 +1,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +import type {HoldAction} from '@app/features/app/keybindings/utils/RuntimeKeybinds'; import {COPY_TEXT_DESCRIPTOR, DELETE_MESSAGE_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors'; +import GlobalShortcuts from '@app/features/input/state/GlobalShortcuts'; +import {HoldSources} from '@app/features/input/state/HoldSources'; import { ADD_REACTION_DESCRIPTOR, ANSWER_THE_INCOMING_CALL_DESCRIPTOR, @@ -211,7 +214,7 @@ const KEYBIND_COMMAND_VALUES = [ export type KeybindCommand = (typeof KEYBIND_COMMAND_VALUES)[number]; const KEYBIND_COMMAND_SET = new Set(KEYBIND_COMMAND_VALUES); -const isKeybindCommand = (value: unknown): value is KeybindCommand => +export const isKeybindCommand = (value: unknown): value is KeybindCommand => typeof value === 'string' && KEYBIND_COMMAND_SET.has(value); export interface KeyCombo { @@ -1041,9 +1044,10 @@ class Keybind { private i18n: I18n | null = null; private initialized = false; private keyboardShortcutsOverlayCombo: KeyCombo = {...DEFAULT_KEYBOARD_SHORTCUTS_OVERLAY_COMBO}; + private holdSources = new HoldSources(() => this.pushToTalkReleaseDelay); constructor() { - makeAutoObservable(this, {}, {autoBind: true}); + makeAutoObservable(this, {holdSources: false}, {autoBind: true}); void this.initPersistence(); } @@ -1353,7 +1357,12 @@ class Keybind { } hasPushToTalkKeybind(): boolean { - return this.hasActiveBindingFor('voice_push_to_talk') || this.hasActiveBindingFor('voice_push_to_talk_priority'); + return ( + this.hasActiveBindingFor('voice_push_to_talk') || + this.hasActiveBindingFor('voice_push_to_talk_priority') || + GlobalShortcuts.isPortalActionAssigned('voice_push_to_talk') || + GlobalShortcuts.isPortalActionAssigned('voice_push_to_talk_priority') + ); } isPushToTalkEffective(): boolean { @@ -1367,24 +1376,21 @@ class Keybind { }); } - handlePushToTalkPress(): boolean { - runInAction(() => { - this.pushToTalkHeld = true; - }); - return true; + pressHoldSource(action: HoldAction, sourceId: string): boolean { + const activated = this.holdSources.press(action, sourceId); + this.prioritySpeakerHeld = this.holdSources.prioritySpeakerHeld; + return activated; } - handlePushToTalkRelease(): boolean { - runInAction(() => { - this.pushToTalkHeld = false; - }); - return true; + releaseHoldSource(action: HoldAction, sourceId: string, onReleased?: () => void): void { + this.holdSources.release(action, sourceId, onReleased); + this.prioritySpeakerHeld = this.holdSources.prioritySpeakerHeld; } resetPushToTalkState(): void { - runInAction(() => { - this.pushToTalkHeld = false; - }); + this.holdSources.resetPushToTalk(); + this.pushToTalkHeld = false; + this.prioritySpeakerHeld = this.holdSources.prioritySpeakerHeld; } setPushToMuteHeld(held: boolean): void { @@ -1394,7 +1400,9 @@ class Keybind { } hasPushToMuteKeybind(): boolean { - return this.hasActiveBindingFor('voice_push_to_mute'); + return ( + this.hasActiveBindingFor('voice_push_to_mute') || GlobalShortcuts.isPortalActionAssigned('voice_push_to_mute') + ); } isPushToMuteEffective(): boolean { @@ -1402,19 +1410,9 @@ class Keybind { } resetPushToMuteState(): void { - runInAction(() => { - this.pushToMuteHeld = false; - }); - } - - setPrioritySpeakerHeld(held: boolean): void { - runInAction(() => { - this.prioritySpeakerHeld = held; - }); - } - - resetPrioritySpeakerState(): void { - this.setPrioritySpeakerHeld(false); + this.holdSources.resetPushToMute(); + this.pushToMuteHeld = false; + this.prioritySpeakerHeld = this.holdSources.prioritySpeakerHeld; } muteActions(actions: Iterable): void { diff --git a/fluxer_app/src/features/input/utils/PushToTalkHint.ts b/fluxer_app/src/features/input/utils/PushToTalkHint.ts new file mode 100644 index 000000000..a65143ce2 --- /dev/null +++ b/fluxer_app/src/features/input/utils/PushToTalkHint.ts @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import GlobalShortcuts from '@app/features/input/state/GlobalShortcuts'; +import Keybind from '@app/features/input/state/InputKeybind'; +import {formatKeyCombo} from '@app/features/input/utils/KeybindUtils'; +import type {I18n, MessageDescriptor} from '@lingui/core'; +import {msg} from '@lingui/core/macro'; + +const PUSH_TO_TALK_ACTIONS = ['voice_push_to_talk', 'voice_push_to_talk_priority'] as const; + +const PUSH_TO_TALK_ON_WITHOUT_KEY_DESCRIPTOR = msg({ + message: 'Push-to-talk is on. Hold your push-to-talk key to speak.', + comment: + 'Tooltip and screen reader label on the mic button when push-to-talk is on but the key has no name to show, for example when the Linux desktop assigns it.', +}); + +function getPushToTalkHint(i18n: I18n): string | null { + for (const action of PUSH_TO_TALK_ACTIONS) { + const trigger = GlobalShortcuts.getPortalTrigger(action); + if (trigger) return trigger; + const formatted = formatKeyCombo(i18n, Keybind.getByAction(action).combo); + if (formatted) return formatted; + } + return null; +} + +export function getPushToTalkHoldLabel(i18n: I18n, descriptor: MessageDescriptor): string { + const pushToTalkHint = getPushToTalkHint(i18n); + if (pushToTalkHint === null) return i18n._(PUSH_TO_TALK_ON_WITHOUT_KEY_DESCRIPTOR); + return i18n._(descriptor, {pushToTalkHint}); +} diff --git a/fluxer_app/src/features/permissions/system/state/NativePermission.ts b/fluxer_app/src/features/permissions/system/state/NativePermission.ts index f102be6b3..9d1ea27ae 100644 --- a/fluxer_app/src/features/permissions/system/state/NativePermission.ts +++ b/fluxer_app/src/features/permissions/system/state/NativePermission.ts @@ -1,17 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants'; import MacPermissions from '@app/features/permissions/system/state/MacPermissions'; import type {NativePermissionResult} from '@app/features/permissions/system/utils/NativePermissions'; -import AppStorage from '@app/features/platform/state/PersistentStorage'; import {Logger} from '@app/features/platform/utils/AppLogger'; import {getElectronAPI, getNativePlatform, isDesktop, type NativePlatform} from '@app/features/ui/utils/NativeUtils'; import {makeAutoObservable, runInAction} from 'mobx'; -export type LinuxInputAccessNagbarReason = 'global-hotkeys' | 'push-to-talk' | 'settings'; -export type LinuxInputAccessStatus = 'unknown' | 'granted' | 'blocked'; - -const LINUX_INPUT_ACCESS_NAGBAR_DISMISSED_STORAGE_KEY = 'NativePermission:linuxInputAccessNagbarDismissed'; const logger = new Logger('NativePermission'); class NativePermission { @@ -19,14 +13,6 @@ class NativePermission { private _isDesktop = false; private _platform: NativePlatform = 'unknown'; private _waylandSession = false; - private _linuxFlatpak = false; - private _linuxInputAccessStatus: LinuxInputAccessStatus = 'unknown'; - private _linuxInputAccessNagbarRequested = false; - private _linuxInputAccessNagbarDismissed = - AppStorage.getItem(LINUX_INPUT_ACCESS_NAGBAR_DISMISSED_STORAGE_KEY) === 'true'; - private _linuxInputAccessNagbarReason: LinuxInputAccessNagbarReason | null = null; - private _linuxInputAccessGrantNeedsRelogin = false; - private _linuxInputAccessGrantError: string | null = null; constructor() { makeAutoObservable(this, {}, {autoBind: true}); @@ -37,16 +23,10 @@ class NativePermission { const desktop = isDesktop(); const platform = await getNativePlatform(); let waylandSession = false; - let linuxFlatpak = false; - let linuxInputAccessStatus: LinuxInputAccessStatus = 'unknown'; if (desktop && platform === 'linux') { try { - const electronApi = getElectronAPI(); - const desktopInfo = await electronApi?.getDesktopInfo(); + const desktopInfo = await getElectronAPI()?.getDesktopInfo(); waylandSession = Boolean(desktopInfo?.waylandSession); - linuxFlatpak = Boolean(desktopInfo?.flatpak); - const linuxInputAccess = await electronApi?.linuxEvdevStatus?.(); - linuxInputAccessStatus = linuxInputAccess?.hasAccess ? 'granted' : 'blocked'; } catch (error) { logger.warn('Failed to read desktop session type', error); } @@ -55,17 +35,11 @@ class NativePermission { desktop, platform, waylandSession, - linuxFlatpak, }); runInAction(() => { this._isDesktop = desktop; this._platform = platform; this._waylandSession = waylandSession; - this._linuxFlatpak = linuxFlatpak; - this._linuxInputAccessStatus = linuxInputAccessStatus; - if (linuxInputAccessStatus === 'granted') { - this.resolveLinuxInputAccessNagbar(); - } this._initialized = true; }); } @@ -90,10 +64,6 @@ class NativePermission { return this._isDesktop && this._platform === 'linux' && this._waylandSession; } - get isLinuxFlatpakDesktop(): boolean { - return this._isDesktop && this._platform === 'linux' && this._linuxFlatpak; - } - get platform(): NativePlatform { return this._platform; } @@ -106,56 +76,6 @@ class NativePermission { return MacPermissions.statuses['input-monitoring'] === 'granted'; } - get linuxInputAccessStatus(): LinuxInputAccessStatus { - return this._linuxInputAccessStatus; - } - - get linuxInputAccessGrantNeedsRelogin(): boolean { - return this._linuxInputAccessGrantNeedsRelogin; - } - - get linuxInputAccessGrantError(): string | null { - return this._linuxInputAccessGrantError; - } - - get linuxInputAccessNagbarReason(): LinuxInputAccessNagbarReason | null { - return this._linuxInputAccessNagbarReason; - } - - get shouldShowLinuxInputAccessNagbar(): boolean { - return ( - this._linuxInputAccessNagbarRequested && - !this._linuxInputAccessNagbarDismissed && - this._isDesktop && - this._platform === 'linux' && - this._waylandSession && - this._linuxInputAccessStatus !== 'granted' - ); - } - - requestLinuxInputAccessNagbar(reason: LinuxInputAccessNagbarReason): void { - if (this._linuxInputAccessNagbarDismissed) return; - this._linuxInputAccessNagbarRequested = true; - this._linuxInputAccessNagbarReason = reason; - } - - dismissLinuxInputAccessNagbar(): void { - this._linuxInputAccessNagbarDismissed = true; - AppStorage.setItem(LINUX_INPUT_ACCESS_NAGBAR_DISMISSED_STORAGE_KEY, 'true'); - this.clearLinuxInputAccessNagbarRequest(); - } - - private clearLinuxInputAccessNagbarRequest(): void { - this._linuxInputAccessNagbarRequested = false; - this._linuxInputAccessNagbarReason = null; - } - - private resolveLinuxInputAccessNagbar(): void { - this.clearLinuxInputAccessNagbarRequest(); - this._linuxInputAccessNagbarDismissed = false; - AppStorage.removeItem(LINUX_INPUT_ACCESS_NAGBAR_DISMISSED_STORAGE_KEY); - } - async recheckInputMonitoring(): Promise { return MacPermissions.refreshKind('input-monitoring'); } @@ -163,56 +83,6 @@ class NativePermission { setInputMonitoringStatus(status: NativePermissionResult): void { MacPermissions.applyPermissionResult('input-monitoring', status); } - - async recheckLinuxInputAccess(): Promise { - if (!this._isDesktop || this._platform !== 'linux' || !this._waylandSession) { - return 'granted'; - } - const status = await getElectronAPI()?.linuxEvdevStatus?.(); - const nextStatus: LinuxInputAccessStatus = status?.hasAccess ? 'granted' : 'blocked'; - runInAction(() => { - this._linuxInputAccessStatus = nextStatus; - if (nextStatus === 'granted') { - this._linuxInputAccessGrantNeedsRelogin = false; - this._linuxInputAccessGrantError = null; - this.resolveLinuxInputAccessNagbar(); - } - }); - logger.debug('Rechecked Linux input access', {status: nextStatus}); - return nextStatus; - } - - async grantLinuxInputAccess(): Promise<{success: boolean; needsRelogin: boolean; error?: string}> { - if (!this._isDesktop || this._platform !== 'linux' || !this._waylandSession) { - return {success: false, needsRelogin: false, error: 'Not a Linux Wayland session'}; - } - if (this._linuxFlatpak) { - return { - success: false, - needsRelogin: false, - error: `Enable Flatpak input device access, then restart ${PRODUCT_NAME}`, - }; - } - const electronApi = getElectronAPI(); - if (!electronApi?.linuxEvdevGrantAccess) { - return {success: false, needsRelogin: false, error: 'Input access helper is unavailable'}; - } - runInAction(() => { - this._linuxInputAccessGrantNeedsRelogin = false; - this._linuxInputAccessGrantError = null; - }); - const result = await electronApi.linuxEvdevGrantAccess(); - const nextStatus = result.success && !result.needsRelogin ? await this.recheckLinuxInputAccess() : 'blocked'; - runInAction(() => { - this._linuxInputAccessGrantNeedsRelogin = Boolean(result.success && result.needsRelogin); - this._linuxInputAccessGrantError = result.success ? null : (result.error ?? 'Input access could not be enabled'); - this._linuxInputAccessStatus = nextStatus; - if (nextStatus === 'granted') { - this.resolveLinuxInputAccessNagbar(); - } - }); - return result; - } } export default new NativePermission(); diff --git a/fluxer_app/src/features/platform/types/Electron.ts b/fluxer_app/src/features/platform/types/Electron.ts index 530f1b412..d2d7eb009 100644 --- a/fluxer_app/src/features/platform/types/Electron.ts +++ b/fluxer_app/src/features/platform/types/Electron.ts @@ -414,11 +414,7 @@ export interface ElectronAPI { readableEventDevices: number; inInputGroup: boolean; }>; - linuxEvdevGrantAccess: () => Promise<{ - success: boolean; - needsRelogin: boolean; - error?: string; - }>; + globalShortcuts?: GlobalShortcutsApi; onGlobalKeyEvent: (callback: (event: GlobalKeyEvent) => void) => () => void; onGlobalMouseEvent: (callback: (event: GlobalMouseEvent) => void) => () => void; onGlobalKeybindTriggered: (callback: (event: GlobalKeybindTriggeredEvent) => void) => () => void; @@ -615,6 +611,113 @@ export interface GlobalKeybindTriggeredEvent { type: 'keydown' | 'keyup'; } +export type GlobalShortcutsBackend = 'portal' | 'x11' | 'evdev' | 'windows' | 'macos' | 'none'; + +export type GlobalShortcutsPortalState = + | 'unknown' + | 'probing' + | 'unsupported' + | 'not-set-up' + | 'binding' + | 'bound' + | 'declined' + | 'error'; + +export interface GlobalShortcutsPortalShortcut { + action: string; + triggerDescription: string | null; +} + +export interface GlobalShortcutsPortalStatus { + state: GlobalShortcutsPortalState; + version: number | null; + canConfigure: boolean; + canRecheck: boolean; + portalAppId: string | null; + shortcuts: Array; + error: string | null; + recovering: boolean; +} + +export interface GlobalShortcutsLinuxStatus { + session: 'wayland' | 'x11' | 'unknown'; + sandbox: 'flatpak' | 'none'; + desktop: 'kde' | 'gnome' | 'hyprland' | 'other'; + portal: GlobalShortcutsPortalStatus | null; + directInput: {available: boolean; enabled: boolean; locked: boolean}; +} + +export interface GlobalShortcutsStatus { + backend: GlobalShortcutsBackend; + platform: 'linux' | 'windows' | 'macos'; + linux: GlobalShortcutsLinuxStatus | null; + hooksActive: boolean; + hookError: 'permission' | 'start-failed' | null; + supportsMouseButtons: boolean; + supportsModifierOnly: boolean; +} + +export interface GlobalShortcutCombo { + code?: string; + key: string; + ctrl: boolean; + alt: boolean; + shift: boolean; + meta: boolean; + mouseButton?: number; + modifierOnly?: boolean; + bothSides?: boolean; +} + +export interface GlobalShortcutBinding { + sourceId: string; + action: string; + combo: GlobalShortcutCombo; +} + +export interface GlobalShortcutActionDefinition { + action: string; + description: string; + preferredCombo: GlobalShortcutCombo | null; +} + +export interface GlobalShortcutsSyncPayload { + bindings: Array; + actions: Array; +} + +export interface GlobalShortcutEvent { + action: string; + sourceId: string; + phase: 'press' | 'release'; +} + +export interface GlobalShortcutCaptureEvent { + type: 'keydown' | 'keyup' | 'mousedown' | 'mouseup'; + code: string | null; + key: string | null; + button: number | null; + ctrl: boolean; + alt: boolean; + shift: boolean; + meta: boolean; +} + +export interface GlobalShortcutsApi { + sync: (payload: GlobalShortcutsSyncPayload) => Promise; + setPaused: (paused: boolean) => Promise; + getStatus: () => Promise; + onStatus: (callback: (status: GlobalShortcutsStatus) => void) => () => void; + onEvent: (callback: (event: GlobalShortcutEvent) => void) => () => void; + setUp: () => Promise; + configure: () => Promise; + setDirectInputEnabled: (enabled: boolean) => Promise; + recheck: () => Promise; + startCapture: () => Promise; + stopCapture: (captureId: number) => Promise; + onCapture: (callback: (event: GlobalShortcutCaptureEvent) => void) => () => void; +} + export type SpellcheckEngine = 'auto' | 'hunspell' | 'system'; export interface SpellcheckState { diff --git a/fluxer_app/src/features/user/components/modals/tabs/KeybindsTab.module.css b/fluxer_app/src/features/user/components/modals/tabs/KeybindsTab.module.css index cce52fdeb..bd09db942 100644 --- a/fluxer_app/src/features/user/components/modals/tabs/KeybindsTab.module.css +++ b/fluxer_app/src/features/user/components/modals/tabs/KeybindsTab.module.css @@ -297,3 +297,13 @@ line-height: 1.4; color: var(--text-primary-muted); } + +.systemShortcutsCommands { + margin: 0; + overflow-x: auto; + font-family: var(--font-mono); + font-size: 0.75rem; + line-height: 1.4; + color: var(--text-primary); + white-space: pre; +} diff --git a/fluxer_app/src/features/user/components/modals/tabs/KeybindsTab.tsx b/fluxer_app/src/features/user/components/modals/tabs/KeybindsTab.tsx index 2e5714efb..d759d4acb 100644 --- a/fluxer_app/src/features/user/components/modals/tabs/KeybindsTab.tsx +++ b/fluxer_app/src/features/user/components/modals/tabs/KeybindsTab.tsx @@ -2,7 +2,7 @@ import {remFromPx} from '@app/features/theme/layout/RemFromPx'; import {Input} from '@app/features/ui/components/form/FormInput'; -import {LinuxInputAccessSection} from '@app/features/user/components/modals/tabs/components/LinuxInputAccessSection'; +import {SystemShortcutsSection} from '@app/features/user/components/modals/tabs/components/SystemShortcutsSection'; import styles from '@app/features/user/components/modals/tabs/KeybindsTab.module.css'; import {CustomKeybindsList} from '@app/features/user/components/modals/tabs/keybinds_tab/CustomKeybindsList'; import {DefaultKeybindsList} from '@app/features/user/components/modals/tabs/keybinds_tab/DefaultKeybindsList'; @@ -49,7 +49,7 @@ const KeybindsTab: React.FC = observer(() => { /> - + ); diff --git a/fluxer_app/src/features/user/components/modals/tabs/UserVoiceTab.tsx b/fluxer_app/src/features/user/components/modals/tabs/UserVoiceTab.tsx index b7840dcca..27c80fdec 100644 --- a/fluxer_app/src/features/user/components/modals/tabs/UserVoiceTab.tsx +++ b/fluxer_app/src/features/user/components/modals/tabs/UserVoiceTab.tsx @@ -29,7 +29,9 @@ import {WarningAlert} from '@app/features/ui/warning_alert/WarningAlert'; import {CompactComboboxRow} from '@app/features/user/components/modals/tabs/components/CompactComboboxRow'; import {EntranceSoundSection} from '@app/features/user/components/modals/tabs/components/EntranceSoundSection'; import {MicTestSection} from '@app/features/user/components/modals/tabs/components/MicTestSection'; +import {SystemShortcutsPushToTalkAlert} from '@app/features/user/components/modals/tabs/components/SystemShortcutsSection'; import {useMediaPermission} from '@app/features/user/components/modals/tabs/hooks/useMediaPermission'; +import {SystemShortcutRowHint} from '@app/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint'; import styles from '@app/features/user/components/modals/tabs/UserVoiceTab.module.css'; import * as VoiceSettingsCommands from '@app/features/voice/commands/VoiceSettingsCommands'; import MediaEngine from '@app/features/voice/engine/MediaEngineFacade'; @@ -344,9 +346,6 @@ export const VoiceTab: React.FC = observer(({voiceSettings, autoR if (enabled && isNativeMac && !inputMonitoringGranted) { openMacPermissionsModal({focus: 'input-monitoring'}); } - if (enabled && NativePermission.isLinuxWaylandDesktop && NativePermission.linuxInputAccessStatus !== 'granted') { - NativePermission.requestLinuxInputAccessNagbar('push-to-talk'); - } Keybind.setTransmitMode(mode); MediaEngine.handlePushToTalkModeChange(); }; @@ -394,6 +393,11 @@ export const VoiceTab: React.FC = observer(({voiceSettings, autoR }} data-flx="user.voice-tab.render-ptt-controls.keybind-recorder.set-primary-custom-keybind-combo" /> +
@@ -445,6 +449,9 @@ export const VoiceTab: React.FC = observer(({voiceSettings, autoR })} )} + {isPushToTalk && !isPttLimited && ( + + )} ); const renderAutoGainControlSwitch = () => ( @@ -696,7 +703,7 @@ export const VoiceTab: React.FC = observer(({voiceSettings, autoR {renderStereoMicrophoneSwitch('user.voice-tab.studio-profile.switch.set-stereo-microphone')}
)} - {voiceProcessingMode === 'studio' && pttCombo?.key && isPushToTalk && ( + {voiceProcessingMode === 'studio' && isPushToTalk && Keybind.hasPushToTalkKeybind() && ( Push-to-talk is ignored in direct input. Switch to focused voice or custom to use it. diff --git a/fluxer_app/src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx b/fluxer_app/src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx deleted file mode 100644 index 43280153e..000000000 --- a/fluxer_app/src/features/user/components/modals/tabs/components/LinuxInputAccessSection.tsx +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants'; -import KeybindManager from '@app/features/app/keybindings/KeybindManager'; -import NativePermission from '@app/features/permissions/system/state/NativePermission'; -import {Button} from '@app/features/ui/button/Button'; -import {WarningAlert} from '@app/features/ui/warning_alert/WarningAlert'; -import {msg} from '@lingui/core/macro'; -import {useLingui} from '@lingui/react/macro'; -import {observer} from 'mobx-react-lite'; -import type React from 'react'; -import {useState} from 'react'; - -const SYSTEM_WIDE_SHORTCUTS_DESCRIPTOR = msg({ - message: 'System-wide shortcuts', - comment: 'Settings subsection title for keyboard and mouse shortcuts that work outside the app.', -}); -const WAYLAND_DESCRIPTION_DESCRIPTOR = msg({ - message: - 'Input access is required for system-wide keyboard and mouse shortcuts on Wayland. After changing input access, fully quit and restart {productName} so shortcuts can use it.', - comment: 'Settings warning for Linux Wayland system-wide shortcuts. {productName} is the app name.', -}); -const ENABLE_DESCRIPTOR = msg({ - message: 'Enable', - comment: 'Button label in the keybind settings system-wide shortcuts section.', -}); -const RECHECK_DESCRIPTOR = msg({ - message: 'Recheck', - comment: 'Button label in the keybind settings system-wide shortcuts section.', -}); -const FINISH_RELOGIN_DESCRIPTOR = msg({ - message: 'Input access was changed. Fully quit and restart {productName} so system-wide shortcuts can use it.', - comment: 'Status text after Linux input access was changed. {productName} is the app name.', -}); -const LINUX_ERROR_DESCRIPTOR = msg({ - message: - 'Input access could not be enabled automatically. Change it manually, then fully quit and restart {productName} so shortcuts can use it.', - comment: 'Status text after Linux input access setup failed. {productName} is the app name.', -}); - -type LinuxInputAccessSectionProps = React.HTMLAttributes; - -export const LinuxInputAccessSection: React.FC = observer(() => { - const {i18n} = useLingui(); - const [busy, setBusy] = useState(false); - if (!NativePermission.isLinuxWaylandDesktop) return null; - const granted = NativePermission.linuxInputAccessStatus === 'granted'; - if (granted) return null; - const body = (() => { - if (NativePermission.linuxInputAccessGrantNeedsRelogin) { - return i18n._(FINISH_RELOGIN_DESCRIPTOR, {productName: PRODUCT_NAME}); - } - if (NativePermission.linuxInputAccessGrantError) return i18n._(LINUX_ERROR_DESCRIPTOR, {productName: PRODUCT_NAME}); - return i18n._(WAYLAND_DESCRIPTION_DESCRIPTOR, {productName: PRODUCT_NAME}); - })(); - const buttonLabel = NativePermission.linuxInputAccessGrantNeedsRelogin - ? i18n._(RECHECK_DESCRIPTOR) - : i18n._(ENABLE_DESCRIPTOR); - const handleLinuxAction = async () => { - setBusy(true); - try { - if (NativePermission.linuxInputAccessGrantNeedsRelogin) { - await NativePermission.recheckLinuxInputAccess(); - } else { - await NativePermission.grantLinuxInputAccess(); - } - await KeybindManager.reapplyGlobalShortcuts(); - } finally { - setBusy(false); - } - }; - return ( - - {buttonLabel} - - } - data-flx="user.linux-input-access-section.settings-tab-section" - > - {body} - - ); -}); diff --git a/fluxer_app/src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx b/fluxer_app/src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx new file mode 100644 index 000000000..4bda85763 --- /dev/null +++ b/fluxer_app/src/features/user/components/modals/tabs/components/SystemShortcutsSection.tsx @@ -0,0 +1,802 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { + CANARY_DESKTOP_ENTRY_NAME, + DESKTOP_ENTRY_NAME, + PRODUCT_NAME, +} from '@app/features/app/config/I18nDisplayConstants'; +import {TRY_AGAIN_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors'; +import GlobalShortcuts from '@app/features/input/state/GlobalShortcuts'; +import Keybind from '@app/features/input/state/InputKeybind'; +import {Button} from '@app/features/ui/button/Button'; +import * as TextCopyCommands from '@app/features/ui/commands/TextCopyCommands'; +import {Switch} from '@app/features/ui/components/form/FormSwitch'; +import {Spinner} from '@app/features/ui/components/Spinner'; +import {isCanaryDesktop} from '@app/features/ui/utils/NativeUtils'; +import {WarningAlert} from '@app/features/ui/warning_alert/WarningAlert'; +import styles from '@app/features/user/components/modals/tabs/KeybindsTab.module.css'; +import type {I18n, MessageDescriptor} from '@lingui/core'; +import {msg} from '@lingui/core/macro'; +import {useLingui} from '@lingui/react/macro'; +import {observer} from 'mobx-react-lite'; +import type React from 'react'; + +export const SYSTEM_WIDE_SHORTCUTS_DESCRIPTOR = msg({ + message: 'System-wide shortcuts', + comment: 'Settings subsection title for keyboard and mouse shortcuts that work outside the app.', +}); +const NOT_SET_UP_DESCRIPTOR = msg({ + message: + '{productName} can ask your desktop to run its shortcuts while {productName} is not focused. Your desktop asks you to confirm and lets you pick the keys.', + comment: + 'Linux settings text before system-wide shortcuts are set up. The desktop environment owns the keys. {productName} is the app name.', +}); +const HYPRLAND_NOT_SET_UP_DESCRIPTOR = msg({ + message: + '{productName} can register its shortcuts with Hyprland so they run while {productName} is not focused. You then bind the keys in your Hyprland config.', + comment: + 'Linux settings text before system-wide shortcuts are set up on the Hyprland desktop. Hyprland shows no dialog. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name.', +}); +const SET_UP_DESCRIPTOR = msg({ + message: 'Set up', + comment: 'Button that asks the Linux desktop to register system-wide shortcuts for the app. Keep it short.', +}); +const WAITING_FOR_DESKTOP_DESCRIPTOR = msg({ + message: 'Waiting for your desktop…', + comment: 'Status text while the Linux desktop shows its own dialog for confirming system-wide shortcuts.', +}); +const RECONNECTING_DESCRIPTOR = msg({ + message: 'Reconnecting to your desktop…', + comment: + 'Status text on Linux while the app reconnects to the desktop service that runs system-wide shortcuts, for example after it restarted.', +}); +const BOUND_DESCRIPTION_DESCRIPTOR = msg({ + message: 'Your desktop runs these shortcuts even while {productName} is not focused.', + comment: + 'Subtitle above the list of system-wide shortcuts assigned by the Linux desktop. {productName} is the app name.', +}); +const HYPRLAND_BOUND_DESCRIPTION_DESCRIPTOR = msg({ + message: + '{productName} registered its shortcuts with Hyprland. Bind them in your Hyprland config to use them while {productName} is not focused.', + comment: + 'Subtitle for system-wide shortcuts on the Hyprland desktop after setup. Hyprland shows no list of assigned keys. {productName} is the app name.', +}); +const ONE_ACTION_PER_KEY_DESCRIPTOR = msg({ + message: 'Each key can run only one system-wide shortcut.', + comment: 'Note under the list of system-wide shortcuts on Linux. The desktop cannot assign one key to two actions.', +}); +const NOT_ASSIGNED_DESCRIPTOR = msg({ + message: 'Not assigned', + comment: 'Shown instead of a key when the desktop has no key assigned to a system-wide shortcut.', +}); +const CHANGE_IN_SYSTEM_SETTINGS_DESCRIPTOR = msg({ + message: 'Change in system settings', + comment: 'Button that opens the Linux desktop dialog for changing the keys of system-wide shortcuts.', +}); +const KDE_INSTRUCTIONS_DESCRIPTOR = msg({ + message: 'To change the keys, go to System Settings > Keyboard > Shortcuts > {productName}.', + comment: + 'Instructions for KDE Plasma. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name.', +}); +const GNOME_INSTRUCTIONS_DESCRIPTOR = msg({ + message: 'To change the keys, go to Settings > Apps > {productName}.', + comment: + 'Instructions for GNOME. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name.', +}); +const HYPRLAND_INSTRUCTIONS_DESCRIPTOR = msg({ + message: + 'Hyprland has no shortcut dialog. Add the lines for your Hyprland version to your Hyprland config and replace KEY with the key you want.', + comment: + 'Instructions for the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written.', +}); +const HYPRLAND_NO_APP_ID_DESCRIPTOR = msg({ + message: 'Hyprland has no shortcut dialog. Bind the keys in your Hyprland config.', + comment: + 'Instructions for the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before.', +}); +const OTHER_DESKTOP_INSTRUCTIONS_DESCRIPTOR = msg({ + message: "To change the keys, open your desktop's keyboard shortcut settings.", + comment: 'Instructions for changing system-wide shortcut keys on an unrecognized Linux desktop.', +}); +const HYPRLAND_LUA_LINES_DESCRIPTOR = msg({ + message: 'hyprland.lua (Hyprland 0.55 and later)', + comment: 'Label above Hyprland config lines in the Lua format. Keep "hyprland.lua" exactly as written.', +}); +const HYPRLAND_CONF_LINES_DESCRIPTOR = msg({ + message: 'hyprland.conf (before Hyprland 0.55)', + comment: 'Label above Hyprland config lines in the older hyprlang format. Keep "hyprland.conf" exactly as written.', +}); +const COPY_LINES_DESCRIPTOR = msg({ + message: 'Copy lines', + comment: 'Button that copies Hyprland config lines to the clipboard.', +}); +const COPY_LUA_LINES_DESCRIPTOR = msg({ + message: 'Copy lines for hyprland.lua', + comment: + 'Screen reader name of the button that copies Hyprland config lines in the Lua format. Starts with the visible button text "Copy lines". Keep "hyprland.lua" exactly as written.', +}); +const COPY_CONF_LINES_DESCRIPTOR = msg({ + message: 'Copy lines for hyprland.conf', + comment: + 'Screen reader name of the button that copies Hyprland config lines in the older hyprlang format. Starts with the visible button text "Copy lines". Keep "hyprland.conf" exactly as written.', +}); +const DECLINED_DESCRIPTOR = msg({ + message: "Your desktop didn't set up system-wide shortcuts.", + comment: + 'Linux settings text when the desktop dialog for system-wide shortcuts was dismissed or failed. Neutral wording, the user may not have declined anything.', +}); +const CHECK_AGAIN_DESCRIPTOR = msg({ + message: 'Check again', + comment: + 'Button on Linux that checks again whether the desktop offers system-wide shortcuts, for example after the desktop service started. Keep it short.', +}); +const UNSUPPORTED_DESCRIPTOR = msg({ + message: "This desktop doesn't offer system-wide shortcuts. Shortcuts still work while {productName} is focused.", + comment: 'Linux settings text when the desktop has no system-wide shortcut support. {productName} is the app name.', +}); +const ERROR_DESCRIPTOR = msg({ + message: 'System-wide shortcuts could not be set up.', + comment: 'Linux settings text when setting up system-wide shortcuts failed.', +}); +const UPDATE_DESCRIPTOR = msg({ + message: 'Update {productName} to use system-wide shortcuts on Wayland.', + comment: + 'Linux settings text when the installed desktop app is too old for system-wide shortcuts. {productName} is the app name.', +}); +const DIRECT_INPUT_LABEL_DESCRIPTOR = msg({ + message: 'Use direct input device access', + comment: + 'Switch label on Linux. When on, the app reads keyboard devices directly for system-wide shortcuts. Not related to the "Direct input" voice processing profile.', +}); +const DIRECT_INPUT_DESCRIPTION_DESCRIPTOR = msg({ + message: 'Lets {productName} read your keyboard directly. Your system already allows this.', + comment: 'Description for the Linux direct input device access switch. {productName} is the app name.', +}); +const DIRECT_INPUT_UNAVAILABLE_DESCRIPTION_DESCRIPTOR = msg({ + message: "{productName} can no longer read your keyboard, so your desktop's shortcuts are used instead.", + comment: + 'Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, for example after a restart removed the access. The app already falls back to the shortcuts the desktop runs. {productName} is the app name.', +}); +const DIRECT_INPUT_UNAVAILABLE_X11_DESCRIPTION_DESCRIPTOR = msg({ + message: '{productName} can no longer read your keyboard, so it listens for shortcuts through X11 instead.', + comment: + 'Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read, on an X11 session. The app now listens for shortcuts through the X11 display server itself. Keep "X11" as written. {productName} is the app name.', +}); +const DIRECT_INPUT_UNAVAILABLE_FOCUSED_DESCRIPTION_DESCRIPTOR = msg({ + message: '{productName} can no longer read your keyboard. Shortcuts only work while {productName} is focused.', + comment: + 'Description for the Linux direct input device access switch when it is on but the keyboard can no longer be read and nothing else runs system-wide shortcuts. {productName} is the app name.', +}); +const DIRECT_INPUT_PUSH_TO_TALK_WARNING_DESCRIPTOR = msg({ + message: + 'Push-to-talk will use the key you set in {productName}. Set one first, or your microphone will use voice activity.', + comment: + 'Linux warning next to the direct input device access switch when push-to-talk only has a key assigned by the desktop. With direct input on, only the push-to-talk key set in the app works, and without one the microphone sends whenever you speak. {productName} is the app name.', +}); +const DIRECT_INPUT_ERROR_DESCRIPTOR = msg({ + message: "Keyboard access couldn't start. Shortcuts only work while {productName} is focused.", + comment: + 'Linux warning when direct input device access is on but reading the keyboard failed. Not related to the "Direct input" voice processing profile. {productName} is the app name.', +}); +const GNOME_HOLD_WARNING_PUSH_TO_TALK_DESCRIPTOR = msg({ + message: + 'For push-to-talk, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay on.', + comment: + 'Warning on GNOME when the push-to-talk shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone on.', +}); +const GNOME_HOLD_WARNING_PUSH_TO_MUTE_DESCRIPTOR = msg({ + message: + 'For push-to-mute, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the microphone can stay muted.', + comment: + 'Warning on GNOME when the push-to-mute shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the microphone muted.', +}); +const GNOME_HOLD_WARNING_PRIORITY_DESCRIPTOR = msg({ + message: + 'For voice activity priority, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or priority can stay on.', + comment: + 'Warning on GNOME when the voice activity priority shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave priority speaking on. "Voice activity priority" is the shortcut name.', +}); +const GNOME_HOLD_WARNING_GENERIC_DESCRIPTOR = msg({ + message: + 'For this shortcut, use a single key without Ctrl, Alt or Shift. With a combination, let go of the main key first or the shortcut can stay active.', + comment: + 'Warning on GNOME when a hold shortcut uses a key combination. Letting go of a modifier key such as Ctrl before the main key can leave the shortcut active.', +}); +const PTT_NOT_SET_UP_DESCRIPTOR = msg({ + message: 'Push-to-talk only works while {productName} is focused until you set up system-wide shortcuts.', + comment: 'Voice settings warning on Linux before system-wide shortcuts are set up. {productName} is the app name.', +}); +const PTT_DECLINED_DESCRIPTOR = msg({ + message: + "Push-to-talk only works while {productName} is focused because your desktop didn't set up system-wide shortcuts.", + comment: + 'Voice settings warning on Linux when the desktop dialog for system-wide shortcuts was dismissed or failed. {productName} is the app name.', +}); +const PTT_UNSUPPORTED_DESCRIPTOR = msg({ + message: "This desktop doesn't offer system-wide shortcuts. Push-to-talk only works while {productName} is focused.", + comment: + 'Voice settings warning on Linux desktops without system-wide shortcut support. {productName} is the app name.', +}); +const PTT_UNASSIGNED_KDE_DESCRIPTOR = msg({ + message: + 'Push-to-talk has no system-wide key yet. To set one, go to System Settings > Keyboard > Shortcuts > {productName}.', + comment: + 'Voice settings warning on KDE Plasma when the desktop has no key assigned to push-to-talk. Keep "System Settings", "Keyboard" and "Shortcuts" as the KDE menu names in your language. {productName} is the app name.', +}); +const PTT_UNASSIGNED_GNOME_DESCRIPTOR = msg({ + message: 'Push-to-talk has no system-wide key yet. To set one, go to Settings > Apps > {productName}.', + comment: + 'Voice settings warning on GNOME when the desktop has no key assigned to push-to-talk. Keep "Settings" and "Apps" as the GNOME menu names in your language. {productName} is the app name.', +}); +const PTT_UNASSIGNED_OTHER_DESCRIPTOR = msg({ + message: "Push-to-talk has no system-wide key yet. To set one, open your desktop's keyboard shortcut settings.", + comment: + 'Voice settings warning on an unrecognized Linux desktop when the desktop has no key assigned to push-to-talk.', +}); +const PTT_HYPRLAND_DESCRIPTOR = msg({ + message: + 'Push-to-talk works outside {productName} only after you bind it in your Hyprland config. Add the line for your Hyprland version and replace KEY with the key you want.', + comment: + 'Voice settings note on the Hyprland desktop, shown above config lines for hyprland.lua (Hyprland 0.55 and later) and for hyprland.conf (before 0.55). Keep "KEY" exactly as written. {productName} is the app name.', +}); +const PTT_HYPRLAND_NO_APP_ID_DESCRIPTOR = msg({ + message: 'Push-to-talk works outside {productName} only after you bind it in your Hyprland config.', + comment: + 'Voice settings note on the Hyprland desktop when the app id is unknown. The config is hyprland.lua on Hyprland 0.55 and later, hyprland.conf before. {productName} is the app name.', +}); + +export const HOLD_PORTAL_ACTIONS: ReadonlySet = new Set([ + 'voice_push_to_talk', + 'voice_push_to_talk_priority', + 'voice_push_to_mute', + 'voice_priority_vad', +]); + +export function triggerHasModifiers(triggerDescription: string): boolean { + return triggerDescription.length > 1 && triggerDescription.includes('+'); +} + +export function isGnomeHoldTriggerRisky(action: string, triggerDescription: string | null): boolean { + if (GlobalShortcuts.linux?.desktop !== 'gnome') return false; + if (!HOLD_PORTAL_ACTIONS.has(action)) return false; + return triggerDescription !== null && triggerHasModifiers(triggerDescription); +} + +export function getGnomeHoldWarning(i18n: I18n, action: string): string { + switch (action) { + case 'voice_push_to_talk': + case 'voice_push_to_talk_priority': + return i18n._(GNOME_HOLD_WARNING_PUSH_TO_TALK_DESCRIPTOR); + case 'voice_push_to_mute': + return i18n._(GNOME_HOLD_WARNING_PUSH_TO_MUTE_DESCRIPTOR); + case 'voice_priority_vad': + return i18n._(GNOME_HOLD_WARNING_PRIORITY_DESCRIPTOR); + default: + return i18n._(GNOME_HOLD_WARNING_GENERIC_DESCRIPTOR); + } +} + +function getPortalActions(): Array<{action: string; label: string; triggerDescription: string | null}> { + return Keybind.getDefaults() + .filter((config) => config.allowGlobal) + .map((config) => ({ + action: config.action, + label: config.label, + triggerDescription: GlobalShortcuts.getPortalTrigger(config.action), + })); +} + +function getHyprlandLuaBindLines(portalAppId: string, actions: ReadonlyArray): string { + return actions.map((action) => `hl.bind("KEY", hl.dsp.global("${portalAppId}:${action}"))`).join('\n'); +} + +function getHyprlandConfBindLines(portalAppId: string, actions: ReadonlyArray): string { + return actions.map((action) => `bind = , KEY, global, ${portalAppId}:${action}`).join('\n'); +} + +function getDesktopEntryName(): string { + return isCanaryDesktop() ? CANARY_DESKTOP_ENTRY_NAME : DESKTOP_ENTRY_NAME; +} + +function getPushToTalkUnassignedText(i18n: I18n): string { + switch (GlobalShortcuts.linux?.desktop) { + case 'kde': + return i18n._(PTT_UNASSIGNED_KDE_DESCRIPTOR, {productName: getDesktopEntryName()}); + case 'gnome': + return i18n._(PTT_UNASSIGNED_GNOME_DESCRIPTOR, {productName: getDesktopEntryName()}); + default: + return i18n._(PTT_UNASSIGNED_OTHER_DESCRIPTOR); + } +} + +function getChangeInstructions(i18n: I18n): string { + switch (GlobalShortcuts.linux?.desktop) { + case 'kde': + return i18n._(KDE_INSTRUCTIONS_DESCRIPTOR, {productName: getDesktopEntryName()}); + case 'gnome': + return i18n._(GNOME_INSTRUCTIONS_DESCRIPTOR, {productName: getDesktopEntryName()}); + case 'hyprland': + return GlobalShortcuts.portal?.portalAppId + ? i18n._(HYPRLAND_INSTRUCTIONS_DESCRIPTOR) + : i18n._(HYPRLAND_NO_APP_ID_DESCRIPTOR); + default: + return i18n._(OTHER_DESKTOP_INSTRUCTIONS_DESCRIPTOR); + } +} + +const PUSH_TO_TALK_ACTIONS = ['voice_push_to_talk', 'voice_push_to_talk_priority'] as const; + +function isPushToTalkDesktopOnly(): boolean { + if (!Keybind.isPushToTalkEnabled()) return false; + if (!PUSH_TO_TALK_ACTIONS.some((action) => GlobalShortcuts.isPortalActionAssigned(action))) return false; + return !PUSH_TO_TALK_ACTIONS.some((action) => + Keybind.getActiveCombosForAction(action).some((combo) => combo.global === true), + ); +} + +const SetUpButton: React.FC<{label: string; 'data-flx': string}> = observer(({label, 'data-flx': dataFlx}) => ( + +)); + +const ConfigureButton: React.FC<{'data-flx': string}> = observer(({'data-flx': dataFlx}) => { + const {i18n} = useLingui(); + return ( + + ); +}); + +const CheckAgainButton: React.FC<{'data-flx': string}> = observer(({'data-flx': dataFlx}) => { + const {i18n} = useLingui(); + return ( + + ); +}); + +const ReconnectingSection: React.FC<{'data-flx': string}> = observer(({'data-flx': dataFlx}) => { + const {i18n} = useLingui(); + return ( +
+
+
+

+ {i18n._(SYSTEM_WIDE_SHORTCUTS_DESCRIPTOR)} +

+

+ {i18n._(RECONNECTING_DESCRIPTOR)} +

+
+ +
+
+ ); +}); + +const HyprlandBindBlock: React.FC<{label: string; copyLabel: string; lines: string; 'data-flx': string}> = observer( + ({label, copyLabel, lines, 'data-flx': dataFlx}) => { + const {i18n} = useLingui(); + return ( +
+

+ {label} +

+
+					{lines}
+				
+
+ +
+
+ ); + }, +); + +const HyprlandBindLines: React.FC<{portalAppId: string; actions: ReadonlyArray; 'data-flx': string}> = observer( + ({portalAppId, actions, 'data-flx': dataFlx}) => { + const {i18n} = useLingui(); + return ( + <> + + + + ); + }, +); + +const BoundPortalShortcuts: React.FC<{'data-flx': string}> = observer(({'data-flx': dataFlx}) => { + const {i18n} = useLingui(); + const portal = GlobalShortcuts.portal; + if (!portal) return null; + const desktop = GlobalShortcuts.linux?.desktop; + const actions = getPortalActions(); + return ( +
+
+
+

+ {i18n._(SYSTEM_WIDE_SHORTCUTS_DESCRIPTOR)} +

+

+ {i18n._(desktop === 'hyprland' ? HYPRLAND_BOUND_DESCRIPTION_DESCRIPTOR : BOUND_DESCRIPTION_DESCRIPTOR, { + productName: PRODUCT_NAME, + })} +

+ {GlobalShortcuts.portalRecovering ? ( +

+ {i18n._(RECONNECTING_DESCRIPTOR)} +

+ ) : null} +
+ {portal.canConfigure ? ( +
+ +
+ ) : null} +
+ {desktop === 'hyprland' ? null : ( +
+ {actions.map(({action, label, triggerDescription}) => ( +
+
+ {label} + {isGnomeHoldTriggerRisky(action, triggerDescription) ? ( +
+ {getGnomeHoldWarning(i18n, action)} +
+ ) : null} +
+ {triggerDescription ? ( + + {triggerDescription} + + ) : ( + + {i18n._(NOT_ASSIGNED_DESCRIPTOR)} + + )} +
+ ))} +
+ )} +

+ {getChangeInstructions(i18n)} +

+ {desktop === 'hyprland' ? null : ( +

+ {i18n._(ONE_ACTION_PER_KEY_DESCRIPTOR)} +

+ )} + {desktop === 'hyprland' && portal.portalAppId ? ( + action)} + data-flx="user.system-shortcuts-section.hyprland-lines" + /> + ) : null} +
+ ); +}); + +const PortalStateSection: React.FC<{'data-flx': string}> = observer(({'data-flx': dataFlx}) => { + const {i18n} = useLingui(); + const portal = GlobalShortcuts.portal; + if (!portal) return null; + if (GlobalShortcuts.portalRecovering && portal.state !== 'bound') { + return ; + } + const title = i18n._(SYSTEM_WIDE_SHORTCUTS_DESCRIPTOR); + switch (portal.state) { + case 'not-set-up': + return ( + + } + data-flx={`${dataFlx}.not-set-up`} + > + {i18n._( + GlobalShortcuts.linux?.desktop === 'hyprland' ? HYPRLAND_NOT_SET_UP_DESCRIPTOR : NOT_SET_UP_DESCRIPTOR, + {productName: PRODUCT_NAME}, + )} + + ); + case 'binding': + return ( +
+
+
+

+ {title} +

+

+ {i18n._(WAITING_FOR_DESKTOP_DESCRIPTOR)} +

+
+ +
+
+ ); + case 'bound': + return ; + case 'declined': + return ( + + } + data-flx={`${dataFlx}.declined`} + > + {i18n._(DECLINED_DESCRIPTOR)} + + ); + case 'unsupported': + return ( + + ) : undefined + } + data-flx={`${dataFlx}.unsupported`} + > + {i18n._(UNSUPPORTED_DESCRIPTOR, {productName: PRODUCT_NAME})} + + ); + case 'error': + return ( + + } + data-flx={`${dataFlx}.error`} + > + {i18n._(ERROR_DESCRIPTOR)} + + ); + default: + return null; + } +}); + +function shouldShowDirectInputSwitch(): boolean { + const directInput = GlobalShortcuts.linux?.directInput; + if (!directInput || directInput.locked === true) return false; + return directInput.available || directInput.enabled; +} + +function getDirectInputDescription(available: boolean): MessageDescriptor { + if (available) return DIRECT_INPUT_DESCRIPTION_DESCRIPTOR; + const backend = GlobalShortcuts.backend; + if (backend === 'x11') return DIRECT_INPUT_UNAVAILABLE_X11_DESCRIPTION_DESCRIPTOR; + if (backend === 'portal' && GlobalShortcuts.portal?.state === 'bound') { + return DIRECT_INPUT_UNAVAILABLE_DESCRIPTION_DESCRIPTOR; + } + return DIRECT_INPUT_UNAVAILABLE_FOCUSED_DESCRIPTION_DESCRIPTOR; +} + +const DirectInputSwitch: React.FC<{'data-flx': string}> = observer(({'data-flx': dataFlx}) => { + const {i18n} = useLingui(); + const directInput = GlobalShortcuts.linux?.directInput; + if (!directInput || !shouldShowDirectInputSwitch()) return null; + return ( + <> + {!directInput.enabled && isPushToTalkDesktopOnly() ? ( + + {i18n._(DIRECT_INPUT_PUSH_TO_TALK_WARNING_DESCRIPTOR, {productName: PRODUCT_NAME})} + + ) : null} + void GlobalShortcuts.setDirectInputEnabled(value)} + disabled={GlobalShortcuts.pendingAction !== null} + ariaLabel={i18n._(DIRECT_INPUT_LABEL_DESCRIPTOR)} + data-flx={dataFlx} + /> + + ); +}); + +function isPortalStatusBackend(): boolean { + const backend = GlobalShortcuts.backend; + return backend === 'portal' || backend === 'none'; +} + +function hasDirectInputError(): boolean { + return GlobalShortcuts.backend === 'evdev' && GlobalShortcuts.hookError !== null; +} + +export const SystemShortcutsSection: React.FC<{'data-flx'?: string}> = observer( + ({'data-flx': dataFlx = 'user.system-shortcuts-section'}) => { + const {i18n} = useLingui(); + if (GlobalShortcuts.legacyWaylandNeedsUpdate) { + return ( + + {i18n._(UPDATE_DESCRIPTOR, {productName: PRODUCT_NAME})} + + ); + } + const linux = GlobalShortcuts.linux; + if (!linux || GlobalShortcuts.backend === null) return null; + const portalState = GlobalShortcuts.portal?.state; + const showPortal = + isPortalStatusBackend() && portalState !== undefined && portalState !== 'unknown' && portalState !== 'probing'; + const showDirectInputError = hasDirectInputError(); + if (!showPortal && !showDirectInputError && !shouldShowDirectInputSwitch()) return null; + return ( +
+ {showPortal ? : null} + + {showDirectInputError ? ( + + {i18n._(DIRECT_INPUT_ERROR_DESCRIPTOR, {productName: PRODUCT_NAME})} + + ) : null} +
+ ); + }, +); + +const HyprlandPushToTalkCard: React.FC<{portalAppId: string | null; 'data-flx': string}> = observer( + ({portalAppId, 'data-flx': dataFlx}) => { + const {i18n} = useLingui(); + return ( +
+

+ {portalAppId + ? i18n._(PTT_HYPRLAND_DESCRIPTOR, {productName: PRODUCT_NAME}) + : i18n._(PTT_HYPRLAND_NO_APP_ID_DESCRIPTOR, {productName: PRODUCT_NAME})} +

+ {portalAppId ? ( + + ) : null} +
+ ); + }, +); + +export const SystemShortcutsPushToTalkAlert: React.FC<{'data-flx'?: string}> = observer( + ({'data-flx': dataFlx = 'user.system-shortcuts-ptt-alert'}) => { + const {i18n} = useLingui(); + if (GlobalShortcuts.legacyWaylandNeedsUpdate) { + return ( + + {i18n._(UPDATE_DESCRIPTOR, {productName: PRODUCT_NAME})} + + ); + } + const linux = GlobalShortcuts.linux; + if (!linux) return null; + if (hasDirectInputError()) { + return ( + + {i18n._(DIRECT_INPUT_ERROR_DESCRIPTOR, {productName: PRODUCT_NAME})} + + ); + } + const portal = GlobalShortcuts.portal; + if (!portal || !isPortalStatusBackend()) return null; + if (GlobalShortcuts.portalRecovering && portal.state !== 'bound') { + return {i18n._(RECONNECTING_DESCRIPTOR)}; + } + switch (portal.state) { + case 'not-set-up': + return ( + } + data-flx={`${dataFlx}.not-set-up`} + > + {i18n._(PTT_NOT_SET_UP_DESCRIPTOR, {productName: PRODUCT_NAME})} + + ); + case 'binding': + return {i18n._(WAITING_FOR_DESKTOP_DESCRIPTOR)}; + case 'declined': + return ( + } + data-flx={`${dataFlx}.declined`} + > + {i18n._(PTT_DECLINED_DESCRIPTOR, {productName: PRODUCT_NAME})} + + ); + case 'error': + return ( + } + data-flx={`${dataFlx}.error`} + > + {i18n._(ERROR_DESCRIPTOR)} + + ); + case 'unsupported': + return ( + : undefined} + data-flx={`${dataFlx}.unsupported`} + > + {i18n._(PTT_UNSUPPORTED_DESCRIPTOR, {productName: PRODUCT_NAME})} + + ); + case 'bound': { + if (linux.desktop === 'hyprland') + return ; + const trigger = GlobalShortcuts.getPortalTrigger('voice_push_to_talk'); + if (trigger !== null) { + if (!isGnomeHoldTriggerRisky('voice_push_to_talk', trigger)) return null; + return ( + + {getGnomeHoldWarning(i18n, 'voice_push_to_talk')} + + ); + } + return ( + : undefined} + data-flx={`${dataFlx}.unassigned`} + > + {getPushToTalkUnassignedText(i18n)} + + ); + } + default: + return null; + } + }, +); diff --git a/fluxer_app/src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx b/fluxer_app/src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx index 0f5e4abad..ec777a9df 100644 --- a/fluxer_app/src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx +++ b/fluxer_app/src/features/user/components/modals/tabs/keybinds_tab/CustomKeybindRow.tsx @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import {KeybindRecorder} from '@app/features/input/components/KeybindRecorder'; +import GlobalShortcuts from '@app/features/input/state/GlobalShortcuts'; import Keybind, { type CustomKeybindEntry, type KeybindCommand, @@ -15,6 +16,7 @@ import * as ContextMenuCommands from '@app/features/ui/commands/ContextMenuComma import {Switch} from '@app/features/ui/components/form/FormSwitch'; import styles from '@app/features/user/components/modals/tabs/KeybindsTab.module.css'; import {ActionDropdown} from '@app/features/user/components/modals/tabs/keybinds_tab/ActionDropdown'; +import {SystemShortcutRowHint} from '@app/features/user/components/modals/tabs/keybinds_tab/SystemShortcutRowHint'; import {useAssignableActionOptions} from '@app/features/user/components/modals/tabs/keybinds_tab/useAssignableActionOptions'; import {msg} from '@lingui/core/macro'; import {Trans, useLingui} from '@lingui/react/macro'; @@ -70,7 +72,7 @@ export const CustomKeybindRow = observer( const openRowMenu = (event: React.MouseEvent) => { ContextMenuCommands.openFromEvent(event, ({onClose}) => ( <> - {entry.action && Keybind.isActionGlobalCapable(entry.action) ? ( + {entry.action && Keybind.isActionGlobalCapable(entry.action) && !GlobalShortcuts.isPortalBackend ? ( Choose an action. - ) : null} + ) : ( + + )} {conflictLabel ? (
p.builtinDisabled); const allBuiltinDisabled = perAction.every((p) => p.builtinDisabled); const globalAction = - actions.length === 1 && !anyBuiltinDisabled && Keybind.isActionGlobalCapable(actions[0].action) + actions.length === 1 && + !anyBuiltinDisabled && + !GlobalShortcuts.isPortalBackend && + Keybind.isActionGlobalCapable(actions[0].action) ? actions[0].action : null; const reEnableGroup = anyBuiltinDisabled ? ( @@ -268,6 +273,10 @@ export const DefaultShortcutRow: React.FC<{
{entry.label} +
{sectionName}.', + comment: + 'Hint under the push-to-talk key recorder in voice settings on Linux when the desktop owns system-wide shortcuts. {settingsTabName} is the name of the settings tab with keyboard shortcuts and {sectionName} the title of its system-wide shortcuts section, both filled in by the app. {productName} is the app name.', +}); +const SYSTEM_WIDE_TRIGGER_DESCRIPTOR = msg({ + message: 'System-wide: {trigger}', + comment: + 'Hint under a shortcut on Linux showing the key the desktop assigned. {trigger} is the key, as named by the desktop.', +}); +const SYSTEM_WIDE_NOT_ASSIGNED_DESCRIPTOR = msg({ + message: 'System-wide: Not assigned', + comment: 'Hint under a shortcut on Linux when the desktop has no key assigned for it system-wide.', +}); +const EXTRA_MODIFIERS_DESCRIPTOR = msg({ + message: 'Holding Shift, Ctrl or Alt blocks this shortcut. Add alternates in your system settings.', + comment: + 'Hint under a system-wide hold shortcut such as push-to-talk on Linux. The desktop ignores the key while other modifier keys are held.', +}); + +export const SystemShortcutRowHint = observer( + ({ + action, + variant = 'row', + 'data-flx': dataFlx = 'user.keybinds-tab.system-shortcut-row-hint', + }: { + action: KeybindCommand; + variant?: 'row' | 'voice-tab'; + 'data-flx'?: string; + }) => { + const {i18n} = useLingui(); + if (!GlobalShortcuts.isPortalBackend || !Keybind.isActionGlobalCapable(action)) return null; + const bound = GlobalShortcuts.portal?.state === 'bound'; + const showTrigger = bound && GlobalShortcuts.linux?.desktop !== 'hyprland'; + const trigger = GlobalShortcuts.getPortalTrigger(action); + return ( +
+
+ {variant === 'voice-tab' + ? i18n._(IN_APP_KEY_DESCRIPTOR, { + productName: PRODUCT_NAME, + settingsTabName: getUserSettingsTabLabel(i18n, 'keybinds'), + sectionName: i18n._(SYSTEM_WIDE_SHORTCUTS_DESCRIPTOR), + }) + : i18n._(WORKS_WHILE_FOCUSED_DESCRIPTOR, {productName: PRODUCT_NAME})} +
+ {showTrigger ? ( +
+ {trigger === null + ? i18n._(SYSTEM_WIDE_NOT_ASSIGNED_DESCRIPTOR) + : i18n._(SYSTEM_WIDE_TRIGGER_DESCRIPTOR, {trigger})} +
+ ) : null} + {showTrigger && trigger !== null && HOLD_PORTAL_ACTIONS.has(action) && !triggerHasModifiers(trigger) ? ( +
{i18n._(EXTRA_MODIFIERS_DESCRIPTOR)}
+ ) : null} + {showTrigger && variant === 'row' && isGnomeHoldTriggerRisky(action, trigger) ? ( +
{getGnomeHoldWarning(i18n, action)}
+ ) : null} +
+ ); + }, +); diff --git a/fluxer_app/src/features/voice/components/VoiceControlBar.tsx b/fluxer_app/src/features/voice/components/VoiceControlBar.tsx index 4fa52a442..1d31624a3 100644 --- a/fluxer_app/src/features/voice/components/VoiceControlBar.tsx +++ b/fluxer_app/src/features/voice/components/VoiceControlBar.tsx @@ -3,7 +3,7 @@ import * as VoiceStateCommands from '@app/features/devtools/commands/VoiceStateCommands'; import {MORE_OPTIONS_DESCRIPTOR, TURN_OFF_CAMERA_DESCRIPTOR} from '@app/features/i18n/utils/CommonMessageDescriptors'; import Keybind from '@app/features/input/state/InputKeybind'; -import {formatKeyCombo} from '@app/features/input/utils/KeybindUtils'; +import {getPushToTalkHoldLabel} from '@app/features/input/utils/PushToTalkHint'; import {SoundType} from '@app/features/notification/utils/SoundUtils'; import Permission from '@app/features/permissions/state/Permission'; import NativePermission from '@app/features/permissions/system/state/NativePermission'; @@ -253,8 +253,7 @@ const VoiceControlBarInner = observer(function VoiceControlBarInner() { serverDeaf: voiceState?.deaf, }); const effectiveMuted = effectiveAudioState.effectiveMute || muteReason !== null || isMuted || isPermissionMuted; - const pushToTalkCombo = Keybind.getByAction('voice_push_to_talk').combo; - const pushToTalkHint = formatKeyCombo(i18n, pushToTalkCombo); + const pushToTalkHoldLabel = getPushToTalkHoldLabel(i18n, PUSH_TO_TALK_HOLD_HINT_DESCRIPTOR); const displayShareEnvironment = resolveDisplayShareEnvironment(isDesktop(), NativePermission.isLinuxWaylandDesktop); const disconnectLabel = i18n._(VOICE_DISCONNECT_DESCRIPTOR); const renderInputSettingsMenu = useCallback( @@ -582,14 +581,14 @@ const VoiceControlBarInner = observer(function VoiceControlBarInner() { case 'permissionMuted': return i18n._(VOICE_NO_SPEAK_PERMISSION_DESCRIPTOR); case 'pushToTalkHoldHint': - return i18n._(PUSH_TO_TALK_HOLD_HINT_DESCRIPTOR, {pushToTalkHint}); + return pushToTalkHoldLabel; case 'unmute': return i18n._(UNMUTE_DESCRIPTOR); default: return i18n._(MUTE_DESCRIPTOR); } }, - [i18n, pushToTalkHint], + [i18n, pushToTalkHoldLabel], ); const getDeafenTooltipLabel = useCallback( (label: VoiceControlBarDeafenLabel) => { diff --git a/fluxer_app/src/features/voice/engine/MediaEngineFacade.ts b/fluxer_app/src/features/voice/engine/MediaEngineFacade.ts index 6034a7bf4..8c239afd1 100644 --- a/fluxer_app/src/features/voice/engine/MediaEngineFacade.ts +++ b/fluxer_app/src/features/voice/engine/MediaEngineFacade.ts @@ -2240,8 +2240,12 @@ class MediaEngineFacade extends Store { this.syncVoiceEngineV2AudioControlsFromAppState(); } - handlePushToTalkModeChange(): void { - voiceEngineV2AppMediaExecutionAdapter.handlePushToTalkModeChange(this.room, () => this.getCurrentUserVoiceState()); + handlePushToTalkModeChange(options: {preserveSelfMute?: boolean} = {}): void { + voiceEngineV2AppMediaExecutionAdapter.handlePushToTalkModeChange( + this.room, + () => this.getCurrentUserVoiceState(), + options, + ); this.syncVoiceEngineV2AudioControlsFromAppState(); } @@ -2325,9 +2329,11 @@ class MediaEngineFacade extends Store { requestMicrophoneRefresh: (targetRoom, request) => voiceEngineV2AppMediaExecutionAdapter.requestMicrophoneRefresh(targetRoom, request), configureVoiceInput: (targetRoom) => voiceEngineV2AppMediaExecutionAdapter.configureVoiceInput(targetRoom), - handleInputKeybindChange: (targetRoom) => - voiceEngineV2AppMediaExecutionAdapter.handlePushToTalkModeChange(targetRoom, () => - this.getCurrentUserVoiceState(), + handleInputKeybindChange: (targetRoom, options) => + voiceEngineV2AppMediaExecutionAdapter.handlePushToTalkModeChange( + targetRoom, + () => this.getCurrentUserVoiceState(), + options, ), applyAllLocalAudioPreferences: () => voiceEngineV2AppMediaExecutionAdapter.applyAllLocalAudioPreferences(room), }, diff --git a/fluxer_app/src/features/voice/engine/v2/VoiceEngineV2AppAudioPreferencesSyncBinding.ts b/fluxer_app/src/features/voice/engine/v2/VoiceEngineV2AppAudioPreferencesSyncBinding.ts index 9d81a1563..8d87be981 100644 --- a/fluxer_app/src/features/voice/engine/v2/VoiceEngineV2AppAudioPreferencesSyncBinding.ts +++ b/fluxer_app/src/features/voice/engine/v2/VoiceEngineV2AppAudioPreferencesSyncBinding.ts @@ -23,13 +23,14 @@ export interface VoiceEngineV2AppAudioPreferencesSnapshot { readonly participantMutes: Readonly>; readonly connectionVolumes: Readonly>>>; readonly streamAudioRevision: number; + readonly transmitMode: typeof Keybind.transmitMode; readonly pushToMuteActive: boolean; } export interface VoiceEngineV2AppAudioPreferencesMediaAdapter { requestMicrophoneRefresh(room: Room, request: {republish: boolean}): Promise; configureVoiceInput(room: Room): void; - handleInputKeybindChange(room: Room): void; + handleInputKeybindChange(room: Room, options: {preserveSelfMute: boolean}): void; applyAllLocalAudioPreferences(room: Room): void; } @@ -49,6 +50,7 @@ export function createVoiceEngineV2AppAudioPreferencesSnapshot(): VoiceEngineV2A participantMutes: ParticipantVolume.localMutes, connectionVolumes: ParticipantVolume.connectionVolumesByLocalConnectionId, streamAudioRevision: StreamAudioPrefs.audioPrefsRevision, + transmitMode: Keybind.transmitMode, pushToMuteActive: Keybind.isPushToMuteEffective(), }; } @@ -94,7 +96,9 @@ export function syncVoiceEngineV2AppAudioPreferences( 'audio preferences sync adapter missing requestMicrophoneRefresh', ); assert.equal(typeof logger.warn, 'function', 'audio preferences sync logger missing warn'); - if (previous.pushToMuteActive !== current.pushToMuteActive) adapter.handleInputKeybindChange(room); + if (previous.pushToMuteActive !== current.pushToMuteActive) { + adapter.handleInputKeybindChange(room, {preserveSelfMute: previous.transmitMode === current.transmitMode}); + } const republish = hasVoiceEngineV2MicrophonePublishSettingsChanged(previous.audioSettings, current.audioSettings); if (republish || hasVoiceEngineV2MicrophoneCaptureSettingsChanged(previous.audioSettings, current.audioSettings)) { refreshMicrophone(room, adapter, logger, republish); diff --git a/fluxer_app/src/features/voice/engine/v2/VoiceEngineV2AppMediaExecutionAdapter.tsx b/fluxer_app/src/features/voice/engine/v2/VoiceEngineV2AppMediaExecutionAdapter.tsx index 5cd196969..bd9ddca89 100644 --- a/fluxer_app/src/features/voice/engine/v2/VoiceEngineV2AppMediaExecutionAdapter.tsx +++ b/fluxer_app/src/features/voice/engine/v2/VoiceEngineV2AppMediaExecutionAdapter.tsx @@ -1560,7 +1560,11 @@ export class VoiceEngineV2AppMediaExecutionAdapter extends Store { this.syncLocalSpeakingOverride(room); } - handlePushToTalkModeChange(room: Room | null, getCurrentUserVoiceState: () => VoiceState | null): void { + handlePushToTalkModeChange( + room: Room | null, + getCurrentUserVoiceState: () => VoiceState | null, + options: {preserveSelfMute?: boolean} = {}, + ): void { assertNullableObjectLike(room, 'handlePushToTalkModeChange.room'); assert.equal( typeof getCurrentUserVoiceState, @@ -1569,7 +1573,7 @@ export class VoiceEngineV2AppMediaExecutionAdapter extends Store { ); Keybind.resetPushToTalkState(); Keybind.resetPushToMuteState(); - if (isPushToTalkActiveFromAppState() && LocalVoiceState.getSelfMute()) { + if (!options.preserveSelfMute && isPushToTalkActiveFromAppState() && LocalVoiceState.getSelfMute()) { LocalVoiceState.updateSelfMute(false); } const serverVoiceState = getCurrentUserVoiceState(); diff --git a/fluxer_app/src/types/electron.d.ts b/fluxer_app/src/types/electron.d.ts index dbf00dd77..fe02cca52 100644 --- a/fluxer_app/src/types/electron.d.ts +++ b/fluxer_app/src/types/electron.d.ts @@ -246,6 +246,113 @@ export interface GlobalKeyHookRegisterOptions { meta?: boolean; } +export type GlobalShortcutsBackend = 'portal' | 'x11' | 'evdev' | 'windows' | 'macos' | 'none'; + +export type GlobalShortcutsPortalState = + | 'unknown' + | 'probing' + | 'unsupported' + | 'not-set-up' + | 'binding' + | 'bound' + | 'declined' + | 'error'; + +export interface GlobalShortcutsPortalShortcut { + action: string; + triggerDescription: string | null; +} + +export interface GlobalShortcutsPortalStatus { + state: GlobalShortcutsPortalState; + version: number | null; + canConfigure: boolean; + canRecheck: boolean; + portalAppId: string | null; + shortcuts: Array; + error: string | null; + recovering: boolean; +} + +export interface GlobalShortcutsLinuxStatus { + session: 'wayland' | 'x11' | 'unknown'; + sandbox: 'flatpak' | 'none'; + desktop: 'kde' | 'gnome' | 'hyprland' | 'other'; + portal: GlobalShortcutsPortalStatus | null; + directInput: {available: boolean; enabled: boolean; locked: boolean}; +} + +export interface GlobalShortcutsStatus { + backend: GlobalShortcutsBackend; + platform: 'linux' | 'windows' | 'macos'; + linux: GlobalShortcutsLinuxStatus | null; + hooksActive: boolean; + hookError: 'permission' | 'start-failed' | null; + supportsMouseButtons: boolean; + supportsModifierOnly: boolean; +} + +export interface GlobalShortcutCombo { + code?: string; + key: string; + ctrl: boolean; + alt: boolean; + shift: boolean; + meta: boolean; + mouseButton?: number; + modifierOnly?: boolean; + bothSides?: boolean; +} + +export interface GlobalShortcutBinding { + sourceId: string; + action: string; + combo: GlobalShortcutCombo; +} + +export interface GlobalShortcutActionDefinition { + action: string; + description: string; + preferredCombo: GlobalShortcutCombo | null; +} + +export interface GlobalShortcutsSyncPayload { + bindings: Array; + actions: Array; +} + +export interface GlobalShortcutEvent { + action: string; + sourceId: string; + phase: 'press' | 'release'; +} + +export interface GlobalShortcutCaptureEvent { + type: 'keydown' | 'keyup' | 'mousedown' | 'mouseup'; + code: string | null; + key: string | null; + button: number | null; + ctrl: boolean; + alt: boolean; + shift: boolean; + meta: boolean; +} + +export interface GlobalShortcutsApi { + sync(payload: GlobalShortcutsSyncPayload): Promise; + setPaused(paused: boolean): Promise; + getStatus(): Promise; + onStatus(callback: (status: GlobalShortcutsStatus) => void): () => void; + onEvent(callback: (event: GlobalShortcutEvent) => void): () => void; + setUp(): Promise; + configure(): Promise; + setDirectInputEnabled(enabled: boolean): Promise; + recheck(): Promise; + startCapture(): Promise; + stopCapture(captureId: number): Promise; + onCapture(callback: (event: GlobalShortcutCaptureEvent) => void): () => void; +} + export interface DisplayMediaRequestInfo { audioRequested: boolean; videoRequested: boolean; @@ -428,11 +535,7 @@ export interface ElectronAPI { readableEventDevices: number; inInputGroup: boolean; }>; - linuxEvdevGrantAccess?(): Promise<{ - success: boolean; - needsRelogin: boolean; - error?: string; - }>; + globalShortcuts?: GlobalShortcutsApi; onGlobalKeyEvent(callback: (event: GlobalKeyEvent) => void): () => void; onGlobalMouseEvent(callback: (event: GlobalMouseEvent) => void): () => void; checkInputMonitoringAccess(): Promise; diff --git a/fluxer_desktop/electron-builder.config.cjs b/fluxer_desktop/electron-builder.config.cjs index e2350c51a..5c30defa8 100644 --- a/fluxer_desktop/electron-builder.config.cjs +++ b/fluxer_desktop/electron-builder.config.cjs @@ -16,6 +16,7 @@ const appId = isCanary ? 'app.fluxer.canary' : 'app.fluxer'; const iconDir = isCanary ? 'icons-canary' : 'icons-stable'; const packageName = isCanary ? 'fluxer_desktop_canary' : 'fluxer_desktop'; const linuxPackageName = isCanary ? 'fluxer-canary' : 'fluxer'; +const linuxDesktopId = isCanary ? 'app.fluxer.FluxerDesktopCanary' : 'app.fluxer.FluxerDesktop'; const linuxDesktopActionIds = ['open-settings', 'new-dm']; const linuxDesktopActionList = `${linuxDesktopActionIds.join(';')};`; const linuxGlibcBaseline = Object.freeze({major: 2, minor: 35, patch: 0, name: 'GLIBC_2.35'}); @@ -341,7 +342,7 @@ const linuxDesktopEntry = { Comment: isCanary ? 'Canary build of Fluxer' : 'Instant messaging and VoIP', Keywords: 'chat;im;messaging;messenger;voip;voice;video;call;', Categories: 'Network;InstantMessaging;Chat;', - StartupWMClass: linuxPackageName, + StartupWMClass: linuxDesktopId, StartupNotify: 'true', SingleMainWindow: 'true', MimeType: 'x-scheme-handler/fluxer;', @@ -1496,7 +1497,7 @@ module.exports = { main: 'dist/main/index.js', name: metadataName, ...(process.env.VERSION ? {version: process.env.VERSION} : {}), - ...(targetPlatform === 'linux' ? {desktopName: `${linuxPackageName}.desktop`} : {}), + ...(targetPlatform === 'linux' ? {desktopName: `${linuxDesktopId}.desktop`} : {}), }, extraResources: [ { diff --git a/fluxer_desktop/native/linux-evdev/Cargo.lock b/fluxer_desktop/native/linux-evdev/Cargo.lock index 94db6ca6f..322473256 100644 --- a/fluxer_desktop/native/linux-evdev/Cargo.lock +++ b/fluxer_desktop/native/linux-evdev/Cargo.lock @@ -2,6 +2,149 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + [[package]] name = "bitflags" version = "2.13.2" @@ -20,6 +163,25 @@ dependencies = [ "wyz", ] +[[package]] +name = "blocking" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "cfg-if" version = "1.0.5" @@ -62,6 +224,39 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.14" @@ -84,17 +279,47 @@ dependencies = [ "nix 0.29.0", ] +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + [[package]] name = "fluxer_linux_evdev" version = "0.0.0" dependencies = [ + "async-io", "evdev", + "event-listener", + "futures-lite", "napi", "napi-build", "napi-derive", "nix 0.31.3", "polling", "udev", + "zbus", ] [[package]] @@ -151,6 +376,19 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.34" @@ -191,6 +429,23 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -203,6 +458,22 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -214,6 +485,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.189" @@ -252,6 +534,15 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "napi" version = "3.12.6" @@ -340,12 +631,45 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.34" @@ -366,6 +690,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -384,6 +717,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "0.7.0" @@ -409,12 +748,69 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + [[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "slab" version = "0.4.12" @@ -449,6 +845,80 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.15+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + [[package]] name = "udev" version = "0.9.3" @@ -461,6 +931,17 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + [[package]] name = "unicode-ident" version = "1.0.26" @@ -473,6 +954,62 @@ version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +[[package]] +name = "uuid" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" +dependencies = [ + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.6", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -554,6 +1091,15 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + [[package]] name = "wyz" version = "0.5.1" @@ -562,3 +1108,114 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] + +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.6", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow", + "zvariant", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", +] + +[[package]] +name = "zvariant" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow", + "zcheapstr", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.6", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.6", + "winnow", +] diff --git a/fluxer_desktop/native/linux-evdev/Cargo.toml b/fluxer_desktop/native/linux-evdev/Cargo.toml index 0f2020896..870c83a6a 100644 --- a/fluxer_desktop/native/linux-evdev/Cargo.toml +++ b/fluxer_desktop/native/linux-evdev/Cargo.toml @@ -16,10 +16,14 @@ napi = {version = "3.12.6", default-features = false, features = ["dyn-symbols", napi-derive = "3.6.7" [target.'cfg(target_os = "linux")'.dependencies] +async-io = "2.6.0" +event-listener = "5.4.0" evdev = "0.13.2" -nix = {version = "0.31.3", default-features = false, features = ["event"]} +futures-lite = "2.6.1" +nix = {version = "0.31.3", default-features = false, features = ["event", "fs", "inotify"]} udev = "0.9.3" polling = "3.11.0" +zbus = "5.19.0" [build-dependencies] napi-build = "2.4.3" diff --git a/fluxer_desktop/native/linux-evdev/index.d.ts b/fluxer_desktop/native/linux-evdev/index.d.ts index 151fb4856..6ef1e431b 100644 --- a/fluxer_desktop/native/linux-evdev/index.d.ts +++ b/fluxer_desktop/native/linux-evdev/index.d.ts @@ -29,6 +29,6 @@ export declare class EvdevHook { stop(): void; } -export declare function nameToEvdevKeycode(name: string): number; +export declare function isKeyboardReadable(): boolean; export declare const loadError: Error | null; diff --git a/fluxer_desktop/native/linux-evdev/index.js b/fluxer_desktop/native/linux-evdev/index.js index 49e4209a1..32c95109c 100644 --- a/fluxer_desktop/native/linux-evdev/index.js +++ b/fluxer_desktop/native/linux-evdev/index.js @@ -59,6 +59,6 @@ if (process.platform === 'linux') { module.exports = { EvdevHook: binding ? binding.EvdevHook : null, - nameToEvdevKeycode: binding ? binding.nameToEvdevKeycode : null, + isKeyboardReadable: binding ? binding.isKeyboardReadable : null, loadError, }; diff --git a/fluxer_desktop/native/linux-evdev/src/keymap.rs b/fluxer_desktop/native/linux-evdev/src/keymap.rs index 904096889..38f3304d1 100644 --- a/fluxer_desktop/native/linux-evdev/src/keymap.rs +++ b/fluxer_desktop/native/linux-evdev/src/keymap.rs @@ -185,14 +185,6 @@ pub fn keycode_to_name(code: u16) -> Option<&'static str> { .map(|(_, name)| *name) } -pub fn name_to_keycode(name: &str) -> u16 { - KEY_MAP - .iter() - .find(|(_, entry_name)| *entry_name == name) - .map(|(code, _)| *code) - .unwrap_or(0) -} - #[cfg(test)] mod tests { use super::*; @@ -211,15 +203,6 @@ mod tests { assert_eq!(keycode_to_name(0xffff), None); } - #[test] - fn name_to_keycode_round_trips_every_entry() { - for (code, name) in KEY_MAP { - assert_eq!(name_to_keycode(name), *code); - } - assert_eq!(name_to_keycode("NoSuchKey"), 0); - assert_eq!(name_to_keycode(""), 0); - } - #[test] fn evdev_button_matches_dom_convention() { assert_eq!(evdev_button_to_browser_button(BTN_LEFT), Some(0)); diff --git a/fluxer_desktop/native/linux-evdev/src/lib.rs b/fluxer_desktop/native/linux-evdev/src/lib.rs index e907d94d6..97d11fdfd 100644 --- a/fluxer_desktop/native/linux-evdev/src/lib.rs +++ b/fluxer_desktop/native/linux-evdev/src/lib.rs @@ -8,10 +8,13 @@ mod keymap; mod linux; #[cfg(target_os = "linux")] -pub use linux::{EvdevHook, name_to_evdev_keycode}; +mod logind; + +#[cfg(target_os = "linux")] +pub use linux::{EvdevHook, is_keyboard_readable}; #[cfg(not(target_os = "linux"))] -#[napi_derive::napi(js_name = "nameToEvdevKeycode")] -pub fn name_to_evdev_keycode(name: String) -> u32 { - u32::from(crate::keymap::name_to_keycode(&name)) +#[napi_derive::napi(js_name = "isKeyboardReadable")] +pub fn is_keyboard_readable() -> bool { + false } diff --git a/fluxer_desktop/native/linux-evdev/src/linux.rs b/fluxer_desktop/native/linux-evdev/src/linux.rs index 9f6776f8f..9d4325a04 100644 --- a/fluxer_desktop/native/linux-evdev/src/linux.rs +++ b/fluxer_desktop/native/linux-evdev/src/linux.rs @@ -2,16 +2,22 @@ use std::{ collections::{HashMap, HashSet}, - os::fd::{AsFd, AsRawFd, BorrowedFd, RawFd}, + ffi::OsStr, + ops::Range, + os::{ + fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd}, + unix::fs::OpenOptionsExt, + }, path::{Path, PathBuf}, sync::{ Arc, Mutex, atomic::{AtomicBool, Ordering}, }, thread::{self, JoinHandle}, + time::{Duration, Instant}, }; -use evdev::{Device, EventType, KeyCode}; +use evdev::{EventType, KeyCode, SynchronizationCode, raw_stream::RawDevice}; use napi::{ Env, Status, bindgen_prelude::{Function, Object, Result, ToNapiValue}, @@ -19,16 +25,28 @@ use napi::{ threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode, UnknownReturnValue}, }; use napi_derive::napi; -use nix::sys::eventfd::{EfdFlags, EventFd}; +use nix::{ + fcntl::OFlag, + sys::{ + eventfd::{EfdFlags, EventFd}, + inotify::{AddWatchFlags, InitFlags, Inotify}, + }, +}; use polling::{Event as PollEvent, Events, Poller}; use crate::keymap; +use crate::logind::{SessionGate, SessionWatch}; const EXIT_KEY: usize = 0; const MONITOR_KEY: usize = 1; -const DEVICE_KEY_BASE: usize = 2; +const INOTIFY_KEY: usize = 2; +const DEVICE_KEY_BASE: usize = 3; +const DEV_INPUT_DIR: &str = "/dev/input"; const DEV_INPUT_PREFIX: &str = "/dev/input/event"; -const EVENT_QUEUE_LIMIT: usize = 1024; +const EVENT_NODE_PREFIX: &str = "event"; +const FLATPAK_INFO_PATH: &str = "/.flatpak-info"; +const PERMISSION_RETRY_DELAY: Duration = Duration::from_millis(500); +const FULL_KEYBOARD_KEY_CODES: std::ops::Range = 1..32; fn poll_key_for_device_fd(fd: RawFd) -> Option { usize::try_from(fd).ok()?.checked_add(DEVICE_KEY_BASE) @@ -126,17 +144,8 @@ impl ToNapiValue for NativeEvent { } } -type EventTsfn = Arc< - ThreadsafeFunction< - NativeEvent, - UnknownReturnValue, - NativeEvent, - Status, - false, - true, - EVENT_QUEUE_LIMIT, - >, ->; +type EventTsfn = + Arc>; struct ExitFd { fd: EventFd, @@ -191,18 +200,79 @@ fn lookup_input_seat(sysname: &str) -> Option { read_seat_from_device(&device) } +struct SeatFilter { + udev_handle_available: bool, + seat: String, +} + +impl SeatFilter { + fn detect() -> Self { + Self { + udev_handle_available: udev::Enumerator::new().is_ok(), + seat: resolve_seat(), + } + } + + fn includes(&self, sysname: &str) -> bool { + if !self.udev_handle_available || self.seat.is_empty() { + return true; + } + match lookup_input_seat(sysname) { + Some(found) => found == self.seat, + None => true, + } + } + + fn event_node_paths(&self) -> Vec { + let Ok(entries) = std::fs::read_dir(DEV_INPUT_DIR) else { + return Vec::new(); + }; + entries + .flatten() + .filter(|entry| { + entry + .file_name() + .to_str() + .is_some_and(|name| is_event_node_name(name) && self.includes(name)) + }) + .map(|entry| entry.path()) + .collect() + } +} + +fn is_event_node_name(name: &str) -> bool { + name.starts_with(EVENT_NODE_PREFIX) +} + +fn is_sandboxed() -> bool { + Path::new(FLATPAK_INFO_PATH).exists() +} + struct OpenedDevice { - device: Device, + device: RawDevice, path: PathBuf, + held: HashSet, + dropped: bool, +} + +enum DeviceInput { + Key(u16, i32), + Resync, +} + +struct PermissionRetry { + path: PathBuf, + due: Instant, } struct Reader { poller: Arc, exit_fd: Arc, - udev_handle_available: bool, - seat: String, + gate: Arc, + seats: SeatFilter, devices: HashMap, - held_keys: HashSet, + retries: Vec, + delivering: bool, callback: EventTsfn, stop: Arc, } @@ -217,22 +287,31 @@ impl Reader { unsafe { poller.add(&exit_fd.as_borrowed(), PollEvent::readable(EXIT_KEY))?; } - let seat = resolve_seat(); - let udev_handle_available = udev::Enumerator::new().is_ok(); + let gate = Arc::new(SessionGate::new(poller.clone())); Ok(Self { poller, exit_fd, - udev_handle_available, - seat, + gate, + seats: SeatFilter::detect(), devices: HashMap::new(), - held_keys: HashSet::new(), + retries: Vec::new(), + delivering: false, callback, stop, }) } + fn has_full_keyboard(&self) -> bool { + self.devices + .values() + .any(|opened| device_is_full_keyboard(&opened.device)) + } + fn try_attach_monitor(&self) -> Option { + if is_sandboxed() { + return None; + } let socket = udev::MonitorBuilder::new() .and_then(|b| b.match_subsystem("input")) .and_then(|b| b.listen()) @@ -246,38 +325,62 @@ impl Reader { } } - fn device_is_on_our_seat(&self, sysname: &str) -> bool { - if !self.udev_handle_available || self.seat.is_empty() { - return true; + fn try_attach_inotify(&self) -> Option { + let inotify = Inotify::init(InitFlags::IN_CLOEXEC | InitFlags::IN_NONBLOCK).ok()?; + inotify + .add_watch( + DEV_INPUT_DIR, + AddWatchFlags::IN_CREATE | AddWatchFlags::IN_ATTRIB, + ) + .ok()?; + if unsafe { + self.poller + .add(&inotify.as_fd(), PollEvent::readable(INOTIFY_KEY)) } - match lookup_input_seat(sysname) { - Some(found) => found == self.seat, - None => true, + .is_ok() + { + Some(inotify) + } else { + None } } fn open_all_devices(&mut self) { - let entries = match std::fs::read_dir("/dev/input") { - Ok(entries) => entries, - Err(_) => return, - }; - for entry in entries.flatten() { - let name = entry.file_name(); - let name_str = match name.to_str() { - Some(s) => s, - None => continue, - }; - if !name_str.starts_with("event") { - continue; - } - if !self.device_is_on_our_seat(name_str) { - continue; - } - let path = entry.path(); + for path in self.seats.event_node_paths() { let _ = self.open_device(&path); } } + fn open_hotplugged_device(&mut self, path: &Path) { + if let Err(err) = self.open_device(path) + && err.kind() == std::io::ErrorKind::PermissionDenied + && !self.retries.iter().any(|retry| retry.path == path) + { + self.retries.push(PermissionRetry { + path: path.to_path_buf(), + due: Instant::now() + PERMISSION_RETRY_DELAY, + }); + } + } + + fn next_retry_timeout(&self) -> Option { + let now = Instant::now(); + self.retries + .iter() + .map(|retry| retry.due.saturating_duration_since(now)) + .min() + } + + fn run_due_retries(&mut self) { + let now = Instant::now(); + let (due, pending): (Vec, Vec) = + self.retries.drain(..).partition(|retry| retry.due <= now); + self.retries = pending; + for retry in due { + let _ = self.open_device(&retry.path); + } + } + fn open_device(&mut self, path: &Path) -> std::io::Result<()> { if self .devices @@ -286,11 +389,10 @@ impl Reader { { return Ok(()); } - let device = Device::open(path)?; + let device = open_read_only(path)?; if !device_has_routable_input(&device) { return Ok(()); } - let _ = device.set_nonblocking(true); let fd = device.as_raw_fd(); let poll_key = poll_key_for_device_fd(fd).ok_or_else(|| { std::io::Error::new( @@ -307,8 +409,11 @@ impl Reader { OpenedDevice { device, path: path.to_path_buf(), + held: HashSet::new(), + dropped: false, }, ); + self.retries.retain(|retry| retry.path != path); Ok(()) } @@ -327,28 +432,65 @@ impl Reader { if let Some(opened) = self.devices.remove(&fd) { let borrowed = unsafe { BorrowedFd::borrow_raw(fd) }; let _ = self.poller.delete(borrowed); - drop(opened); + let OpenedDevice { device, held, .. } = opened; + drop(device); + self.release_codes(held); } } + fn release_all(&mut self) { + let mut held = HashSet::new(); + for opened in self.devices.values_mut() { + held.extend(opened.held.drain()); + } + self.release_codes(held); + } + + fn release_codes(&mut self, codes: HashSet) { + let mut codes: Vec = codes + .into_iter() + .filter(|code| !self.is_held(*code)) + .collect(); + codes.sort_unstable(); + for code in codes { + self.dispatch(self.native_event(code, false)); + } + } + + fn apply_session_gate(&mut self) { + let active = self.gate.is_active(); + if self.delivering && !active { + self.release_all(); + } + self.delivering = active; + } + fn run(&mut self) { let monitor = self.try_attach_monitor(); + let inotify = self.try_attach_inotify(); + self.open_all_devices(); let mut events = Events::new(); loop { if self.stop.load(Ordering::Acquire) { break; } events.clear(); - if self.poller.wait(&mut events, None).is_err() { + if self + .poller + .wait(&mut events, self.next_retry_timeout()) + .is_err() + { break; } let mut device_fds_to_drain: Vec = Vec::new(); let mut drain_monitor = false; + let mut drain_inotify = false; let mut got_exit = false; for event in events.iter() { match event.key { EXIT_KEY => got_exit = true, MONITOR_KEY => drain_monitor = true, + INOTIFY_KEY => drain_inotify = true, fd_key => { if let Some(fd) = device_fd_from_poll_key(fd_key) { device_fds_to_drain.push(fd); @@ -361,26 +503,43 @@ impl Reader { break; } - self.rearm(monitor.as_ref(), drain_monitor, &device_fds_to_drain); + self.rearm( + monitor.as_ref(), + drain_monitor, + inotify.as_ref().filter(|_| drain_inotify), + &device_fds_to_drain, + ); + self.apply_session_gate(); if drain_monitor && let Some(monitor) = monitor.as_ref() { self.drain_monitor(monitor); } + if drain_inotify && let Some(inotify) = inotify.as_ref() { + self.drain_inotify(inotify); + } + self.run_due_retries(); for fd in device_fds_to_drain { self.drain_device(fd); } } + if !self.stop.load(Ordering::Acquire) { + self.release_all(); + } if let Some(monitor) = monitor.as_ref() { let borrowed = unsafe { BorrowedFd::borrow_raw(monitor.as_raw_fd()) }; let _ = self.poller.delete(borrowed); } + if let Some(inotify) = inotify.as_ref() { + let _ = self.poller.delete(inotify.as_fd()); + } } fn rearm( &self, monitor: Option<&udev::MonitorSocket>, drain_monitor: bool, + inotify: Option<&Inotify>, device_fds: &[RawFd], ) { let _ = self @@ -392,6 +551,11 @@ impl Reader { .poller .modify(borrowed, PollEvent::readable(MONITOR_KEY)); } + if let Some(inotify) = inotify { + let _ = self + .poller + .modify(inotify.as_fd(), PollEvent::readable(INOTIFY_KEY)); + } for fd in device_fds { if self.devices.contains_key(fd) { let Some(poll_key) = poll_key_for_device_fd(*fd) else { @@ -428,13 +592,13 @@ impl Reader { for (action, devnode, seat) in pending { match action.as_str() { "add" => { - if !self.seat.is_empty() + if !self.seats.seat.is_empty() && let Some(seat) = seat.as_deref() - && seat != self.seat + && seat != self.seats.seat { continue; } - let _ = self.open_device(&devnode); + self.open_hotplugged_device(&devnode); } "remove" => self.close_device_by_path(&devnode), _ => {} @@ -442,22 +606,60 @@ impl Reader { } } + fn drain_inotify(&mut self, inotify: &Inotify) { + let mut paths: Vec = Vec::new(); + while let Ok(events) = inotify.read_events() { + if events.is_empty() { + break; + } + for event in events { + let Some(name) = event.name.as_deref().and_then(OsStr::to_str) else { + continue; + }; + if !is_event_node_name(name) || !self.seats.includes(name) { + continue; + } + let path = Path::new(DEV_INPUT_DIR).join(name); + if !paths.contains(&path) { + paths.push(path); + } + } + } + for path in paths { + self.open_hotplugged_device(&path); + } + } + fn drain_device(&mut self, fd: RawFd) { - let mut decoded: Vec<(u16, i32)> = Vec::new(); + let mut decoded: Vec = Vec::new(); let mut device_dead = false; { let Some(opened) = self.devices.get_mut(&fd) else { return; }; loop { - let fetch_result = opened.device.fetch_events(); - match fetch_result { + match opened.device.fetch_events() { Ok(events) => { for ev in events { - if ev.event_type() != EventType::KEY { - continue; + match (ev.event_type(), ev.code()) { + (EventType::SYNCHRONIZATION, code) + if code == SynchronizationCode::SYN_DROPPED.0 => + { + opened.dropped = true; + } + (EventType::SYNCHRONIZATION, code) + if opened.dropped + && code == SynchronizationCode::SYN_REPORT.0 => + { + opened.dropped = false; + decoded.push(DeviceInput::Resync); + } + _ if opened.dropped => {} + (EventType::KEY, code) => { + decoded.push(DeviceInput::Key(code, ev.value())); + } + _ => {} } - decoded.push((ev.code(), ev.value())); } } Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => break, @@ -472,50 +674,86 @@ impl Reader { self.close_device_by_fd(fd); return; } - for (code, value) in decoded { - if value == 2 { - continue; + if !self.delivering { + return; + } + for input in decoded { + match input { + DeviceInput::Key(_, 2) => {} + DeviceInput::Key(code, value) => self.translate_event(fd, code, value == 1), + DeviceInput::Resync => self.resync_device(fd), } - self.translate_event(code, value == 1); } } - fn translate_event(&mut self, code: u16, is_press: bool) { + fn resync_device(&mut self, fd: RawFd) { + let Some(opened) = self.devices.get_mut(&fd) else { + return; + }; + let released = match opened.device.get_key_state() { + Ok(state) => { + let down: HashSet = state + .iter() + .map(|key| key.code()) + .filter(|code| is_routable_key_code(*code)) + .collect(); + let released: HashSet = opened.held.difference(&down).copied().collect(); + opened.held = down; + released + } + Err(_) => std::mem::take(&mut opened.held), + }; + self.release_codes(released); + } + + fn translate_event(&mut self, fd: RawFd, code: u16, is_press: bool) { + if !is_routable_key_code(code) { + return; + } + if let Some(opened) = self.devices.get_mut(&fd) { + if is_press { + opened.held.insert(code); + } else { + opened.held.remove(&code); + } + } + self.dispatch(self.native_event(code, is_press)); + } + + fn native_event(&self, code: u16, is_press: bool) -> NativeEvent { + let ctrl = self.modifier_state_ctrl(); + let alt = self.modifier_state_alt(); + let shift = self.modifier_state_shift(); + let meta = self.modifier_state_meta(); if let Some(button) = keymap::evdev_button_to_browser_button(code) { - let event = NativeEvent::Mouse { + return NativeEvent::Mouse { kind: if is_press { MouseKind::Down } else { MouseKind::Up }, button, - ctrl: self.modifier_state_ctrl(), - alt: self.modifier_state_alt(), - shift: self.modifier_state_shift(), - meta: self.modifier_state_meta(), + ctrl, + alt, + shift, + meta, }; - self.dispatch(event); - return; } - let key_name = match keymap::keycode_to_name(code) { - Some(name) => name, - None => return, - }; - if is_press { - self.held_keys.insert(code); - } else { - self.held_keys.remove(&code); - } - let event = NativeEvent::Key { + NativeEvent::Key { kind: if is_press { KeyKind::Down } else { KeyKind::Up }, keycode: code, - key_name, - ctrl: self.modifier_state_ctrl(), - alt: self.modifier_state_alt(), - shift: self.modifier_state_shift(), - meta: self.modifier_state_meta(), - }; - self.dispatch(event); + key_name: keymap::keycode_to_name(code).unwrap_or_default(), + ctrl, + alt, + shift, + meta, + } + } + + fn is_held(&self, code: u16) -> bool { + self.devices + .values() + .any(|opened| opened.held.contains(&code)) } fn dispatch(&self, event: NativeEvent) { @@ -528,32 +766,53 @@ impl Reader { } fn modifier_state_ctrl(&self) -> bool { - self.held_keys.contains(&keymap::LEFT_CTRL) || self.held_keys.contains(&keymap::RIGHT_CTRL) + self.is_held(keymap::LEFT_CTRL) || self.is_held(keymap::RIGHT_CTRL) } fn modifier_state_alt(&self) -> bool { - self.held_keys.contains(&keymap::LEFT_ALT) || self.held_keys.contains(&keymap::RIGHT_ALT) + self.is_held(keymap::LEFT_ALT) || self.is_held(keymap::RIGHT_ALT) } fn modifier_state_shift(&self) -> bool { - self.held_keys.contains(&keymap::LEFT_SHIFT) - || self.held_keys.contains(&keymap::RIGHT_SHIFT) + self.is_held(keymap::LEFT_SHIFT) || self.is_held(keymap::RIGHT_SHIFT) } fn modifier_state_meta(&self) -> bool { - self.held_keys.contains(&keymap::LEFT_META) || self.held_keys.contains(&keymap::RIGHT_META) + self.is_held(keymap::LEFT_META) || self.is_held(keymap::RIGHT_META) } } +const KEYBOARD_KEY_CODES: Range = KeyCode::KEY_ESC.code()..KeyCode::BTN_0.code(); +const EXTENDED_KEY_CODES: Range = KeyCode::KEY_OK.code()..KeyCode::BTN_TRIGGER_HAPPY1.code(); + fn is_routable_key_code(code: u16) -> bool { - keymap::keycode_to_name(code).is_some() + KEYBOARD_KEY_CODES.contains(&code) + || EXTENDED_KEY_CODES.contains(&code) || keymap::evdev_button_to_browser_button(code).is_some() } -fn device_has_routable_input(device: &Device) -> bool { +fn device_has_routable_input(device: &RawDevice) -> bool { device.supported_keys().is_some_and(|keys| { keys.iter() .any(|key: KeyCode| is_routable_key_code(key.code())) }) } +fn is_full_keyboard_key_set(supports: impl Fn(u16) -> bool) -> bool { + FULL_KEYBOARD_KEY_CODES.into_iter().all(supports) +} + +fn device_is_full_keyboard(device: &RawDevice) -> bool { + device + .supported_keys() + .is_some_and(|keys| is_full_keyboard_key_set(|code| keys.contains(KeyCode::new(code)))) +} + +fn open_read_only(path: &Path) -> std::io::Result { + let file = std::fs::OpenOptions::new() + .read(true) + .custom_flags(OFlag::O_NONBLOCK.bits()) + .open(path)?; + RawDevice::from_fd(OwnedFd::from(file)) +} + impl Drop for Reader { fn drop(&mut self) { let fds: Vec = self.devices.keys().copied().collect(); @@ -570,11 +829,12 @@ struct HookInner { stop: Option>, exit_fd: Option>, thread: Option>, - opened: bool, + session_watch: Option, } impl HookInner { fn stop_and_join(&mut self) { + self.session_watch = None; if let Some(stop) = &self.stop { stop.store(true, Ordering::Release); } @@ -586,7 +846,6 @@ impl HookInner { } self.stop = None; self.exit_fd = None; - self.opened = false; } } @@ -605,7 +864,6 @@ impl EvdevHook { .build_threadsafe_function::() .weak::() .callee_handled::() - .max_queue_size::() .build() .map_err(|err| { generic_error(format!("failed to build callback: {}", err.reason)) @@ -617,7 +875,7 @@ impl EvdevHook { stop: None, exit_fd: None, thread: None, - opened: false, + session_watch: None, }), }) } @@ -628,8 +886,11 @@ impl EvdevHook { .inner .lock() .map_err(|_| generic_error("hook lock poisoned"))?; - if inner.thread.is_some() { - return Ok(inner.opened); + if let Some(thread) = inner.thread.as_ref() { + if !thread.is_finished() { + return Ok(true); + } + inner.stop_and_join(); } let stop = Arc::new(AtomicBool::new(false)); let exit_fd = Arc::new( @@ -640,8 +901,12 @@ impl EvdevHook { .map_err(|err| generic_error(format!("evdev start failed: {err}")))?; reader.open_all_devices(); - let opened = !reader.devices.is_empty(); + if !reader.has_full_keyboard() { + return Ok(false); + } + let session_watch = SessionWatch::spawn(reader.gate.clone()) + .map_err(|err| generic_error(format!("failed to spawn logind thread: {err}")))?; let join = thread::Builder::new() .name("fluxer-linux-evdev-reader".to_string()) .spawn(move || { @@ -653,8 +918,8 @@ impl EvdevHook { inner.stop = Some(stop); inner.exit_fd = Some(exit_fd); inner.thread = Some(join); - inner.opened = opened; - Ok(opened) + inner.session_watch = Some(session_watch); + Ok(true) } #[napi] @@ -676,9 +941,12 @@ impl Drop for EvdevHook { } } -#[napi(js_name = "nameToEvdevKeycode")] -pub fn name_to_evdev_keycode(name: String) -> u32 { - u32::from(keymap::name_to_keycode(&name)) +#[napi(js_name = "isKeyboardReadable")] +pub fn is_keyboard_readable() -> bool { + SeatFilter::detect() + .event_node_paths() + .iter() + .any(|path| open_read_only(path).is_ok_and(|device| device_is_full_keyboard(&device))) } fn generic_error(reason: impl Into) -> napi::Error { @@ -693,11 +961,50 @@ mod tests { fn control_poll_keys_are_legal_and_outside_fd_range() { assert_eq!(EXIT_KEY, 0); assert_eq!(MONITOR_KEY, 1); - assert_ne!(EXIT_KEY, MONITOR_KEY); + assert_eq!(INOTIFY_KEY, 2); assert_eq!(poll_key_for_device_fd(0), Some(DEVICE_KEY_BASE)); assert_eq!(device_fd_from_poll_key(DEVICE_KEY_BASE), Some(0)); assert_eq!(device_fd_from_poll_key(EXIT_KEY), None); assert_eq!(device_fd_from_poll_key(MONITOR_KEY), None); + assert_eq!(device_fd_from_poll_key(INOTIFY_KEY), None); + } + + #[test] + fn full_keyboard_requires_every_key_from_escape_to_s() { + assert!(is_full_keyboard_key_set(|code| code < 200)); + assert!(!is_full_keyboard_key_set(|code| code != 0 && code != 17)); + assert!(!is_full_keyboard_key_set(|code| code == 0 || code >= 32)); + assert!(is_full_keyboard_key_set(|code| (1..32).contains(&code))); + } + + #[test] + fn event_node_names_match_only_event_devices() { + assert!(is_event_node_name("event0")); + assert!(is_event_node_name("event17")); + assert!(!is_event_node_name("mouse0")); + assert!(!is_event_node_name("js0")); + assert!(!is_event_node_name("by-id")); + } + + #[test] + fn session_gate_notifies_only_on_change() { + let poller = Arc::new(Poller::new().expect("create poller")); + let gate = SessionGate::new(poller.clone()); + assert!(!gate.is_active()); + gate.set_active(false); + let mut events = Events::new(); + poller + .wait(&mut events, Some(Duration::from_millis(0))) + .expect("poll without notification"); + gate.set_active(true); + assert!(gate.is_active()); + let started = Instant::now(); + poller + .wait(&mut events, Some(Duration::from_secs(5))) + .expect("poll with notification"); + assert!(started.elapsed() < Duration::from_secs(5)); + gate.set_active(false); + assert!(!gate.is_active()); } #[test] @@ -722,6 +1029,37 @@ mod tests { assert!(is_routable_key_code(KeyCode::BTN_FORWARD.code())); } + #[test] + fn every_key_main_maps_to_a_dom_code_is_routable() { + let source = include_str!("../../../src/main/GlobalShortcutKeys.ts"); + let table = source + .split("const EVDEV_KEY_CODES") + .nth(1) + .and_then(|rest| rest.split("]);").next()) + .expect("EVDEV_KEY_CODES table"); + let codes: Vec = table + .split("[0x") + .skip(1) + .map(|entry| { + let hex = entry.split(',').next().expect("code"); + u16::from_str_radix(hex, 16).expect("hex code") + }) + .collect(); + assert!(codes.len() > 150); + for code in codes { + assert!(is_routable_key_code(code), "evdev code {code:#x}"); + } + } + + #[test] + fn routable_key_code_filter_keeps_keys_without_a_native_name() { + assert!(is_routable_key_code(KeyCode::KEY_CALC.code())); + assert!(is_routable_key_code(KeyCode::KEY_PLAYCD.code())); + assert!(is_routable_key_code(KeyCode::KEY_FN.code())); + assert!(!is_routable_key_code(0)); + assert!(!is_routable_key_code(KeyCode::BTN_TRIGGER_HAPPY1.code())); + } + #[test] fn routable_key_code_filter_ignores_tablet_pad_and_tool_buttons() { assert!(!is_routable_key_code(KeyCode::BTN_0.code())); diff --git a/fluxer_desktop/native/linux-evdev/src/logind.rs b/fluxer_desktop/native/linux-evdev/src/logind.rs new file mode 100644 index 000000000..df773b2bf --- /dev/null +++ b/fluxer_desktop/native/linux-evdev/src/logind.rs @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +use std::{ + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, + thread::{self, JoinHandle}, + time::Duration, +}; + +use event_listener::Event; +use futures_lite::{StreamExt, future}; +use polling::Poller; +use zbus::{Connection, Proxy, zvariant::OwnedObjectPath}; + +const LOGIND_DESTINATION: &str = "org.freedesktop.login1"; +const LOGIND_PATH: &str = "/org/freedesktop/login1"; +const MANAGER_INTERFACE: &str = "org.freedesktop.login1.Manager"; +const SESSION_INTERFACE: &str = "org.freedesktop.login1.Session"; +const USER_INTERFACE: &str = "org.freedesktop.login1.User"; +const RETRY_DELAYS: [Duration; 5] = [ + Duration::from_secs(1), + Duration::from_secs(2), + Duration::from_secs(5), + Duration::from_secs(10), + Duration::from_secs(30), +]; + +pub struct SessionGate { + active: AtomicBool, + poller: Arc, +} + +impl SessionGate { + pub fn new(poller: Arc) -> Self { + Self { + active: AtomicBool::new(false), + poller, + } + } + + pub fn is_active(&self) -> bool { + self.active.load(Ordering::Acquire) + } + + pub fn set_active(&self, active: bool) { + if self.active.swap(active, Ordering::AcqRel) != active { + let _ = self.poller.notify(); + } + } +} + +struct StopSignal { + stopped: AtomicBool, + event: Event, +} + +impl StopSignal { + async fn wait(&self) { + loop { + if self.stopped.load(Ordering::Acquire) { + return; + } + let listener = self.event.listen(); + if self.stopped.load(Ordering::Acquire) { + return; + } + listener.await; + } + } +} + +pub struct SessionWatch { + stop: Arc, + thread: Option>, +} + +impl SessionWatch { + pub fn spawn(gate: Arc) -> std::io::Result { + let stop = Arc::new(StopSignal { + stopped: AtomicBool::new(false), + event: Event::new(), + }); + let thread_stop = stop.clone(); + let thread = thread::Builder::new() + .name("fluxer-linux-evdev-logind".to_string()) + .spawn(move || { + async_io::block_on(future::or(supervise(&gate), thread_stop.wait())); + })?; + Ok(Self { + stop, + thread: Some(thread), + }) + } + + fn shutdown(&mut self) { + self.stop.stopped.store(true, Ordering::Release); + self.stop.event.notify(usize::MAX); + if let Some(thread) = self.thread.take() { + let _ = thread.join(); + } + } +} + +impl Drop for SessionWatch { + fn drop(&mut self) { + self.shutdown(); + } +} + +#[derive(Debug)] +enum WatchEnd { + Unreachable(zbus::Error), + NoSession(zbus::Error), + Lost(Option), +} + +fn retry_delay(failures: usize) -> Duration { + RETRY_DELAYS[failures.min(RETRY_DELAYS.len() - 1)] +} + +async fn supervise(gate: &SessionGate) { + let mut ever_subscribed = false; + let mut failures = 0usize; + loop { + let mut subscribed = false; + let end = watch_session(gate, &mut subscribed).await; + if subscribed { + ever_subscribed = true; + failures = 0; + } + match &end { + WatchEnd::Unreachable(err) if !ever_subscribed => { + eprintln!("[linux-evdev] logind unreachable, session gating disabled: {err}"); + gate.set_active(true); + return; + } + WatchEnd::NoSession(err) if !ever_subscribed => { + eprintln!("[linux-evdev] no logind session found, session gating disabled: {err}"); + gate.set_active(true); + return; + } + WatchEnd::Unreachable(err) | WatchEnd::NoSession(err) | WatchEnd::Lost(Some(err)) + if failures == 0 => + { + eprintln!("[linux-evdev] logind session watch lost, retrying: {err}"); + } + _ => {} + } + async_io::Timer::after(retry_delay(failures)).await; + failures = failures.saturating_add(1); + } +} + +fn session_id() -> Option { + std::env::var("XDG_SESSION_ID") + .ok() + .filter(|value| !value.is_empty()) +} + +fn is_unreachable_error(err: &zbus::Error) -> bool { + match err { + zbus::Error::MethodError(name, _, _) => matches!( + name.as_str(), + "org.freedesktop.DBus.Error.ServiceUnknown" + | "org.freedesktop.DBus.Error.NameHasNoOwner" + | "org.freedesktop.DBus.Error.AccessDenied" + | "org.freedesktop.DBus.Error.Disconnected" + ), + zbus::Error::InputOutput(_) | zbus::Error::Address(_) | zbus::Error::Handshake(_) => true, + _ => false, + } +} + +fn classify(err: zbus::Error) -> WatchEnd { + if is_unreachable_error(&err) { + WatchEnd::Unreachable(err) + } else { + WatchEnd::NoSession(err) + } +} + +fn is_session_path(path: &OwnedObjectPath) -> bool { + path.as_str() != "/" +} + +async fn display_session( + connection: &Connection, + manager: &Proxy<'_>, +) -> zbus::Result { + let user_path: OwnedObjectPath = manager.call("GetUserByPID", &(std::process::id(),)).await?; + let user = Proxy::new(connection, LOGIND_DESTINATION, user_path, USER_INTERFACE).await?; + let (id, path): (String, OwnedObjectPath) = user.get_property("Display").await?; + if id.is_empty() || !is_session_path(&path) { + return Err(zbus::Error::Failure( + "user has no display session".to_string(), + )); + } + Ok(path) +} + +async fn resolve_session( + connection: &Connection, + manager: &Proxy<'_>, +) -> Result { + if let Some(id) = session_id() { + match manager + .call::<_, _, OwnedObjectPath>("GetSession", &(id,)) + .await + { + Ok(path) => return Ok(path), + Err(err) if is_unreachable_error(&err) => return Err(WatchEnd::Unreachable(err)), + Err(_) => {} + } + } + match manager + .call::<_, _, OwnedObjectPath>("GetSessionByPID", &(std::process::id(),)) + .await + { + Ok(path) => return Ok(path), + Err(err) if is_unreachable_error(&err) => return Err(WatchEnd::Unreachable(err)), + Err(_) => {} + } + display_session(connection, manager).await.map_err(classify) +} + +async fn watch_session(gate: &SessionGate, subscribed: &mut bool) -> WatchEnd { + let connection = match Connection::system().await { + Ok(connection) => connection, + Err(err) => return WatchEnd::Unreachable(err), + }; + let manager = match Proxy::new( + &connection, + LOGIND_DESTINATION, + LOGIND_PATH, + MANAGER_INTERFACE, + ) + .await + { + Ok(manager) => manager, + Err(err) => return classify(err), + }; + let session_path = match resolve_session(&connection, &manager).await { + Ok(path) => path, + Err(end) => return end, + }; + let session = match Proxy::new( + &connection, + LOGIND_DESTINATION, + session_path, + SESSION_INTERFACE, + ) + .await + { + Ok(session) => session, + Err(err) => return classify(err), + }; + let mut owner_changes = match session.receive_owner_changed().await { + Ok(stream) => stream, + Err(err) => return classify(err), + }; + let mut changes = session.receive_property_changed::("Active").await; + match session.get_property::("Active").await { + Ok(active) => gate.set_active(active), + Err(err) => return classify(err), + } + *subscribed = true; + loop { + let change = future::or(async { changes.next().await }, async { + owner_changes.next().await; + None + }) + .await; + let Some(change) = change else { + return WatchEnd::Lost(None); + }; + match change.get().await { + Ok(active) => gate.set_active(active), + Err(err) => return WatchEnd::Lost(Some(err)), + } + } +} diff --git a/fluxer_desktop/native/linux-input-hook/index.d.ts b/fluxer_desktop/native/linux-input-hook/index.d.ts index 9aec61bcf..a057791e1 100644 --- a/fluxer_desktop/native/linux-input-hook/index.d.ts +++ b/fluxer_desktop/native/linux-input-hook/index.d.ts @@ -4,6 +4,7 @@ export type InputEvent = | { type: 'keydown' | 'keyup'; keycode: number; + x11Keycode: number; keyName: string; ctrlKey: boolean; altKey: boolean; @@ -19,32 +20,12 @@ export type InputEvent = metaKey: boolean; x?: number; y?: number; - } - | { - type: 'mousemove'; - x: number; - y: number; - ctrlKey: boolean; - altKey: boolean; - shiftKey: boolean; - metaKey: boolean; - } - | { - type: 'wheel'; - x?: number; - y?: number; - deltaX: number; - deltaY: number; - ctrlKey: boolean; - altKey: boolean; - shiftKey: boolean; - metaKey: boolean; }; export declare class InputHook { constructor(callback: (event: InputEvent) => void); - start(): void; + start(): Promise; stop(): void; } diff --git a/fluxer_desktop/native/linux-input-hook/src/env.rs b/fluxer_desktop/native/linux-input-hook/src/env.rs index 287326ded..a66dc020b 100644 --- a/fluxer_desktop/native/linux-input-hook/src/env.rs +++ b/fluxer_desktop/native/linux-input-hook/src/env.rs @@ -1,5 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +use std::ffi::OsStr; +use std::path::{Path, PathBuf}; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DisplayServer { X11, @@ -18,31 +21,45 @@ impl DisplayServer { } pub fn detect_display_server() -> DisplayServer { + let wayland_socket = wayland_socket_path( + std::env::var_os("WAYLAND_DISPLAY").as_deref(), + std::env::var_os("XDG_RUNTIME_DIR").as_deref(), + ); detect_from( std::env::var("XDG_SESSION_TYPE").ok().as_deref(), std::env::var("DISPLAY").ok().as_deref(), - std::env::var("WAYLAND_DISPLAY").ok().as_deref(), + wayland_socket.is_some_and(|path| path.exists()), ) } +fn wayland_socket_path( + wayland_display: Option<&OsStr>, + xdg_runtime_dir: Option<&OsStr>, +) -> Option { + let display = Path::new(wayland_display.filter(|value| !value.is_empty())?); + if display.is_absolute() { + return Some(display.to_path_buf()); + } + let runtime_dir = xdg_runtime_dir.filter(|value| !value.is_empty())?; + Some(Path::new(runtime_dir).join(display)) +} + fn detect_from( xdg_session_type: Option<&str>, display: Option<&str>, - wayland_display: Option<&str>, + wayland_socket_exists: bool, ) -> DisplayServer { let has_x11 = display.is_some_and(|v| !v.is_empty()); - let has_wayland = wayland_display.is_some_and(|v| !v.is_empty()); - let xdg_is_wayland = xdg_session_type == Some("wayland"); - match (has_x11, has_wayland) { + let is_wayland = match xdg_session_type { + Some("x11") => return DisplayServer::X11, + Some("wayland") => true, + _ => wayland_socket_exists, + }; + match (is_wayland, has_x11) { (true, true) => DisplayServer::WaylandWithXwayland, - (true, false) if xdg_is_wayland => DisplayServer::WaylandWithXwayland, - (true, false) => DisplayServer::X11, - (false, true) => DisplayServer::Wayland, - (false, false) => match xdg_session_type { - Some("x11") => DisplayServer::X11, - Some("wayland") => DisplayServer::Wayland, - _ => DisplayServer::Unknown, - }, + (true, false) => DisplayServer::Wayland, + (false, true) => DisplayServer::X11, + (false, false) => DisplayServer::Unknown, } } @@ -53,61 +70,101 @@ mod tests { #[test] fn detect_pure_x11_from_display_only() { assert_eq!( - detect_from(Some("x11"), Some(":0"), None), + detect_from(Some("x11"), Some(":0"), false), DisplayServer::X11 ); - assert_eq!(detect_from(None, Some(":0"), None), DisplayServer::X11); + assert_eq!(detect_from(None, Some(":0"), false), DisplayServer::X11); } #[test] - fn detect_pure_wayland_from_wayland_display_only() { + fn x11_session_type_wins_over_leaked_wayland_display() { assert_eq!( - detect_from(Some("wayland"), None, Some("wayland-0")), - DisplayServer::Wayland - ); - assert_eq!( - detect_from(None, None, Some("wayland-0")), - DisplayServer::Wayland + detect_from(Some("x11"), Some(":0"), true), + DisplayServer::X11 ); } #[test] - fn detect_xwayland_when_both_sockets_present() { - let ds = detect_from(Some("wayland"), Some(":0"), Some("wayland-0")); + fn detect_pure_wayland_from_socket_only() { + assert_eq!( + detect_from(Some("wayland"), None, true), + DisplayServer::Wayland + ); + assert_eq!(detect_from(None, None, true), DisplayServer::Wayland); + } + + #[test] + fn detect_xwayland_when_socket_and_display_present() { + let ds = detect_from(None, Some(":0"), true); assert_eq!(ds, DisplayServer::WaylandWithXwayland); assert!(!ds.supports_global_xrecord()); } #[test] fn detect_xwayland_from_wayland_session_with_display_only() { - let ds = detect_from(Some("wayland"), Some(":0"), None); + let ds = detect_from(Some("wayland"), Some(":0"), false); assert_eq!(ds, DisplayServer::WaylandWithXwayland); assert!(!ds.supports_global_xrecord()); } + #[test] + fn missing_wayland_socket_without_session_type_is_x11() { + assert_eq!( + detect_from(Some("tty"), Some(":0"), false), + DisplayServer::X11 + ); + } + #[test] fn detect_unknown_when_nothing_set() { - assert_eq!(detect_from(None, None, None), DisplayServer::Unknown); - assert_eq!(detect_from(Some("tty"), None, None), DisplayServer::Unknown); + assert_eq!(detect_from(None, None, false), DisplayServer::Unknown); + assert_eq!( + detect_from(Some("tty"), None, false), + DisplayServer::Unknown + ); } #[test] fn detect_empty_display_var_is_ignored() { assert_eq!( - detect_from(Some("wayland"), Some(""), Some("wayland-0")), + detect_from(Some("wayland"), Some(""), true), DisplayServer::Wayland ); } #[test] - fn xdg_session_type_fallback_when_sockets_missing() { - assert_eq!(detect_from(Some("x11"), None, None), DisplayServer::X11); + fn xdg_session_type_without_sockets() { + assert_eq!(detect_from(Some("x11"), None, false), DisplayServer::X11); assert_eq!( - detect_from(Some("wayland"), None, None), + detect_from(Some("wayland"), None, false), DisplayServer::Wayland ); } + #[test] + fn wayland_socket_path_resolves_relative_names_under_runtime_dir() { + assert_eq!( + wayland_socket_path( + Some(OsStr::new("wayland-0")), + Some(OsStr::new("/run/user/1000")) + ), + Some(PathBuf::from("/run/user/1000/wayland-0")) + ); + assert_eq!( + wayland_socket_path(Some(OsStr::new("/tmp/wl")), None), + Some(PathBuf::from("/tmp/wl")) + ); + assert_eq!( + wayland_socket_path(Some(OsStr::new("wayland-0")), None), + None + ); + assert_eq!( + wayland_socket_path(Some(OsStr::new("")), Some(OsStr::new("/run"))), + None + ); + assert_eq!(wayland_socket_path(None, Some(OsStr::new("/run"))), None); + } + #[test] fn global_xrecord_support_is_only_for_pure_x11() { assert!(DisplayServer::X11.supports_global_xrecord()); diff --git a/fluxer_desktop/native/linux-input-hook/src/hook.rs b/fluxer_desktop/native/linux-input-hook/src/hook.rs index 1e0f1c685..b6119e06e 100644 --- a/fluxer_desktop/native/linux-input-hook/src/hook.rs +++ b/fluxer_desktop/native/linux-input-hook/src/hook.rs @@ -2,34 +2,37 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -use std::sync::atomic::{AtomicBool, Ordering}; +use std::collections::BTreeSet; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::mpsc::{self, SyncSender}; use std::sync::{Arc, Mutex}; use std::thread::{self, JoinHandle}; +use std::time::Duration; use napi::{ - Env, Status, - bindgen_prelude::{Function, Object, Result, ToNapiValue}, + Env, Status, Task, + bindgen_prelude::{AsyncTask, Function, Object, Result, ToNapiValue}, sys, threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode, UnknownReturnValue}, }; use napi_derive::napi; use x11rb::connection::{Connection, RequestConnection}; use x11rb::errors::ReplyError; +use x11rb::protocol::Event; use x11rb::protocol::record::{ self, ConnectionExt as RecordConnectionExt, ExtRange, Range, Range8, Range16, }; use x11rb::protocol::xproto::{ - ConnectionExt as XprotoConnectionExt, GetKeyboardMappingReply, Keycode, + ConnectionExt as XprotoConnectionExt, GetKeyboardMappingReply, Keycode, Mapping, }; use x11rb::rust_connection::RustConnection; use x11rb::wrapper::ConnectionExt as WrapperConnectionExt; use x11rb::x11_utils::TryParse; -use crate::env::{DisplayServer, detect_display_server}; +use crate::env::detect_display_server; use crate::keymap; use crate::modifiers::{self, Modifiers}; -use crate::mouse::{self, MouseClassification}; -use crate::x11; +use crate::mouse; const RECORD_FROM_SERVER: u8 = 0; const RECORD_START_OF_DATA: u8 = 4; @@ -38,7 +41,7 @@ const KEY_PRESS: u8 = 2; const KEY_RELEASE: u8 = 3; const BUTTON_PRESS: u8 = 4; const BUTTON_RELEASE: u8 = 5; -const MOTION_NOTIFY: u8 = 6; +const START_TIMEOUT: Duration = Duration::from_secs(2); #[repr(C)] #[derive(Debug, Clone, Copy)] @@ -66,18 +69,26 @@ pub enum EventKind { KeyUp, MouseDown, MouseUp, - MouseMove, - Wheel, +} + +impl EventKind { + fn as_str(&self) -> &'static str { + match self { + Self::KeyDown => "keydown", + Self::KeyUp => "keyup", + Self::MouseDown => "mousedown", + Self::MouseUp => "mouseup", + } + } } #[derive(Debug, Clone)] pub struct DecodedEvent { pub kind: EventKind, pub keycode: u32, + pub x11_keycode: u8, pub key_name: String, pub button: u8, - pub delta_x: i32, - pub delta_y: i32, pub x: i32, pub y: i32, pub has_xy: bool, @@ -89,10 +100,9 @@ impl DecodedEvent { Self { kind, keycode: 0, + x11_keycode: 0, key_name: String::new(), button: 0, - delta_x: 0, - delta_y: 0, x: 0, y: 0, has_xy: false, @@ -105,15 +115,7 @@ impl ToNapiValue for DecodedEvent { unsafe fn to_napi_value(raw_env: sys::napi_env, event: Self) -> Result { let env = Env::from_raw(raw_env); let mut object = Object::new(&env)?; - let kind = match event.kind { - EventKind::KeyDown => "keydown", - EventKind::KeyUp => "keyup", - EventKind::MouseDown => "mousedown", - EventKind::MouseUp => "mouseup", - EventKind::MouseMove => "mousemove", - EventKind::Wheel => "wheel", - }; - object.set("type", kind)?; + object.set("type", event.kind.as_str())?; object.set("ctrlKey", event.mods.ctrl)?; object.set("altKey", event.mods.alt)?; object.set("shiftKey", event.mods.shift)?; @@ -121,6 +123,7 @@ impl ToNapiValue for DecodedEvent { match event.kind { EventKind::KeyDown | EventKind::KeyUp => { object.set("keycode", event.keycode)?; + object.set("x11Keycode", u32::from(event.x11_keycode))?; object.set("keyName", event.key_name.as_str())?; } EventKind::MouseDown | EventKind::MouseUp => { @@ -130,36 +133,13 @@ impl ToNapiValue for DecodedEvent { object.set("y", event.y)?; } } - EventKind::MouseMove => { - object.set("x", event.x)?; - object.set("y", event.y)?; - } - EventKind::Wheel => { - object.set("deltaX", event.delta_x)?; - object.set("deltaY", event.delta_y)?; - if event.has_xy { - object.set("x", event.x)?; - object.set("y", event.y)?; - } - } } unsafe { as ToNapiValue>::to_napi_value(raw_env, object) } } } -const EVENT_QUEUE_LIMIT: usize = 1024; - -type EventTsfn = Arc< - ThreadsafeFunction< - DecodedEvent, - UnknownReturnValue, - DecodedEvent, - Status, - false, - true, - EVENT_QUEUE_LIMIT, - >, ->; +type EventTsfn = + Arc>; #[derive(Clone)] struct KeysymCache { @@ -180,6 +160,21 @@ impl KeysymCache { Self { min_keycode, syms } } + fn fetch(conn: &RustConnection) -> std::result::Result { + let setup = conn.setup(); + let min_keycode = setup.min_keycode; + let count = setup + .max_keycode + .saturating_sub(min_keycode) + .saturating_add(1); + let mapping = conn + .get_keyboard_mapping(min_keycode, count) + .map_err(|err| format!("GetKeyboardMapping: {err}"))? + .reply() + .map_err(|err| format!("GetKeyboardMapping: {err}"))?; + Ok(Self::build(&mapping, min_keycode)) + } + fn lookup(&self, keycode: u8) -> u32 { if keycode < self.min_keycode { return 0; @@ -199,6 +194,78 @@ struct Active { struct Inner { callback: EventTsfn, active: Mutex>, + generation: AtomicU64, +} + +impl Inner { + fn lock(&self) -> Result>> { + self.active + .lock() + .map_err(|_| generic_error("InputHook lock poisoned")) + } + + fn start(&self, generation: u64) -> Result<()> { + let dead = { + let mut guard = self.lock()?; + if guard + .as_ref() + .is_some_and(|active| !active.stop.load(Ordering::Acquire)) + { + return Ok(()); + } + guard.take() + }; + if let Some(dead) = dead { + tear_down(dead); + } + let started = start_record(self.callback.clone())?; + let mut guard = self.lock()?; + if self.generation.load(Ordering::Acquire) != generation { + drop(guard); + tear_down(started); + return Err(generic_error( + "InputHook.start failed: stopped during start", + )); + } + if guard + .as_ref() + .is_some_and(|active| !active.stop.load(Ordering::Acquire)) + { + drop(guard); + tear_down(started); + return Ok(()); + } + let replaced = guard.replace(started); + drop(guard); + if let Some(dead) = replaced { + tear_down(dead); + } + Ok(()) + } + + fn stop(&self) -> Result> { + let mut guard = self.lock()?; + self.generation.fetch_add(1, Ordering::AcqRel); + Ok(guard.take()) + } +} + +pub struct StartTask { + inner: Arc, + generation: u64, +} + +impl Task for StartTask { + type Output = (); + type JsValue = (); + + fn compute(&mut self) -> Result { + self.inner.start(self.generation) + } + + fn resolve(&mut self, _env: Env, _output: Self::Output) -> Result { + Ok(()) + } } #[napi] @@ -214,43 +281,28 @@ impl InputHook { .build_threadsafe_function::() .weak::() .callee_handled::() - .max_queue_size::() .build() .map_err(|err| generic_error(format!("failed to create TSFN: {}", err.reason)))?; Ok(Self { inner: Arc::new(Inner { callback: Arc::new(tsfn), active: Mutex::new(None), + generation: AtomicU64::new(0), }), }) } #[napi] - pub fn start(&self) -> Result<()> { - let mut guard = self - .inner - .active - .lock() - .map_err(|_| generic_error("InputHook lock poisoned"))?; - if guard.is_some() { - return Ok(()); - } - let active = start_record(self.inner.callback.clone())?; - *guard = Some(active); - Ok(()) + pub fn start(&self) -> AsyncTask { + AsyncTask::new(StartTask { + inner: self.inner.clone(), + generation: self.inner.generation.load(Ordering::Acquire), + }) } #[napi] pub fn stop(&self) -> Result<()> { - let active = { - let mut guard = self - .inner - .active - .lock() - .map_err(|_| generic_error("InputHook lock poisoned"))?; - guard.take() - }; - if let Some(active) = active { + if let Some(active) = self.inner.stop()? { tear_down(active); } Ok(()) @@ -259,9 +311,7 @@ impl InputHook { impl Drop for InputHook { fn drop(&mut self) { - if let Ok(mut guard) = self.inner.active.lock() - && let Some(active) = guard.take() - { + if let Ok(Some(active)) = self.inner.stop() { tear_down(active); } } @@ -304,18 +354,8 @@ fn start_record(callback: EventTsfn) -> Result { generic_error(format!("InputHook.start failed: RecordQueryVersion: {err}")) })?; - let setup = ctrl_conn.setup(); - let min_keycode = setup.min_keycode; - let max_keycode = setup.max_keycode; - let count = max_keycode.saturating_sub(min_keycode).saturating_add(1); - let mapping = ctrl_conn - .get_keyboard_mapping(min_keycode, count) - .map_err(|err| generic_error(format!("InputHook.start failed: GetKeyboardMapping: {err}")))? - .reply() - .map_err(|err| { - generic_error(format!("InputHook.start failed: GetKeyboardMapping: {err}")) - })?; - let keysyms = KeysymCache::build(&mapping, min_keycode); + let keysyms = KeysymCache::fetch(&ctrl_conn) + .map_err(|err| generic_error(format!("InputHook.start failed: {err}")))?; let record_ctx = ctrl_conn .generate_id() @@ -333,7 +373,7 @@ fn start_record(callback: EventTsfn) -> Result { delivered_events: empty, device_events: Range8 { first: KEY_PRESS, - last: MOTION_NOTIFY, + last: BUTTON_RELEASE, }, errors: empty, client_started: false, @@ -354,168 +394,248 @@ fn start_record(callback: EventTsfn) -> Result { })?; let stop = Arc::new(AtomicBool::new(false)); + let (ready_tx, ready_rx) = mpsc::sync_channel(1); + let worker_ctrl = ctrl_conn.clone(); let worker_callback = callback.clone(); let worker_stop = stop.clone(); let worker = thread::Builder::new() .name("fluxer-linux-input-hook".to_string()) .spawn(move || { - worker_main(data_conn, record_ctx, keysyms, worker_callback, worker_stop); - }) - .map_err(|err| generic_error(format!("InputHook.start failed: thread spawn: {err}")))?; + worker_main( + &data_conn, + Worker { + ctrl_conn: worker_ctrl, + record_ctx, + keysyms, + callback: worker_callback, + stop: worker_stop, + ready: Some(ready_tx), + held: HeldInputs::default(), + }, + ); + }); + let worker = match worker { + Ok(worker) => worker, + Err(err) => { + let _ = ctrl_conn.record_free_context(record_ctx); + let _ = ctrl_conn.sync(); + return Err(generic_error(format!( + "InputHook.start failed: thread spawn: {err}" + ))); + } + }; - Ok(Active { + let active = Active { ctrl_conn, record_ctx, worker: Some(worker), stop, - }) + }; + let failure = match ready_rx.recv_timeout(START_TIMEOUT) { + Ok(Ok(())) => return Ok(active), + Ok(Err(reason)) => reason, + Err(mpsc::RecvTimeoutError::Timeout) => "RecordEnableContext timed out".to_string(), + Err(mpsc::RecvTimeoutError::Disconnected) => { + "RecordEnableContext worker exited".to_string() + } + }; + abandon(active); + Err(generic_error(format!("InputHook.start failed: {failure}"))) } -fn worker_main( - data_conn: RustConnection, +struct Worker { + ctrl_conn: Arc, record_ctx: record::Context, keysyms: KeysymCache, callback: EventTsfn, stop: Arc, -) { - let cookie = match data_conn.record_enable_context(record_ctx) { + ready: Option>>, + held: HeldInputs, +} + +#[derive(Default)] +struct HeldInputs { + keys: BTreeSet, + buttons: BTreeSet, +} + +impl HeldInputs { + fn track(&mut self, event: &DecodedEvent) { + match event.kind { + EventKind::KeyDown => { + self.keys.insert(event.x11_keycode); + } + EventKind::KeyUp => { + self.keys.remove(&event.x11_keycode); + } + EventKind::MouseDown => { + self.buttons.insert(event.button); + } + EventKind::MouseUp => { + self.buttons.remove(&event.button); + } + } + } + + fn take_releases(&mut self, keysyms: &KeysymCache) -> Vec { + let mods = modifiers::from_state(0); + let keys = std::mem::take(&mut self.keys) + .into_iter() + .map(|x11_keycode| key_event(EventKind::KeyUp, x11_keycode, keysyms, mods)); + let buttons = std::mem::take(&mut self.buttons).into_iter().map(|button| { + let mut event = DecodedEvent::new(EventKind::MouseUp, mods); + event.button = button; + event + }); + keys.chain(buttons).collect() + } +} + +impl Worker { + fn report_ready(&mut self, result: std::result::Result<(), String>) { + if let Some(ready) = self.ready.take() { + let _ = ready.send(result); + } + } + + fn refresh_keysyms_on_mapping_change(&mut self, data_conn: &RustConnection) { + let mut keyboard_changed = false; + while let Ok(Some(event)) = data_conn.poll_for_event() { + if let Event::MappingNotify(notify) = event + && notify.request == Mapping::KEYBOARD + { + keyboard_changed = true; + } + } + if !keyboard_changed { + return; + } + if let Ok(keysyms) = KeysymCache::fetch(&self.ctrl_conn) { + self.keysyms = keysyms; + } + while let Ok(Some(_)) = self.ctrl_conn.poll_for_event() {} + } +} + +fn worker_main(data_conn: &RustConnection, mut worker: Worker) { + let cookie = match data_conn.record_enable_context(worker.record_ctx) { Ok(c) => c, - Err(_) => { - stop.store(true, Ordering::Release); + Err(err) => { + worker.stop.store(true, Ordering::Release); + worker.report_ready(Err(format!("RecordEnableContext: {err}"))); return; } }; for reply in cookie { - if stop.load(Ordering::Acquire) { + if worker.stop.load(Ordering::Acquire) { break; } let reply = match reply { Ok(r) => r, + Err(err) if worker.ready.is_some() => { + worker.report_ready(Err(format!("RecordEnableContext: {err}"))); + break; + } Err(ReplyError::ConnectionError(_)) => break, Err(_) => continue, }; - if reply.client_swapped { + if reply.category == RECORD_START_OF_DATA { + worker.report_ready(Ok(())); continue; } - match reply.category { - RECORD_START_OF_DATA => continue, - RECORD_FROM_SERVER => {} - _ => continue, + if reply.client_swapped || reply.category != RECORD_FROM_SERVER { + continue; } + worker.refresh_keysyms_on_mapping_change(data_conn); let mut data: &[u8] = &reply.data; while !data.is_empty() { - let consumed = decode_one(data, &keysyms, &callback, &stop); + let (consumed, event) = decode_one(data, &worker.keysyms); + if let Some(event) = event { + worker.held.track(&event); + dispatch(&worker.callback, &worker.stop, event); + } if consumed == 0 || consumed > data.len() { break; } data = &data[consumed..]; } } + if !worker.stop.swap(true, Ordering::AcqRel) { + for event in worker.held.take_releases(&worker.keysyms) { + dispatch(&worker.callback, &worker.stop, event); + } + } + worker.report_ready(Err("RecordEnableContext ended before data".to_string())); } -fn decode_one( - data: &[u8], - keysyms: &KeysymCache, - callback: &EventTsfn, - stop: &Arc, -) -> usize { +fn decode_one(data: &[u8], keysyms: &KeysymCache) -> (usize, Option) { if data.is_empty() { - return 0; + return (0, None); } - let type_ = data[0]; - match type_ { - KEY_PRESS | KEY_RELEASE | BUTTON_PRESS | BUTTON_RELEASE | MOTION_NOTIFY => { + match data[0] { + KEY_PRESS | KEY_RELEASE | BUTTON_PRESS | BUTTON_RELEASE => { if data.len() < std::mem::size_of::() { - return 0; + return (0, None); } let evt: XKeyButtonProto = unsafe { std::ptr::read_unaligned(data.as_ptr() as *const XKeyButtonProto) }; - handle_event(&evt, keysyms, callback, stop); - 32 + (32, decode_event(&evt, keysyms)) } 0 => { if data.len() < 8 { - return 0; + return (0, None); } let (length, _) = match u32::try_parse(&data[4..]) { Ok(v) => v, - Err(_) => return 0, + Err(_) => return (0, None), }; - 32 + (length as usize) * 4 + (32 + (length as usize) * 4, None) } - _ => 32, + _ => (32, None), } } -fn handle_event( - evt: &XKeyButtonProto, +fn key_event( + kind: EventKind, + x11_keycode: u8, keysyms: &KeysymCache, - callback: &EventTsfn, - stop: &Arc, -) { + mods: Modifiers, +) -> DecodedEvent { + let keysym = keysyms.lookup(x11_keycode); + let mut event = DecodedEvent::new(kind, mods); + event.keycode = keysym; + event.x11_keycode = x11_keycode; + event.key_name = keymap::keysym_to_name(keysym) + .unwrap_or_default() + .to_string(); + event +} + +fn decode_event(evt: &XKeyButtonProto, keysyms: &KeysymCache) -> Option { let mods = modifiers::from_state(u32::from(evt.state)); match evt.type_ { - KEY_PRESS | KEY_RELEASE => { - let _lookup = x11::xkb_lookup_for_base(); - let keysym = keysyms.lookup(evt.detail); + KEY_PRESS => Some(key_event(EventKind::KeyDown, evt.detail, keysyms, mods)), + KEY_RELEASE => Some(key_event(EventKind::KeyUp, evt.detail, keysyms, mods)), + BUTTON_PRESS | BUTTON_RELEASE => { + let button = mouse::browser_button(u32::from(evt.detail))?; let mut event = DecodedEvent::new( - if evt.type_ == KEY_PRESS { - EventKind::KeyDown + if evt.type_ == BUTTON_PRESS { + EventKind::MouseDown } else { - EventKind::KeyUp + EventKind::MouseUp }, mods, ); - event.keycode = keysym; - event.key_name = match keymap::keysym_to_name(keysym) { - Some(name) => name.to_string(), - None => keymap::fallback_name(keysym), - }; - dispatch(callback, stop, event); - } - BUTTON_PRESS | BUTTON_RELEASE => { - let cls = mouse::classify(u32::from(evt.detail)); - match cls { - MouseClassification::Button(b) => { - let mut event = DecodedEvent::new( - if evt.type_ == BUTTON_PRESS { - EventKind::MouseDown - } else { - EventKind::MouseUp - }, - mods, - ); - event.button = b; - event.x = i32::from(evt.root_x); - event.y = i32::from(evt.root_y); - event.has_xy = true; - dispatch(callback, stop, event); - } - MouseClassification::Wheel(dir) => { - if evt.type_ == BUTTON_PRESS { - let mut event = DecodedEvent::new(EventKind::Wheel, mods); - event.delta_x = dir.delta_x(); - event.delta_y = dir.delta_y(); - event.x = i32::from(evt.root_x); - event.y = i32::from(evt.root_y); - event.has_xy = true; - dispatch(callback, stop, event); - } - } - MouseClassification::Ignored => {} - } - } - MOTION_NOTIFY => { - let mut event = DecodedEvent::new(EventKind::MouseMove, mods); + event.button = button; event.x = i32::from(evt.root_x); event.y = i32::from(evt.root_y); event.has_xy = true; - dispatch(callback, stop, event); + Some(event) } - _ => {} + _ => None, } } @@ -526,6 +646,13 @@ fn dispatch(callback: &EventTsfn, stop: &Arc, event: DecodedEvent) { } } +fn abandon(active: Active) { + active.stop.store(true, Ordering::Release); + let _ = active.ctrl_conn.record_disable_context(active.record_ctx); + let _ = active.ctrl_conn.record_free_context(active.record_ctx); + let _ = active.ctrl_conn.flush(); +} + fn tear_down(mut active: Active) { active.stop.store(true, Ordering::Release); @@ -550,11 +677,6 @@ pub fn is_available() -> bool { x11rb::connect(None).is_ok() } -#[allow(dead_code)] -pub(crate) fn detected_display_server() -> DisplayServer { - detect_display_server() -} - fn generic_error(reason: impl Into) -> napi::Error { napi::Error::new(Status::GenericFailure, reason.into()) } @@ -578,26 +700,78 @@ const _ASSERT_PROTO_LAYOUT: fn() = || { mod tests { use super::*; + fn cache_with(x11_keycode: u8, keysym: u32) -> KeysymCache { + let mut syms = vec![0; 248]; + syms[usize::from(x11_keycode - 8)] = keysym; + KeysymCache { + min_keycode: 8, + syms, + } + } + + fn wire_event(type_: u8, detail: u8, state: u16) -> [u8; 32] { + let mut data = [0u8; 32]; + data[0] = type_; + data[1] = detail; + data[28..30].copy_from_slice(&state.to_ne_bytes()); + data + } + #[test] - fn decoded_keydown_has_keysym_and_name() { - let mut event = DecodedEvent::new(EventKind::KeyDown, modifiers::from_state(0)); - event.keycode = 0x0061; - event.key_name = keymap::keysym_to_name(0x0061).unwrap().to_string(); + fn decoded_keydown_has_keysym_raw_keycode_and_name() { + let (consumed, event) = decode_one(&wire_event(KEY_PRESS, 38, 0), &cache_with(38, 0x0061)); + let event = event.expect("key event"); + assert_eq!(consumed, 32); assert!(matches!(event.kind, EventKind::KeyDown)); assert_eq!(event.keycode, 0x0061); + assert_eq!(event.x11_keycode, 38); assert_eq!(event.key_name, "A"); assert!(!event.has_xy); } #[test] - fn decoded_wheel_uses_120_step_deltas() { - let dir = mouse::WheelDirection::Down; - let mut event = DecodedEvent::new(EventKind::Wheel, modifiers::from_state(0)); - event.delta_x = dir.delta_x(); - event.delta_y = dir.delta_y(); - event.has_xy = true; - assert_eq!(event.delta_x, 0); - assert_eq!(event.delta_y, 120); + fn unknown_keysym_has_no_layout_key_name() { + let (_, event) = decode_one(&wire_event(KEY_PRESS, 24, 0x4), &cache_with(24, 0x6ca)); + let event = event.expect("key event"); + assert_eq!(event.keycode, 0x6ca); + assert_eq!(event.x11_keycode, 24); + assert_eq!(event.key_name, ""); + assert!(event.mods.ctrl); + } + + #[test] + fn wheel_buttons_are_not_decoded() { + let cache = cache_with(38, 0x0061); + assert!( + decode_one(&wire_event(BUTTON_PRESS, 4, 0), &cache) + .1 + .is_none() + ); + let (_, event) = decode_one(&wire_event(BUTTON_PRESS, 8, 0), &cache); + assert_eq!(event.expect("button event").button, 3); + } + + #[test] + fn held_inputs_release_every_pressed_key_and_button_once() { + let cache = cache_with(38, 0x0061); + let mut held = HeldInputs::default(); + for data in [ + wire_event(KEY_PRESS, 38, 0), + wire_event(KEY_PRESS, 37, 0), + wire_event(KEY_RELEASE, 37, 0), + wire_event(BUTTON_PRESS, 9, 0), + ] { + held.track(&decode_one(&data, &cache).1.expect("event")); + } + let releases = held.take_releases(&cache); + assert_eq!(releases.len(), 2); + assert!(matches!(releases[0].kind, EventKind::KeyUp)); + assert_eq!(releases[0].x11_keycode, 38); + assert_eq!(releases[0].key_name, "A"); + assert!(!releases[0].mods.ctrl); + assert!(matches!(releases[1].kind, EventKind::MouseUp)); + assert_eq!(releases[1].button, 4); + assert!(held.take_releases(&cache).is_empty()); } #[test] @@ -614,25 +788,10 @@ mod tests { #[test] fn event_kind_to_string_matches_js_contract() { - let cases: &[(EventKind, &str)] = &[ - (EventKind::KeyDown, "keydown"), - (EventKind::KeyUp, "keyup"), - (EventKind::MouseDown, "mousedown"), - (EventKind::MouseUp, "mouseup"), - (EventKind::MouseMove, "mousemove"), - (EventKind::Wheel, "wheel"), - ]; - for (kind, expected) in cases { - let label = match kind { - EventKind::KeyDown => "keydown", - EventKind::KeyUp => "keyup", - EventKind::MouseDown => "mousedown", - EventKind::MouseUp => "mouseup", - EventKind::MouseMove => "mousemove", - EventKind::Wheel => "wheel", - }; - assert_eq!(label, *expected); - } + assert_eq!(EventKind::KeyDown.as_str(), "keydown"); + assert_eq!(EventKind::KeyUp.as_str(), "keyup"); + assert_eq!(EventKind::MouseDown.as_str(), "mousedown"); + assert_eq!(EventKind::MouseUp.as_str(), "mouseup"); } #[test] diff --git a/fluxer_desktop/native/linux-input-hook/src/keymap.rs b/fluxer_desktop/native/linux-input-hook/src/keymap.rs index fd351c305..0bdb21ac9 100644 --- a/fluxer_desktop/native/linux-input-hook/src/keymap.rs +++ b/fluxer_desktop/native/linux-input-hook/src/keymap.rs @@ -157,10 +157,6 @@ pub fn keysym_to_name(keysym: u32) -> Option<&'static str> { .map(|(_, name)| *name) } -pub fn fallback_name(keysym: u32) -> String { - format!("Key{keysym}") -} - #[cfg(test)] mod tests { use super::*; @@ -200,9 +196,9 @@ mod tests { } #[test] - fn unknown_keysym_falls_back_to_key_n() { + fn unknown_keysym_has_no_name() { assert!(keysym_to_name(0x12345).is_none()); - assert_eq!(fallback_name(0x12345), "Key74565"); + assert!(keysym_to_name(0x6ca).is_none()); } #[test] diff --git a/fluxer_desktop/native/linux-input-hook/src/lib.rs b/fluxer_desktop/native/linux-input-hook/src/lib.rs index 401f4cdd2..09473f4cc 100644 --- a/fluxer_desktop/native/linux-input-hook/src/lib.rs +++ b/fluxer_desktop/native/linux-input-hook/src/lib.rs @@ -6,7 +6,6 @@ pub mod env; pub mod keymap; pub mod modifiers; pub mod mouse; -pub mod x11; #[cfg(target_os = "linux")] mod hook; diff --git a/fluxer_desktop/native/linux-input-hook/src/mouse.rs b/fluxer_desktop/native/linux-input-hook/src/mouse.rs index 87e81ae43..50a3d6aa1 100644 --- a/fluxer_desktop/native/linux-input-hook/src/mouse.rs +++ b/fluxer_desktop/native/linux-input-hook/src/mouse.rs @@ -1,51 +1,13 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum WheelDirection { - Up, - Down, - Left, - Right, -} - -impl WheelDirection { - pub fn delta_x(self) -> i32 { - match self { - Self::Left => -120, - Self::Right => 120, - _ => 0, - } - } - - pub fn delta_y(self) -> i32 { - match self { - Self::Up => -120, - Self::Down => 120, - _ => 0, - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MouseClassification { - Button(u8), - Wheel(WheelDirection), - - Ignored, -} - -pub fn classify(x11_button: u32) -> MouseClassification { +pub fn browser_button(x11_button: u32) -> Option { match x11_button { - 1 => MouseClassification::Button(0), - 2 => MouseClassification::Button(1), - 3 => MouseClassification::Button(2), - 4 => MouseClassification::Wheel(WheelDirection::Up), - 5 => MouseClassification::Wheel(WheelDirection::Down), - 6 => MouseClassification::Wheel(WheelDirection::Left), - 7 => MouseClassification::Wheel(WheelDirection::Right), - 8 => MouseClassification::Button(3), - 9 => MouseClassification::Button(4), - _ => MouseClassification::Ignored, + 1 => Some(0), + 2 => Some(1), + 3 => Some(2), + 8 => Some(3), + 9 => Some(4), + _ => None, } } @@ -55,36 +17,28 @@ mod tests { #[test] fn primary_buttons_map_to_browser_indices() { - assert_eq!(classify(1), MouseClassification::Button(0)); - assert_eq!(classify(2), MouseClassification::Button(1)); - assert_eq!(classify(3), MouseClassification::Button(2)); - } - - #[test] - fn vertical_wheel_delta_y_120_step() { - assert_eq!(classify(4), MouseClassification::Wheel(WheelDirection::Up)); - assert_eq!(WheelDirection::Up.delta_y(), -120); - assert_eq!(WheelDirection::Down.delta_y(), 120); - assert_eq!(WheelDirection::Up.delta_x(), 0); - } - - #[test] - fn horizontal_wheel_delta_x_120_step() { - assert_eq!(WheelDirection::Left.delta_x(), -120); - assert_eq!(WheelDirection::Right.delta_x(), 120); - assert_eq!(WheelDirection::Left.delta_y(), 0); + assert_eq!(browser_button(1), Some(0)); + assert_eq!(browser_button(2), Some(1)); + assert_eq!(browser_button(3), Some(2)); } #[test] fn back_forward_buttons_map_to_3_and_4() { - assert_eq!(classify(8), MouseClassification::Button(3)); - assert_eq!(classify(9), MouseClassification::Button(4)); + assert_eq!(browser_button(8), Some(3)); + assert_eq!(browser_button(9), Some(4)); + } + + #[test] + fn wheel_buttons_are_ignored() { + for button in 4..=7 { + assert_eq!(browser_button(button), None); + } } #[test] fn unknown_buttons_are_ignored() { - assert_eq!(classify(0), MouseClassification::Ignored); - assert_eq!(classify(15), MouseClassification::Ignored); - assert_eq!(classify(255), MouseClassification::Ignored); + assert_eq!(browser_button(0), None); + assert_eq!(browser_button(15), None); + assert_eq!(browser_button(255), None); } } diff --git a/fluxer_desktop/native/linux-input-hook/src/x11.rs b/fluxer_desktop/native/linux-input-hook/src/x11.rs deleted file mode 100644 index 36a7c2245..000000000 --- a/fluxer_desktop/native/linux-input-hook/src/x11.rs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -#[derive(Debug, Clone, Copy)] -pub struct XkbLookup { - pub group: u32, - pub level: u32, -} - -pub fn xkb_lookup_for_base() -> XkbLookup { - XkbLookup { group: 0, level: 0 } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn xkb_lookup_for_base_pins_group_level_to_zero() { - let lookup = xkb_lookup_for_base(); - assert_eq!(lookup.group, 0); - assert_eq!(lookup.level, 0); - } -} diff --git a/fluxer_desktop/native/linux-portals/Cargo.lock b/fluxer_desktop/native/linux-portals/Cargo.lock index 441fc3b21..a177f8e05 100644 --- a/fluxer_desktop/native/linux-portals/Cargo.lock +++ b/fluxer_desktop/native/linux-portals/Cargo.lock @@ -279,6 +279,8 @@ checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" name = "fluxer_linux_portals" version = "0.0.0" dependencies = [ + "async-channel", + "async-executor", "async-io", "futures-lite", "napi", diff --git a/fluxer_desktop/native/linux-portals/Cargo.toml b/fluxer_desktop/native/linux-portals/Cargo.toml index dc224c4e7..50b35d011 100644 --- a/fluxer_desktop/native/linux-portals/Cargo.toml +++ b/fluxer_desktop/native/linux-portals/Cargo.toml @@ -16,6 +16,8 @@ napi = {version = "3.12.6", default-features = false, features = ["dyn-symbols", napi-derive = "3.6.7" [target.'cfg(target_os = "linux")'.dependencies] +async-channel = "2.5.0" +async-executor = "1.14.0" async-io = "2.6.0" futures-lite = "2.6.1" x11rb = "0.14.0" diff --git a/fluxer_desktop/native/linux-portals/index.d.ts b/fluxer_desktop/native/linux-portals/index.d.ts index 54cfcb2dd..df35965be 100644 --- a/fluxer_desktop/native/linux-portals/index.d.ts +++ b/fluxer_desktop/native/linux-portals/index.d.ts @@ -62,42 +62,66 @@ export interface BackgroundResult { export declare function requestBackground(options: BackgroundOptions): Promise; -export interface GlobalShortcutEntry { +export interface GlobalShortcutDefinition { id: string; description: string; - preferredTrigger?: string; + preferredTrigger?: string | null; } -export interface BoundGlobalShortcut { +export interface GlobalShortcutBinding { id: string; - description?: string; - triggerDescription?: string; + description: string | null; + triggerDescription: string | null; } -export type GlobalShortcutPortalAction = 'listed' | 'bound' | 'cancelled'; - -export interface GlobalShortcutsConfigureResult { - action: GlobalShortcutPortalAction; - shortcuts: Array; -} - -export type GlobalShortcutPortalEvent = +export type GlobalShortcutsPortalEvent = | {type: 'activated'; id: string} | {type: 'deactivated'; id: string} - | {type: 'shortcuts-changed'; shortcuts: Array} - | {type: 'closed'}; + | {type: 'shortcuts-changed'; shortcuts: Array} + | {type: 'session-lost'; reason: 'closed' | 'portal-restarted' | 'bus-error'} + | {type: 'portal-available'}; + +export interface GlobalShortcutsPortalOpenResult { + version: number; + appIdSource: 'sandbox' | 'registered' | 'unregistered'; + uniqueName: string; + listed: Array; +} + +export type GlobalShortcutsBindOutcome = + | {outcome: 'bound'; shortcuts: Array} + | {outcome: 'cancelled'} + | {outcome: 'denied'} + | {outcome: 'failed'; code: number}; + +export type GlobalShortcutsPortalDesktop = 'kde' | 'gnome' | 'hyprland' | 'other'; + +export interface GlobalShortcutsPortalOptions { + portalAppId: string | null; + sandboxed: boolean; + sessionToken?: string; + desktop?: GlobalShortcutsPortalDesktop; +} export declare class GlobalShortcutsPortal { - constructor(onEvent: (event: GlobalShortcutPortalEvent) => void, appId?: string | null); + constructor(onEvent: (event: GlobalShortcutsPortalEvent) => void, options: GlobalShortcutsPortalOptions); - configure(entries: ReadonlyArray): Promise; + open(): Promise; + + bind(shortcuts: ReadonlyArray, parentWindow: string): Promise; + + configure(parentWindow: string): Promise; close(): void; } -export declare function isAvailable(): boolean; +export type SessionStateMonitorEvent = {type: 'screen-locked'} | {type: 'screen-unlocked'}; -export declare function getPortalVersion(): number | null; +export declare class SessionStateMonitor { + constructor(onEvent: (event: SessionStateMonitorEvent) => void); + + close(): void; +} export declare function readColorScheme(): 'no-preference' | 'prefer-dark' | 'prefer-light'; diff --git a/fluxer_desktop/native/linux-portals/index.js b/fluxer_desktop/native/linux-portals/index.js index e5efcd21a..f3b621b4b 100644 --- a/fluxer_desktop/native/linux-portals/index.js +++ b/fluxer_desktop/native/linux-portals/index.js @@ -65,8 +65,7 @@ module.exports = { saveFile: binding ? binding.saveFile : null, requestBackground: binding ? binding.requestBackground : null, GlobalShortcutsPortal: binding ? binding.GlobalShortcutsPortal : null, - isAvailable: binding ? binding.isAvailable : () => false, - getPortalVersion: binding ? binding.getPortalVersion : () => null, + SessionStateMonitor: binding ? binding.SessionStateMonitor : null, readColorScheme: binding ? binding.readColorScheme : () => 'no-preference', readContrast: binding ? binding.readContrast : () => 'no-preference', readAccentColor: binding ? binding.readAccentColor : () => null, diff --git a/fluxer_desktop/native/linux-portals/src/global_shortcuts.rs b/fluxer_desktop/native/linux-portals/src/global_shortcuts.rs index 6876f9f2c..909135d81 100644 --- a/fluxer_desktop/native/linux-portals/src/global_shortcuts.rs +++ b/fluxer_desktop/native/linux-portals/src/global_shortcuts.rs @@ -1,646 +1,2052 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -#[cfg(target_os = "linux")] use std::{ - collections::{HashMap, HashSet}, - sync::{ - Arc, Mutex, - atomic::{AtomicBool, Ordering}, - mpsc, - }, - thread::{self, JoinHandle}, + collections::{BTreeMap, BTreeSet}, time::Duration, }; -#[cfg(target_os = "linux")] -use futures_lite::{FutureExt, StreamExt, future}; -#[cfg(target_os = "linux")] -use zbus::{ - MatchRule, MessageStream, Proxy, - message::Type as MessageType, - zvariant::{OwnedObjectPath, OwnedValue, Value}, -}; - -#[cfg(target_os = "linux")] -use crate::portal::{REQUEST_INTERFACE, mint_token, request_path}; - pub const PORTAL_DESTINATION: &str = "org.freedesktop.portal.Desktop"; pub const PORTAL_PATH: &str = "/org/freedesktop/portal/desktop"; pub const GLOBAL_SHORTCUTS_INTERFACE: &str = "org.freedesktop.portal.GlobalShortcuts"; pub const SESSION_INTERFACE: &str = "org.freedesktop.portal.Session"; +pub const SESSION_PATH_NAMESPACE: &str = "/org/freedesktop/portal/desktop/session"; +pub const REGISTRY_INTERFACE: &str = "org.freedesktop.host.portal.Registry"; +pub const PROPERTIES_INTERFACE: &str = "org.freedesktop.DBus.Properties"; +pub const BUS_NAME: &str = "org.freedesktop.DBus"; +pub const BUS_PATH: &str = "/org/freedesktop/DBus"; +pub const DEFAULT_SESSION_TOKEN: &str = "fluxer_global_shortcuts"; +pub const SETUP_TIMEOUT: Duration = Duration::from_secs(30); +pub const TEARDOWN_TIMEOUT: Duration = Duration::from_secs(2); +pub const REGISTER_RETRY_DELAYS: [Duration; 3] = [ + Duration::from_millis(250), + Duration::from_millis(750), + Duration::from_secs(2), +]; +pub const ESTABLISH_ATTEMPTS: usize = 3; -#[cfg(target_os = "linux")] -pub const REQUEST_TIMEOUT: Duration = Duration::from_secs(5 * 60); -#[cfg(target_os = "linux")] -pub const SIGNAL_POLL_INTERVAL: Duration = Duration::from_millis(200); -#[cfg(target_os = "linux")] -pub const SIGNAL_THREAD_START_TIMEOUT: Duration = Duration::from_secs(5); +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Desktop { + Kde, + Gnome, + Hyprland, + Other, +} -#[cfg(target_os = "linux")] -type ShortcutProperties = HashMap; -#[cfg(target_os = "linux")] -type ShortcutsChangedBody = (OwnedObjectPath, Vec<(String, ShortcutProperties)>); +impl Desktop { + pub fn parse(value: &str) -> Self { + match value { + "kde" => Self::Kde, + "gnome" => Self::Gnome, + "hyprland" => Self::Hyprland, + _ => Self::Other, + } + } + + pub fn backend_name(self) -> Option<&'static str> { + match self { + Self::Kde => Some("org.freedesktop.impl.portal.desktop.kde"), + Self::Gnome => Some("org.freedesktop.impl.portal.desktop.gnome"), + Self::Hyprland => Some("org.freedesktop.impl.portal.desktop.hyprland"), + Self::Other => None, + } + } +} + +pub fn is_valid_token(token: &str) -> bool { + !token.is_empty() + && token + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || byte == b'_') +} + +pub fn cgroup_is_snap(cgroups: &str) -> bool { + cgroups.lines().any(|line| { + let Some((hierarchy, rest)) = line.split_once(':') else { + return false; + }; + if hierarchy.parse::().is_err() { + return false; + } + let unit = match rest.split_once(':') { + Some(("" | "freezer" | "name=systemd", unit)) => unit, + _ => return false, + }; + unit.rsplit('/') + .next() + .is_some_and(|scope| scope.starts_with("snap.")) + }) +} #[derive(Debug, Clone, PartialEq, Eq)] -pub struct ShortcutEntry { +pub struct ShortcutDefinition { pub id: String, pub description: String, pub preferred_trigger: Option, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DefinitionError { + Empty, + EmptyId, + DuplicateId, + EmptyDescription, +} + +impl DefinitionError { + pub fn message(self) -> &'static str { + match self { + Self::Empty => "shortcuts must not be empty", + Self::EmptyId => "shortcut.id must not be empty", + Self::DuplicateId => "shortcut ids must be unique", + Self::EmptyDescription => "shortcut.description must not be empty", + } + } +} + +pub fn validate_definitions(shortcuts: &[ShortcutDefinition]) -> Result<(), DefinitionError> { + if shortcuts.is_empty() { + return Err(DefinitionError::Empty); + } + let mut ids = BTreeSet::new(); + for shortcut in shortcuts { + if shortcut.id.is_empty() { + return Err(DefinitionError::EmptyId); + } + if shortcut.description.trim().is_empty() { + return Err(DefinitionError::EmptyDescription); + } + if !ids.insert(shortcut.id.as_str()) { + return Err(DefinitionError::DuplicateId); + } + } + Ok(()) +} + #[derive(Debug, Clone, PartialEq, Eq)] -pub struct BoundShortcut { +pub struct ShortcutBinding { pub id: String, pub description: Option, pub trigger_description: Option, } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ConfigureResult { - pub action: String, - pub shortcuts: Vec, +impl ShortcutBinding { + pub fn new( + id: String, + description: Option, + trigger_description: Option, + ) -> Self { + Self { + id, + description: description.filter(|value| !value.is_empty()), + trigger_description: trigger_description.filter(|value| !value.is_empty()), + } + } +} + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct ShortcutTable { + entries: BTreeMap, +} + +impl ShortcutTable { + pub fn from_definitions(shortcuts: &[ShortcutDefinition]) -> Self { + Self { + entries: shortcuts + .iter() + .map(|shortcut| { + ( + shortcut.id.clone(), + ShortcutBinding::new( + shortcut.id.clone(), + Some(shortcut.description.clone()), + None, + ), + ) + }) + .collect(), + } + } + + pub fn from_listed(listed: &[ShortcutBinding]) -> Self { + Self { + entries: listed + .iter() + .map(|binding| (binding.id.clone(), binding.clone())) + .collect(), + } + } + + pub fn contains(&self, id: &str) -> bool { + self.entries.contains_key(id) + } + + pub fn known(&self, incoming: Vec) -> Vec { + incoming + .into_iter() + .filter(|binding| self.contains(&binding.id)) + .collect() + } + + pub fn merge(&mut self, incoming: Vec) -> Vec { + let mut changed = Vec::new(); + for binding in incoming { + let Some(entry) = self.entries.get_mut(&binding.id) else { + continue; + }; + if entry.trigger_description != binding.trigger_description { + changed.push(binding.id.clone()); + } + entry.trigger_description = binding.trigger_description; + if binding.description.is_some() { + entry.description = binding.description; + } + } + changed + } + + pub fn bindings(&self) -> Vec { + self.entries.values().cloned().collect() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AppIdSource { + Sandbox, + Registered, + Unregistered, +} + +impl AppIdSource { + pub fn as_str(self) -> &'static str { + match self { + Self::Sandbox => "sandbox", + Self::Registered => "registered", + Self::Unregistered => "unregistered", + } + } } #[derive(Debug, Clone, PartialEq, Eq)] -pub enum ShortcutEvent { +pub struct OpenResult { + pub version: u32, + pub app_id_source: AppIdSource, + pub unique_name: String, + pub listed: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BindOutcome { + Bound(Vec), + Cancelled, + Denied, + Failed(u32), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BindStep { + Done(BindOutcome), + List, +} + +pub fn bind_step(code: u32, shortcuts: Vec) -> BindStep { + if !shortcuts.is_empty() { + return BindStep::Done(BindOutcome::Bound(shortcuts)); + } + match code { + 0 => BindStep::List, + 1 => BindStep::Done(BindOutcome::Cancelled), + 2 => BindStep::Done(BindOutcome::Denied), + other => BindStep::Done(BindOutcome::Failed(other)), + } +} + +pub fn listed_outcome(listed: Vec) -> BindOutcome { + if listed.is_empty() { + BindOutcome::Denied + } else { + BindOutcome::Bound(listed) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SessionLostReason { + Closed, + PortalRestarted, + BusError, +} + +impl SessionLostReason { + pub fn as_str(self) -> &'static str { + match self { + Self::Closed => "closed", + Self::PortalRestarted => "portal-restarted", + Self::BusError => "bus-error", + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PortalEvent { Activated { id: String }, Deactivated { id: String }, - ShortcutsChanged { shortcuts: Vec }, + ShortcutsChanged { shortcuts: Vec }, + SessionLost { reason: SessionLostReason }, + PortalAvailable, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PortalError { + NoPortal, + Interface, + Version, + Response(u32), + AppIdRequired, + Timeout, + Bus, + InvalidReply, + NoSession, + SessionLost, Closed, } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum GlobalShortcutsError { - DbusError, - PortalTimeout, - InvalidReply, - SendFailed, - Cancelled, - ThreadStartFailed, - LockPoisoned, -} - -impl std::fmt::Display for GlobalShortcutsError { +impl std::fmt::Display for PortalError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let s = match self { - Self::DbusError => "DbusError", - Self::PortalTimeout => "PortalTimeout", - Self::InvalidReply => "InvalidReply", - Self::SendFailed => "SendFailed", - Self::Cancelled => "Cancelled", - Self::ThreadStartFailed => "ThreadStartFailed", - Self::LockPoisoned => "LockPoisoned", - }; - f.write_str(s) - } -} - -#[cfg(target_os = "linux")] -type ShortcutCallback = Arc; - -#[cfg(target_os = "linux")] -pub struct Subscription { - stop_flag: Arc, - thread: Mutex>>, -} - -#[cfg(target_os = "linux")] -impl Subscription { - pub fn configure( - entries: Vec, - callback: ShortcutCallback, - ) -> Result<(Self, ConfigureResult), GlobalShortcutsError> { - let stop_flag = Arc::new(AtomicBool::new(false)); - let stop_for_thread = stop_flag.clone(); - let (ready_tx, ready_rx) = - mpsc::sync_channel::>(1); - let thread = thread::Builder::new() - .name("fluxer-linux-portals-global-shortcuts".to_string()) - .spawn(move || { - let setup = future::block_on(async { - let conn = zbus::Connection::session() - .await - .map_err(|_| GlobalShortcutsError::DbusError)?; - let rule = MatchRule::builder().msg_type(MessageType::Signal).build(); - let stream = MessageStream::for_match_rule(rule, &conn, Some(64)) - .await - .map_err(|_| GlobalShortcutsError::DbusError)?; - let ready = configure_session(&conn, &entries).await?; - Ok::<_, GlobalShortcutsError>((conn, stream, ready)) - }); - let (conn, mut stream, ready) = match setup { - Ok(parts) => parts, - Err(err) => { - let _ = ready_tx.send(Err(err)); - return; - } - }; - let session_handle = ready.session_handle.clone(); - if ready_tx.send(Ok(ready)).is_err() { - let _ = future::block_on(close_session(&conn, &session_handle)); - return; - } - while !stop_for_thread.load(Ordering::Acquire) { - let timeout = async { - async_io::Timer::after(SIGNAL_POLL_INTERVAL).await; - None::> - }; - match future::block_on(stream.next().or(timeout)) { - Some(Ok(message)) => { - if let Some(event) = parse_signal(&message, &session_handle) { - let closed = matches!(event, ShortcutEvent::Closed); - callback(event); - if closed { - return; - } - } - } - Some(Err(_)) => break, - None => {} - } - } - let _ = future::block_on(close_session(&conn, &session_handle)); - }) - .map_err(|_| GlobalShortcutsError::ThreadStartFailed)?; - - match ready_rx.recv_timeout(SIGNAL_THREAD_START_TIMEOUT) { - Ok(Ok(ready)) => Ok(( - Self { - stop_flag, - thread: Mutex::new(Some(thread)), - }, - ready.result, - )), - Ok(Err(err)) => { - let _ = thread.join(); - Err(err) - } - Err(err) => { - stop_flag.store(true, Ordering::Release); - if matches!(err, mpsc::RecvTimeoutError::Disconnected) { - let _ = thread.join(); - } - Err(GlobalShortcutsError::ThreadStartFailed) - } - } - } - - pub fn close(&self) { - self.stop_flag.store(true, Ordering::Release); - if let Ok(mut thread) = self.thread.lock() - && let Some(t) = thread.take() - { - let _ = t.join(); + match self { + Self::NoPortal => f.write_str("unsupported:no-portal"), + Self::Interface => f.write_str("unsupported:interface"), + Self::Version => f.write_str("unsupported:version"), + Self::Response(code) => write!(f, "unsupported:{code}"), + Self::AppIdRequired => f.write_str("identity:app-id-required"), + Self::Timeout => f.write_str("error:timeout"), + Self::Bus => f.write_str("error:bus"), + Self::InvalidReply => f.write_str("error:invalid-reply"), + Self::NoSession => f.write_str("error:no-session"), + Self::SessionLost => f.write_str("error:session-lost"), + Self::Closed => f.write_str("error:closed"), } } } -#[cfg(target_os = "linux")] -impl Drop for Subscription { +const UNKNOWN_METHOD: &str = "org.freedesktop.DBus.Error.UnknownMethod"; +const UNKNOWN_INTERFACE: &str = "org.freedesktop.DBus.Error.UnknownInterface"; +const UNKNOWN_PROPERTY: &str = "org.freedesktop.DBus.Error.UnknownProperty"; +const INVALID_ARGS: &str = "org.freedesktop.DBus.Error.InvalidArgs"; +const SERVICE_UNKNOWN: &str = "org.freedesktop.DBus.Error.ServiceUnknown"; +const NAME_HAS_NO_OWNER: &str = "org.freedesktop.DBus.Error.NameHasNoOwner"; +const NO_REPLY: &str = "org.freedesktop.DBus.Error.NoReply"; +const TIMED_OUT: &str = "org.freedesktop.DBus.Error.TimedOut"; +const PORTAL_FAILED: &str = "org.freedesktop.portal.Error.Failed"; +const PORTAL_NOT_ALLOWED: &str = "org.freedesktop.portal.Error.NotAllowed"; +const ALREADY_ASSOCIATED: &str = "already associated"; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RegisterFailure { + Unregistered, + AlreadyAssociated, + Retry, + Fatal(PortalError), +} + +pub fn classify_register_error(name: &str, message: &str) -> RegisterFailure { + match name { + SERVICE_UNKNOWN | NAME_HAS_NO_OWNER | NO_REPLY | TIMED_OUT => { + RegisterFailure::Fatal(PortalError::NoPortal) + } + PORTAL_FAILED if message.contains(ALREADY_ASSOCIATED) => RegisterFailure::AlreadyAssociated, + PORTAL_FAILED => RegisterFailure::Retry, + _ => RegisterFailure::Unregistered, + } +} + +pub fn classify_interface_error(name: &str) -> PortalError { + match name { + UNKNOWN_METHOD | UNKNOWN_INTERFACE | UNKNOWN_PROPERTY | INVALID_ARGS => { + PortalError::Interface + } + SERVICE_UNKNOWN | NAME_HAS_NO_OWNER => PortalError::NoPortal, + NO_REPLY | TIMED_OUT => PortalError::Timeout, + _ => PortalError::Bus, + } +} + +pub fn classify_create_session_error(name: &str) -> PortalError { + match name { + PORTAL_NOT_ALLOWED => PortalError::AppIdRequired, + other => classify_interface_error(other), + } +} + +pub fn classify_configure_error(name: &str) -> PortalError { + match name { + UNKNOWN_METHOD | PORTAL_FAILED => PortalError::Version, + other => classify_interface_error(other), + } +} + +pub fn classify_owner_error(name: &str) -> PortalError { + match name { + NAME_HAS_NO_OWNER => PortalError::NoPortal, + other => classify_interface_error(other), + } +} + +pub fn portal_owner_lost(stored: &str, old_owner: &str, new_owner: &str) -> bool { + old_owner == stored || (!new_owner.is_empty() && new_owner != stored) +} + +pub fn portal_appeared(old_owner: &str, new_owner: &str) -> bool { + old_owner.is_empty() && !new_owner.is_empty() +} + +pub fn backend_owner_lost(old_owner: &str) -> bool { + !old_owner.is_empty() +} + +#[derive(Debug, Default)] +pub struct ActiveShortcuts { + ids: BTreeSet, +} + +impl ActiveShortcuts { + pub fn activate(&mut self, id: &str) { + self.ids.insert(id.to_string()); + } + + pub fn deactivate(&mut self, id: &str) -> bool { + self.ids.remove(id) + } + + pub fn release_all(&mut self) -> Vec { + std::mem::take(&mut self.ids).into_iter().collect() + } +} + +type Reply = Box) + Send>; + +pub struct Responder { + send: Option>, +} + +impl Responder { + pub fn new(send: impl FnOnce(Result) + Send + 'static) -> Self { + Self { + send: Some(Box::new(send)), + } + } + + pub fn respond(mut self, result: Result) { + if let Some(send) = self.send.take() { + send(result); + } + } +} + +impl Drop for Responder { fn drop(&mut self) { - self.close(); - } -} - -#[cfg(target_os = "linux")] -struct ConfigureReady { - session_handle: String, - result: ConfigureResult, -} - -#[cfg(target_os = "linux")] -struct PortalResponse { - code: u32, - results: HashMap, -} - -#[cfg(target_os = "linux")] -async fn configure_session( - conn: &zbus::Connection, - entries: &[ShortcutEntry], -) -> Result { - let session_handle = create_session(conn).await?; - let persisted = list_shortcuts(conn, &session_handle) - .await - .unwrap_or_default(); - if shortcut_ids_match(entries, &persisted) { - return Ok(ConfigureReady { - session_handle, - result: ConfigureResult { - action: "listed".to_string(), - shortcuts: persisted, - }, - }); - } - let bound = bind_shortcuts(conn, &session_handle, entries).await?; - Ok(ConfigureReady { - session_handle, - result: bound, - }) -} - -#[cfg(target_os = "linux")] -fn shortcut_ids_match(entries: &[ShortcutEntry], persisted: &[BoundShortcut]) -> bool { - if entries.is_empty() || persisted.is_empty() { - return false; - } - let requested: HashSet<&str> = entries.iter().map(|entry| entry.id.as_str()).collect(); - let existing: HashSet<&str> = persisted - .iter() - .map(|shortcut| shortcut.id.as_str()) - .collect(); - requested == existing -} - -#[cfg(target_os = "linux")] -async fn create_session(conn: &zbus::Connection) -> Result { - let handle_token = mint_token("fluxer_gs_create"); - let session_handle_token = mint_token("fluxer_gs_session"); - let mut stream = request_stream(conn, &handle_token).await?; - let proxy = global_shortcuts_proxy(conn).await?; - let mut options: HashMap<&str, Value<'_>> = HashMap::new(); - options.insert("handle_token", Value::new(handle_token.as_str())); - options.insert( - "session_handle_token", - Value::new(session_handle_token.as_str()), - ); - let _reply_path: OwnedObjectPath = proxy - .call("CreateSession", &(options,)) - .await - .map_err(|_| GlobalShortcutsError::SendFailed)?; - let response = wait_for_response(&mut stream).await?; - if response.code != 0 { - return Err(GlobalShortcutsError::Cancelled); - } - response - .results - .get("session_handle") - .and_then(|value| string_or_object_path(crate::kwin::value_of_owned(value))) - .ok_or(GlobalShortcutsError::InvalidReply) -} - -#[cfg(target_os = "linux")] -async fn list_shortcuts( - conn: &zbus::Connection, - session_handle: &str, -) -> Result, GlobalShortcutsError> { - let session_path = owned_path(session_handle)?; - let handle_token = mint_token("fluxer_gs_list"); - let mut stream = request_stream(conn, &handle_token).await?; - let proxy = global_shortcuts_proxy(conn).await?; - let mut options: HashMap<&str, Value<'_>> = HashMap::new(); - options.insert("handle_token", Value::new(handle_token.as_str())); - let _reply_path: OwnedObjectPath = proxy - .call("ListShortcuts", &(&session_path, options)) - .await - .map_err(|_| GlobalShortcutsError::SendFailed)?; - let response = wait_for_response(&mut stream).await?; - if response.code != 0 { - return Err(GlobalShortcutsError::Cancelled); - } - Ok(shortcuts_from_results(&response.results)) -} - -#[cfg(target_os = "linux")] -async fn bind_shortcuts( - conn: &zbus::Connection, - session_handle: &str, - entries: &[ShortcutEntry], -) -> Result { - let session_path = owned_path(session_handle)?; - let handle_token = mint_token("fluxer_gs_bind"); - let mut stream = request_stream(conn, &handle_token).await?; - let proxy = global_shortcuts_proxy(conn).await?; - let shortcuts = serialize_shortcuts(entries); - let mut options: HashMap<&str, Value<'_>> = HashMap::new(); - options.insert("handle_token", Value::new(handle_token.as_str())); - let _reply_path: OwnedObjectPath = proxy - .call("BindShortcuts", &(&session_path, shortcuts, "", options)) - .await - .map_err(|_| GlobalShortcutsError::SendFailed)?; - let response = wait_for_response(&mut stream).await?; - if response.code != 0 { - return Ok(ConfigureResult { - action: "cancelled".to_string(), - shortcuts: Vec::new(), - }); - } - Ok(ConfigureResult { - action: "bound".to_string(), - shortcuts: shortcuts_from_results(&response.results), - }) -} - -#[cfg(target_os = "linux")] -async fn global_shortcuts_proxy( - conn: &zbus::Connection, -) -> Result, GlobalShortcutsError> { - Proxy::new( - conn, - PORTAL_DESTINATION, - PORTAL_PATH, - GLOBAL_SHORTCUTS_INTERFACE, - ) - .await - .map_err(|_| GlobalShortcutsError::DbusError) -} - -async fn request_stream( - conn: &zbus::Connection, - handle_token: &str, -) -> Result { - let unique_owned = conn - .unique_name() - .ok_or(GlobalShortcutsError::DbusError)? - .to_owned(); - let unique_name = unique_owned.as_str().to_string(); - let expected_path = request_path(&unique_name, handle_token); - let rule = MatchRule::builder() - .msg_type(MessageType::Signal) - .interface(REQUEST_INTERFACE) - .map_err(|_| GlobalShortcutsError::DbusError)? - .member("Response") - .map_err(|_| GlobalShortcutsError::DbusError)? - .path(expected_path.clone()) - .map_err(|_| GlobalShortcutsError::DbusError)? - .build(); - MessageStream::for_match_rule(rule, conn, Some(8)) - .await - .map_err(|_| GlobalShortcutsError::DbusError) -} - -async fn wait_for_response( - stream: &mut MessageStream, -) -> Result { - loop { - let timeout = async { - async_io::Timer::after(REQUEST_TIMEOUT).await; - None::> - }; - match stream.next().or(timeout).await { - Some(Ok(message)) => { - if let Some(parsed) = parse_request_response(&message) { - return Ok(parsed); - } - } - Some(Err(_)) => return Err(GlobalShortcutsError::DbusError), - None => return Err(GlobalShortcutsError::PortalTimeout), + if let Some(send) = self.send.take() { + send(Err(PortalError::Closed)); } } } -#[cfg(target_os = "linux")] -fn parse_request_response(message: &zbus::Message) -> Option { - let body = message.body(); - let (code, results): (u32, HashMap) = body.deserialize().ok()?; - Some(PortalResponse { code, results }) +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PortalOptions { + pub portal_app_id: Option, + pub sandboxed: bool, + pub session_token: String, + pub desktop: Desktop, } #[cfg(target_os = "linux")] -fn serialize_shortcuts(entries: &[ShortcutEntry]) -> Vec<(&str, HashMap<&str, Value<'_>>)> { - entries - .iter() - .map(|entry| { - let mut options: HashMap<&str, Value<'_>> = HashMap::new(); - options.insert("description", Value::new(entry.description.as_str())); - if let Some(trigger) = entry.preferred_trigger.as_deref() { - options.insert("preferred_trigger", Value::new(trigger)); - } - (entry.id.as_str(), options) - }) - .collect() -} +pub use worker::GlobalShortcutsClient; #[cfg(target_os = "linux")] -fn shortcuts_from_results(results: &HashMap) -> Vec { - results - .get("shortcuts") - .and_then(|value| shortcuts_from_value(crate::kwin::value_of_owned(value))) - .unwrap_or_default() -} +mod worker { + use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc, thread}; -#[cfg(target_os = "linux")] -fn shortcuts_from_value(value: &Value<'_>) -> Option> { - let inner = unbox_value(value); - let Value::Array(array) = inner else { - return None; + use async_channel::{Receiver, Sender}; + use async_executor::{LocalExecutor, Task}; + use async_io::Timer; + use futures_lite::{FutureExt, StreamExt, future}; + use zbus::{ + Connection, MatchRule, Message, MessageStream, + message::Type as MessageType, + names::OwnedUniqueName, + zvariant::{DynamicType, OwnedObjectPath, OwnedValue, Value}, }; - let mut shortcuts = Vec::new(); - for value in array.inner() { - if let Some(shortcut) = bound_shortcut_from_value(value) { - shortcuts.push(shortcut); + + use super::*; + use crate::portal::{REQUEST_INTERFACE, mint_token, request_path}; + + type EventSink = Arc; + type Results = HashMap; + type SignalHandler = fn(&RefCell, u64, &Message); + + enum Command { + Open(Responder), + Bind { + shortcuts: Vec, + parent_window: String, + reply: Responder, + }, + Configure { + parent_window: String, + reply: Responder<()>, + }, + } + + pub struct GlobalShortcutsClient { + commands: Sender, + shutdown: Sender<()>, + } + + impl GlobalShortcutsClient { + pub fn spawn( + options: PortalOptions, + sink: Arc, + ) -> std::io::Result { + let (commands, commands_rx) = async_channel::unbounded(); + let (shutdown, shutdown_rx) = async_channel::bounded(1); + thread::Builder::new() + .name("fluxer-linux-portals-global-shortcuts".to_string()) + .spawn(move || run(options, sink, commands_rx, shutdown_rx))?; + Ok(Self { commands, shutdown }) + } + + pub fn open(&self, reply: Responder) { + let _ = self.commands.try_send(Command::Open(reply)); + } + + pub fn bind( + &self, + shortcuts: Vec, + parent_window: String, + reply: Responder, + ) { + let _ = self.commands.try_send(Command::Bind { + shortcuts, + parent_window, + reply, + }); + } + + pub fn configure(&self, parent_window: String, reply: Responder<()>) { + let _ = self.commands.try_send(Command::Configure { + parent_window, + reply, + }); + } + + pub fn close(&self) { + self.commands.close(); + self.shutdown.close(); } } - Some(shortcuts) -} -#[cfg(target_os = "linux")] -fn bound_shortcut_from_value(value: &Value<'_>) -> Option { - let Value::Structure(structure) = unbox_value(value) else { - return None; - }; - let fields = structure.fields(); - if fields.len() < 2 { - return None; + impl Drop for GlobalShortcutsClient { + fn drop(&mut self) { + self.close(); + } } - let id = string_or_object_path(&fields[0])?; - let dict = match unbox_value(&fields[1]) { - Value::Dict(dict) => Some(dict), - _ => None, - }; - Some(BoundShortcut { - id, - description: dict.and_then(|d| dict_string(d, "description")), - trigger_description: dict.and_then(|d| dict_string(d, "trigger_description")), - }) -} -#[cfg(target_os = "linux")] -fn dict_string(dict: &zbus::zvariant::Dict<'_, '_>, key: &str) -> Option { - dict.iter().find_map(|(k, v)| { - if string_or_object_path(k).as_deref() == Some(key) { - string_or_object_path(unbox_value(v)) - } else { - None - } - }) -} + struct State { + options: PortalOptions, + sink: EventSink, + link: Option, + session: Option, + announced: bool, + active: ActiveShortcuts, + pending_request: Option, + next_generation: u64, + } -#[cfg(target_os = "linux")] -fn parse_signal(message: &zbus::Message, session_handle: &str) -> Option { - let header = message.header(); - let interface = header.interface()?.as_str(); - let member = header.member()?.as_str(); - match (interface, member) { - (GLOBAL_SHORTCUTS_INTERFACE, "Activated") => { - let (session, id, _timestamp, _options): ( - OwnedObjectPath, - String, - u64, - HashMap, - ) = message.body().deserialize().ok()?; - if session.as_str() == session_handle { - Some(ShortcutEvent::Activated { id }) - } else { - None + struct Link { + conn: Connection, + generation: u64, + info: Option, + failed: bool, + lost: Sender<()>, + lost_rx: Receiver<()>, + _watchers: Vec>, + } + + #[derive(Clone)] + struct LinkInfo { + owner: OwnedUniqueName, + version: u32, + app_id_source: AppIdSource, + } + + #[derive(Clone)] + struct LinkHandle { + conn: Connection, + generation: u64, + info: LinkInfo, + lost: Receiver<()>, + } + + struct SessionState { + handle: OwnedObjectPath, + bind_attempted: bool, + shortcuts: ShortcutTable, + closed: Sender<()>, + closed_rx: Receiver<()>, + } + + struct PortalResponse { + code: u32, + results: Results, + } + + struct PendingRequest { + token: String, + predicted: OwnedObjectPath, + stream: MessageStream, + } + + enum CallError { + Method { name: String, message: String }, + Timeout, + Bus, + } + + impl CallError { + fn classify(self, method: fn(&str) -> PortalError) -> PortalError { + match self { + Self::Method { name, .. } => method(&name), + Self::Timeout => PortalError::Timeout, + Self::Bus => PortalError::Bus, } } - (GLOBAL_SHORTCUTS_INTERFACE, "Deactivated") => { - let (session, id, _timestamp, _options): ( - OwnedObjectPath, - String, - u64, - HashMap, - ) = message.body().deserialize().ok()?; - if session.as_str() == session_handle { - Some(ShortcutEvent::Deactivated { id }) - } else { - None - } + } + + impl State { + fn emit(&self, event: PortalEvent) { + (self.sink)(event); } - (GLOBAL_SHORTCUTS_INTERFACE, "ShortcutsChanged") => { - let (session, shortcuts): ShortcutsChangedBody = message.body().deserialize().ok()?; - if session.as_str() != session_handle { - return None; - } - Some(ShortcutEvent::ShortcutsChanged { - shortcuts: shortcuts - .into_iter() - .map(|(id, properties)| bound_shortcut_from_parts(id, &properties)) - .collect(), + + fn current_link(&self) -> Option { + let link = self.link.as_ref()?; + Some(LinkHandle { + conn: link.conn.clone(), + generation: link.generation, + info: link.info.clone()?, + lost: link.lost_rx.clone(), }) } - (SESSION_INTERFACE, "Closed") => { - if header.path()?.as_str() == session_handle { - Some(ShortcutEvent::Closed) + + fn link_for(&self, generation: u64) -> Option<&Link> { + self.link + .as_ref() + .filter(|link| link.generation == generation) + } + + fn release_active(&mut self) { + for id in self.active.release_all() { + self.emit(PortalEvent::Deactivated { id }); + } + } + + fn drop_session(&mut self) -> Option { + let session = self.session.take()?; + session.closed.close(); + self.release_active(); + Some(session.handle) + } + + fn lose_session(&mut self, reason: SessionLostReason) { + self.drop_session(); + if std::mem::take(&mut self.announced) { + self.emit(PortalEvent::SessionLost { reason }); + } + } + + fn fail_link(&mut self, generation: u64) { + if let Some(link) = self + .link + .as_mut() + .filter(|link| link.generation == generation) + { + link.info = None; + link.failed = true; + } + } + + fn announce_portal(&mut self, generation: u64) { + let Some(link) = self + .link + .as_mut() + .filter(|link| link.generation == generation && link.failed) + else { + return; + }; + if self.session.is_some() || self.announced { + return; + } + link.failed = false; + self.emit(PortalEvent::PortalAvailable); + } + + fn lose_link(&mut self, generation: u64, reason: SessionLostReason) -> Option { + self.link_for(generation)?; + self.lose_session(reason); + self.pending_request = None; + let link = self.link.take()?; + link.lost.close(); + Some(link) + } + } + + fn run( + options: PortalOptions, + sink: EventSink, + commands: Receiver, + shutdown: Receiver<()>, + ) { + let executor = Rc::new(LocalExecutor::new()); + let state = Rc::new(RefCell::new(State { + options, + sink, + link: None, + session: None, + announced: false, + active: ActiveShortcuts::default(), + pending_request: None, + next_generation: 1, + })); + let operations = process_commands(executor.clone(), state.clone(), commands.clone()); + async_io::block_on(executor.run(operations.or(async { + let _ = shutdown.recv().await; + }))); + commands.close(); + while commands.try_recv().is_ok() {} + async_io::block_on(executor.run(teardown(state))); + } + + async fn process_commands( + executor: Rc>, + state: Rc>, + commands: Receiver, + ) { + while let Ok(command) = commands.recv().await { + match command { + Command::Open(reply) => reply.respond(open(&executor, &state).await), + Command::Configure { + parent_window, + reply, + } => reply.respond(configure(&state, &parent_window).await), + Command::Bind { + shortcuts, + parent_window, + reply, + } => reply.respond(bind(&state, &shortcuts, &parent_window).await), + } + } + } + + async fn teardown(state: Rc>) { + let (link, session, request) = { + let mut state = state.borrow_mut(); + let session = state.drop_session(); + let request = state.pending_request.take(); + (state.link.take(), session, request) + }; + let Some(link) = link else { + return; + }; + if let Some(request) = request { + let _ = close_object(&link.conn, &request, REQUEST_INTERFACE, TEARDOWN_TIMEOUT).await; + } + if let Some(session) = session { + let _ = close_object(&link.conn, &session, SESSION_INTERFACE, TEARDOWN_TIMEOUT).await; + } + link.lost.close(); + } + + async fn close_object( + conn: &Connection, + path: &OwnedObjectPath, + interface: &str, + timeout: Duration, + ) -> Result { + call( + conn, + PORTAL_DESTINATION, + path.as_str(), + interface, + "Close", + &(), + Some(timeout), + ) + .await + } + + async fn call( + conn: &Connection, + destination: &str, + path: &str, + interface: &str, + method: &str, + body: &B, + timeout: Option, + ) -> Result + where + B: zbus::export::serde::Serialize + DynamicType, + { + let request = async { + conn.call_method(Some(destination), path, Some(interface), method, body) + .await + .map_err(|err| match err { + zbus::Error::MethodError(name, message, _) => CallError::Method { + name: name.to_string(), + message: message.unwrap_or_default(), + }, + _ => CallError::Bus, + }) + }; + match timeout { + Some(limit) => { + request + .or(async { + Timer::after(limit).await; + Err(CallError::Timeout) + }) + .await + } + None => request.await, + } + } + + async fn open( + executor: &Rc>, + state: &Rc>, + ) -> Result { + let previous = { + let mut state = state.borrow_mut(); + state.announced = false; + state.drop_session(); + state.pending_request = None; + state.link.take() + }; + drop(previous); + let link = connect(executor, state).await?; + if let Err(err) = create_session(state, &link).await { + state.borrow_mut().fail_link(link.generation); + return Err(err); + } + let listed = match list_shortcuts(state, &link).await { + Ok(listed) => listed, + Err(err) => { + let session = { + let mut state = state.borrow_mut(); + state.fail_link(link.generation); + state.drop_session() + }; + if let Some(session) = session { + let _ = close_object(&link.conn, &session, SESSION_INTERFACE, TEARDOWN_TIMEOUT) + .await; + } + return Err(err); + } + }; + let mut state = state.borrow_mut(); + let session = state.session.as_mut().ok_or(PortalError::SessionLost)?; + session.shortcuts = ShortcutTable::from_listed(&listed); + state.announced = true; + Ok(OpenResult { + version: link.info.version, + app_id_source: link.info.app_id_source, + unique_name: link.info.owner.to_string(), + listed, + }) + } + + async fn connect( + executor: &Rc>, + state: &Rc>, + ) -> Result { + let conn = zbus::connection::Builder::session() + .map_err(|_| PortalError::Bus)? + .build() + .await + .map_err(|_| PortalError::Bus)?; + let (generation, options) = { + let mut state = state.borrow_mut(); + let generation = state.next_generation; + state.next_generation += 1; + (generation, state.options.clone()) + }; + let watchers = watch_signals(executor, state, &conn, generation, &options) + .await + .map_err(|_| PortalError::Bus)?; + let (lost, lost_rx) = async_channel::bounded(1); + state.borrow_mut().link = Some(Link { + conn: conn.clone(), + generation, + info: None, + failed: false, + lost, + lost_rx, + _watchers: watchers, + }); + let setup = establish(&conn, &options).await; + let mut state = state.borrow_mut(); + let Some(link) = state + .link + .as_mut() + .filter(|link| link.generation == generation) + else { + return Err(PortalError::SessionLost); + }; + match setup { + Ok(info) => link.info = Some(info), + Err(err) => { + link.failed = true; + return Err(err); + } + } + state.current_link().ok_or(PortalError::SessionLost) + } + + async fn establish( + conn: &Connection, + options: &PortalOptions, + ) -> Result { + let mut owner_changes = + MessageStream::for_match_rule(owner_rule(PORTAL_DESTINATION)?, conn, None) + .await + .map_err(|_| PortalError::Bus)?; + let mut registered = false; + for _ in 0..ESTABLISH_ATTEMPTS { + let app_id_source = register(conn, options, registered).await?; + registered = app_id_source == AppIdSource::Registered; + let version = read_version(conn).await?; + let owner = portal_owner(conn).await?; + if !portal_restarted(&mut owner_changes).await { + return Ok(LinkInfo { + owner, + version, + app_id_source, + }); + } + } + Err(PortalError::SessionLost) + } + + async fn portal_restarted(changes: &mut MessageStream) -> bool { + let mut restarted = false; + while let Some(Some(Ok(message))) = future::poll_once(changes.next()).await { + if let Some((_, old_owner, _)) = owner_change(&message) { + restarted |= !old_owner.is_empty(); + } + } + restarted + } + + fn running_as_snap() -> bool { + std::fs::read_to_string("/proc/self/cgroup").is_ok_and(|cgroups| cgroup_is_snap(&cgroups)) + } + + async fn register( + conn: &Connection, + options: &PortalOptions, + registered: bool, + ) -> Result { + if options.sandboxed { + return Ok(AppIdSource::Sandbox); + } + let Some(app_id) = options.portal_app_id.as_deref() else { + return Ok(AppIdSource::Unregistered); + }; + if running_as_snap() { + return Ok(AppIdSource::Unregistered); + } + let mut retries = REGISTER_RETRY_DELAYS.iter(); + loop { + let registry_options: HashMap<&str, Value<'_>> = HashMap::new(); + let result = call( + conn, + PORTAL_DESTINATION, + PORTAL_PATH, + REGISTRY_INTERFACE, + "Register", + &(app_id, registry_options), + Some(SETUP_TIMEOUT), + ) + .await; + let failure = match result { + Ok(_) => return Ok(AppIdSource::Registered), + Err(CallError::Timeout) => return Err(PortalError::NoPortal), + Err(CallError::Bus) => return Err(PortalError::Bus), + Err(CallError::Method { name, message }) => { + classify_register_error(&name, &message) + } + }; + match failure { + RegisterFailure::Unregistered => return Ok(AppIdSource::Unregistered), + RegisterFailure::AlreadyAssociated if registered => { + return Ok(AppIdSource::Registered); + } + RegisterFailure::AlreadyAssociated => return Ok(AppIdSource::Unregistered), + RegisterFailure::Fatal(err) => return Err(err), + RegisterFailure::Retry => match retries.next() { + Some(delay) => { + Timer::after(*delay).await; + } + None => return Ok(AppIdSource::Unregistered), + }, + } + } + } + + async fn read_version(conn: &Connection) -> Result { + let reply = call( + conn, + PORTAL_DESTINATION, + PORTAL_PATH, + PROPERTIES_INTERFACE, + "Get", + &(GLOBAL_SHORTCUTS_INTERFACE, "version"), + Some(SETUP_TIMEOUT), + ) + .await + .map_err(|err| err.classify(classify_interface_error))?; + let value: OwnedValue = reply + .body() + .deserialize() + .map_err(|_| PortalError::Interface)?; + crate::kwin::integer_from_variant(crate::kwin::value_of_owned(&value)) + .ok_or(PortalError::Interface) + } + + async fn portal_owner(conn: &Connection) -> Result { + let reply = call( + conn, + BUS_NAME, + BUS_PATH, + BUS_NAME, + "GetNameOwner", + &(PORTAL_DESTINATION,), + Some(SETUP_TIMEOUT), + ) + .await + .map_err(|err| err.classify(classify_owner_error))?; + reply + .body() + .deserialize::() + .map_err(|_| PortalError::InvalidReply) + } + + async fn watch_signals( + executor: &Rc>, + state: &Rc>, + conn: &Connection, + generation: u64, + options: &PortalOptions, + ) -> Result>, PortalError> { + let mut watches: Vec<(MatchRule<'static>, SignalHandler)> = vec![ + (global_shortcuts_rule()?, on_global_shortcuts_signal), + (session_closed_rule()?, on_session_closed), + (owner_rule(PORTAL_DESTINATION)?, on_portal_owner_changed), + ]; + let backend = options + .desktop + .backend_name() + .filter(|_| !options.sandboxed); + if let Some(backend) = backend { + watches.push((owner_rule(backend)?, on_backend_owner_changed)); + } + let mut tasks = Vec::with_capacity(watches.len()); + for (rule, handler) in watches { + let stream = MessageStream::for_match_rule(rule, conn, None) + .await + .map_err(|_| PortalError::Bus)?; + tasks.push(executor.spawn(drain(state.clone(), generation, stream, handler))); + } + Ok(tasks) + } + + fn global_shortcuts_rule() -> Result, PortalError> { + MatchRule::builder() + .msg_type(MessageType::Signal) + .sender(PORTAL_DESTINATION) + .and_then(|rule| rule.interface(GLOBAL_SHORTCUTS_INTERFACE)) + .and_then(|rule| rule.path(PORTAL_PATH)) + .map(|rule| rule.build()) + .map_err(|_| PortalError::Bus) + } + + fn session_closed_rule() -> Result, PortalError> { + MatchRule::builder() + .msg_type(MessageType::Signal) + .sender(PORTAL_DESTINATION) + .and_then(|rule| rule.interface(SESSION_INTERFACE)) + .and_then(|rule| rule.member("Closed")) + .and_then(|rule| rule.path_namespace(SESSION_PATH_NAMESPACE)) + .map(|rule| rule.build()) + .map_err(|_| PortalError::Bus) + } + + fn owner_rule(name: &'static str) -> Result, PortalError> { + MatchRule::builder() + .msg_type(MessageType::Signal) + .sender(BUS_NAME) + .and_then(|rule| rule.interface(BUS_NAME)) + .and_then(|rule| rule.member("NameOwnerChanged")) + .and_then(|rule| rule.path(BUS_PATH)) + .and_then(|rule| rule.arg(0, name)) + .map(|rule| rule.build()) + .map_err(|_| PortalError::Bus) + } + + async fn drain( + state: Rc>, + generation: u64, + mut stream: MessageStream, + handler: SignalHandler, + ) { + while let Some(Ok(message)) = stream.next().await { + handler(&state, generation, &message); + } + let lost = state + .borrow_mut() + .lose_link(generation, SessionLostReason::BusError); + drop(lost); + } + + fn sent_by_portal(state: &State, generation: u64, message: &Message) -> bool { + let Some(owner) = state + .link_for(generation) + .and_then(|link| link.info.as_ref()) + .map(|info| &info.owner) + else { + return false; + }; + message.header().sender() == Some(owner.inner()) + } + + fn current_session<'a>(state: &'a State, session: &str) -> Option<&'a SessionState> { + state + .session + .as_ref() + .filter(|current| current.handle.as_str() == session) + } + + fn sent_by_bus(message: &Message) -> bool { + message + .header() + .sender() + .is_some_and(|sender| sender.as_str() == BUS_NAME) + } + + fn on_global_shortcuts_signal(state: &RefCell, generation: u64, message: &Message) { + let header = message.header(); + match header.member().map(|member| member.as_str()) { + Some("Activated") => on_activated(state, generation, message), + Some("Deactivated") => on_deactivated(state, generation, message), + Some("ShortcutsChanged") => on_shortcuts_changed(state, generation, message), + _ => {} + } + } + + fn shortcut_signal(state: &State, generation: u64, message: &Message) -> Option { + if !sent_by_portal(state, generation, message) { + return None; + } + let (session, id, _timestamp, _options): (OwnedObjectPath, String, u64, Results) = + message.body().deserialize().ok()?; + current_session(state, session.as_str()) + .is_some_and(|current| current.shortcuts.contains(&id)) + .then_some(id) + } + + fn on_activated(state: &RefCell, generation: u64, message: &Message) { + let mut state = state.borrow_mut(); + let Some(id) = shortcut_signal(&state, generation, message) else { + return; + }; + state.active.activate(&id); + state.emit(PortalEvent::Activated { id }); + } + + fn on_deactivated(state: &RefCell, generation: u64, message: &Message) { + let mut state = state.borrow_mut(); + let Some(id) = shortcut_signal(&state, generation, message) else { + return; + }; + state.active.deactivate(&id); + state.emit(PortalEvent::Deactivated { id }); + } + + fn on_shortcuts_changed(state: &RefCell, generation: u64, message: &Message) { + let mut state = state.borrow_mut(); + if !sent_by_portal(&state, generation, message) { + return; + } + let Ok((session, shortcuts)) = message + .body() + .deserialize::<(OwnedObjectPath, Vec<(String, Results)>)>() + else { + return; + }; + let Some(current) = state + .session + .as_mut() + .filter(|current| current.handle.as_str() == session.as_str()) + else { + return; + }; + let changed = current.shortcuts.merge( + shortcuts + .into_iter() + .map(|(id, properties)| binding_from_properties(id, &properties)) + .collect(), + ); + let merged = current.shortcuts.bindings(); + for id in changed { + if state.active.deactivate(&id) { + state.emit(PortalEvent::Deactivated { id }); + } + } + state.emit(PortalEvent::ShortcutsChanged { shortcuts: merged }); + } + + fn on_session_closed(state: &RefCell, generation: u64, message: &Message) { + let mut state = state.borrow_mut(); + if !sent_by_portal(&state, generation, message) { + return; + } + let header = message.header(); + let Some(path) = header.path() else { + return; + }; + if current_session(&state, path.as_str()).is_some() { + state.lose_session(SessionLostReason::Closed); + } + } + + fn owner_change(message: &Message) -> Option<(String, String, String)> { + if !sent_by_bus(message) { + return None; + } + message.body().deserialize().ok() + } + + fn on_portal_owner_changed(state: &RefCell, generation: u64, message: &Message) { + let Some((_name, old_owner, new_owner)) = owner_change(message) else { + return; + }; + let mut state = state.borrow_mut(); + let Some(link) = state.link_for(generation) else { + return; + }; + match link.info.as_ref() { + Some(info) => { + if portal_owner_lost(info.owner.as_str(), &old_owner, &new_owner) { + let link = state.lose_link(generation, SessionLostReason::PortalRestarted); + drop(state); + drop(link); + } + } + None => { + if portal_appeared(&old_owner, &new_owner) { + state.announce_portal(generation); + } + } + } + } + + fn on_backend_owner_changed(state: &RefCell, generation: u64, message: &Message) { + let Some((_name, old_owner, new_owner)) = owner_change(message) else { + return; + }; + let mut state = state.borrow_mut(); + if portal_appeared(&old_owner, &new_owner) { + state.announce_portal(generation); + return; + } + if !backend_owner_lost(&old_owner) { + return; + } + if state + .link_for(generation) + .is_none_or(|link| link.info.is_none()) + { + return; + } + let link = state.lose_link(generation, SessionLostReason::PortalRestarted); + drop(state); + drop(link); + } + + async fn begin_request(link: &LinkHandle, prefix: &str) -> Result { + let unique_name = link.conn.unique_name().ok_or(PortalError::Bus)?.to_string(); + let token = mint_token(prefix); + let predicted = OwnedObjectPath::try_from(request_path(&unique_name, &token)) + .map_err(|_| PortalError::InvalidReply)?; + let namespace = request_path(&unique_name, ""); + let namespace = namespace.trim_end_matches('/').to_string(); + let rule = MatchRule::builder() + .msg_type(MessageType::Signal) + .sender(PORTAL_DESTINATION) + .and_then(|rule| rule.interface(REQUEST_INTERFACE)) + .and_then(|rule| rule.member("Response")) + .and_then(|rule| rule.path_namespace(namespace)) + .map_err(|_| PortalError::Bus)? + .build(); + let stream = MessageStream::for_match_rule(rule, &link.conn, None) + .await + .map_err(|_| PortalError::Bus)?; + Ok(PendingRequest { + token, + predicted, + stream, + }) + } + + fn returned_request_path(reply: &Message) -> Option { + reply.body().deserialize::().ok() + } + + async fn wait_response( + state: &Rc>, + link: &LinkHandle, + pending: PendingRequest, + reply: &Message, + session_closed: Option>, + timeout: Option, + ) -> Result { + let PendingRequest { + predicted, + mut stream, + .. + } = pending; + let request = returned_request_path(reply).unwrap_or(predicted); + state.borrow_mut().pending_request = Some(request.clone()); + let owner = link.info.owner.clone(); + let response = async { + while let Some(item) = stream.next().await { + let Ok(message) = item else { + break; + }; + let header = message.header(); + if header.sender() != Some(owner.inner()) + || header.path().map(|path| path.as_str()) != Some(request.as_str()) + { + continue; + } + if let Ok((code, results)) = message.body().deserialize::<(u32, Results)>() { + return Ok(PortalResponse { code, results }); + } + } + Err(PortalError::Bus) + }; + let lost = async { + let _ = link.lost.recv().await; + Err(PortalError::SessionLost) + }; + let closed = async { + match session_closed { + Some(closed) => { + let _ = closed.recv().await; + Err(PortalError::SessionLost) + } + None => future::pending().await, + } + }; + let expired = async { + match timeout { + Some(limit) => { + Timer::after(limit).await; + Err(PortalError::Timeout) + } + None => future::pending().await, + } + }; + let result = response.or(lost).or(closed).or(expired).await; + let request = state.borrow_mut().pending_request.take(); + if let (Err(PortalError::Timeout), Some(request)) = (&result, request) { + let _ = close_object(&link.conn, &request, REQUEST_INTERFACE, TEARDOWN_TIMEOUT).await; + } + result + } + + async fn create_session( + state: &Rc>, + link: &LinkHandle, + ) -> Result<(), PortalError> { + let pending = begin_request(link, "fluxer_gs_create").await?; + let session_token = state.borrow().options.session_token.clone(); + let mut options: HashMap<&str, Value<'_>> = HashMap::new(); + options.insert("handle_token", Value::new(pending.token.as_str())); + options.insert("session_handle_token", Value::new(session_token.as_str())); + let reply = call( + &link.conn, + PORTAL_DESTINATION, + PORTAL_PATH, + GLOBAL_SHORTCUTS_INTERFACE, + "CreateSession", + &(options,), + Some(SETUP_TIMEOUT), + ) + .await + .map_err(|err| err.classify(classify_create_session_error))?; + let response = + wait_response(state, link, pending, &reply, None, Some(SETUP_TIMEOUT)).await?; + if response.code != 0 { + return Err(PortalError::Response(response.code)); + } + let handle = response + .results + .get("session_handle") + .and_then(|value| string_or_object_path(crate::kwin::value_of_owned(value))) + .and_then(|handle| OwnedObjectPath::try_from(handle).ok()) + .ok_or(PortalError::InvalidReply)?; + if state.borrow().link_for(link.generation).is_none() { + let _ = close_object(&link.conn, &handle, SESSION_INTERFACE, TEARDOWN_TIMEOUT).await; + return Err(PortalError::SessionLost); + } + let (closed, closed_rx) = async_channel::bounded(1); + state.borrow_mut().session = Some(SessionState { + handle, + bind_attempted: false, + shortcuts: ShortcutTable::default(), + closed, + closed_rx, + }); + Ok(()) + } + + fn session_handles(state: &State) -> Result<(OwnedObjectPath, Receiver<()>), PortalError> { + state + .session + .as_ref() + .map(|session| (session.handle.clone(), session.closed_rx.clone())) + .ok_or(PortalError::NoSession) + } + + async fn list_shortcuts( + state: &Rc>, + link: &LinkHandle, + ) -> Result, PortalError> { + let (session, closed) = session_handles(&state.borrow())?; + let pending = begin_request(link, "fluxer_gs_list").await?; + let mut options: HashMap<&str, Value<'_>> = HashMap::new(); + options.insert("handle_token", Value::new(pending.token.as_str())); + let reply = call( + &link.conn, + PORTAL_DESTINATION, + PORTAL_PATH, + GLOBAL_SHORTCUTS_INTERFACE, + "ListShortcuts", + &(&session, options), + Some(SETUP_TIMEOUT), + ) + .await + .map_err(|err| err.classify(classify_interface_error))?; + let response = wait_response( + state, + link, + pending, + &reply, + Some(closed), + Some(SETUP_TIMEOUT), + ) + .await?; + if response.code != 0 { + return Ok(Vec::new()); + } + Ok(bindings_from_results(&response.results)) + } + + async fn bind( + state: &Rc>, + shortcuts: &[ShortcutDefinition], + parent_window: &str, + ) -> Result { + let result = bind_session(state, shortcuts, parent_window).await; + if result.is_err() { + let mut state = state.borrow_mut(); + if state.session.is_none() { + state.lose_session(SessionLostReason::BusError); + } + } + result + } + + async fn bind_session( + state: &Rc>, + shortcuts: &[ShortcutDefinition], + parent_window: &str, + ) -> Result { + let link = state + .borrow() + .current_link() + .ok_or(PortalError::NoSession)?; + let attempted = state + .borrow() + .session + .as_ref() + .ok_or(PortalError::NoSession)? + .bind_attempted; + if attempted { + let previous = state.borrow_mut().drop_session(); + if let Some(previous) = previous { + let _ = close_object(&link.conn, &previous, SESSION_INTERFACE, SETUP_TIMEOUT).await; + } + create_session(state, &link).await?; + } + let (session, closed) = { + let mut state = state.borrow_mut(); + let session = state.session.as_mut().ok_or(PortalError::NoSession)?; + session.bind_attempted = true; + session.shortcuts = ShortcutTable::from_definitions(shortcuts); + (session.handle.clone(), session.closed_rx.clone()) + }; + let pending = begin_request(&link, "fluxer_gs_bind").await?; + let mut options: HashMap<&str, Value<'_>> = HashMap::new(); + options.insert("handle_token", Value::new(pending.token.as_str())); + let reply = call( + &link.conn, + PORTAL_DESTINATION, + PORTAL_PATH, + GLOBAL_SHORTCUTS_INTERFACE, + "BindShortcuts", + &( + &session, + serialize_shortcuts(shortcuts), + parent_window, + options, + ), + Some(SETUP_TIMEOUT), + ) + .await + .map_err(|err| err.classify(classify_interface_error))?; + let response = wait_response(state, &link, pending, &reply, Some(closed), None).await?; + let returned = known_bindings(state, bindings_from_results(&response.results))?; + let outcome = match bind_step(response.code, returned) { + BindStep::Done(outcome) => outcome, + BindStep::List => { + let listed = list_shortcuts(state, &link).await?; + listed_outcome(known_bindings(state, listed)?) + } + }; + let BindOutcome::Bound(bound) = outcome else { + return Ok(outcome); + }; + let mut state = state.borrow_mut(); + let session = state.session.as_mut().ok_or(PortalError::SessionLost)?; + session.shortcuts.merge(bound); + Ok(BindOutcome::Bound(session.shortcuts.bindings())) + } + + fn known_bindings( + state: &Rc>, + bindings: Vec, + ) -> Result, PortalError> { + let state = state.borrow(); + let session = state.session.as_ref().ok_or(PortalError::SessionLost)?; + Ok(session.shortcuts.known(bindings)) + } + + async fn configure(state: &Rc>, parent_window: &str) -> Result<(), PortalError> { + let link = state + .borrow() + .current_link() + .ok_or(PortalError::NoSession)?; + let (session, closed) = session_handles(&state.borrow())?; + if link.info.version < 2 { + return Err(PortalError::Version); + } + let options: HashMap<&str, Value<'_>> = HashMap::new(); + let request = async { + call( + &link.conn, + PORTAL_DESTINATION, + PORTAL_PATH, + GLOBAL_SHORTCUTS_INTERFACE, + "ConfigureShortcuts", + &(&session, parent_window, options), + Some(SETUP_TIMEOUT), + ) + .await + .map(|_| ()) + .map_err(|err| err.classify(classify_configure_error)) + }; + let interrupted = async { + async { + let _ = link.lost.recv().await; + } + .or(async { + let _ = closed.recv().await; + }) + .await; + Err(PortalError::SessionLost) + }; + request.or(interrupted).await + } + + fn serialize_shortcuts( + shortcuts: &[ShortcutDefinition], + ) -> Vec<(&str, HashMap<&str, Value<'_>>)> { + shortcuts + .iter() + .map(|shortcut| { + let mut options: HashMap<&str, Value<'_>> = HashMap::new(); + options.insert("description", Value::new(shortcut.description.as_str())); + if let Some(trigger) = shortcut.preferred_trigger.as_deref() { + options.insert("preferred_trigger", Value::new(trigger)); + } + (shortcut.id.as_str(), options) + }) + .collect() + } + + fn bindings_from_results(results: &Results) -> Vec { + let Some(value) = results.get("shortcuts") else { + return Vec::new(); + }; + let Value::Array(array) = unbox_value(crate::kwin::value_of_owned(value)) else { + return Vec::new(); + }; + array + .inner() + .iter() + .filter_map(binding_from_value) + .collect() + } + + fn binding_from_value(value: &Value<'_>) -> Option { + let Value::Structure(structure) = unbox_value(value) else { + return None; + }; + let [id, properties] = structure.fields() else { + return None; + }; + let id = string_or_object_path(id)?; + let Value::Dict(dict) = unbox_value(properties) else { + return Some(ShortcutBinding::new(id, None, None)); + }; + Some(ShortcutBinding::new( + id, + dict_string(dict, "description"), + dict_string(dict, "trigger_description"), + )) + } + + fn binding_from_properties(id: String, properties: &Results) -> ShortcutBinding { + let read = |key: &str| { + properties + .get(key) + .and_then(|value| string_or_object_path(crate::kwin::value_of_owned(value))) + }; + ShortcutBinding::new(id, read("description"), read("trigger_description")) + } + + fn dict_string(dict: &zbus::zvariant::Dict<'_, '_>, key: &str) -> Option { + dict.iter().find_map(|(k, v)| { + if string_or_object_path(k).as_deref() == Some(key) { + string_or_object_path(v) } else { None } + }) + } + + fn unbox_value<'a>(value: &'a Value<'a>) -> &'a Value<'a> { + match value { + Value::Value(inner) => unbox_value(inner), + other => other, } - _ => None, - } -} - -#[cfg(target_os = "linux")] -fn bound_shortcut_from_parts( - id: String, - properties: &HashMap, -) -> BoundShortcut { - BoundShortcut { - id, - description: properties - .get("description") - .and_then(|value| string_or_object_path(crate::kwin::value_of_owned(value))), - trigger_description: properties - .get("trigger_description") - .and_then(|value| string_or_object_path(crate::kwin::value_of_owned(value))), - } -} - -#[cfg(target_os = "linux")] -async fn close_session( - conn: &zbus::Connection, - session_handle: &str, -) -> Result<(), GlobalShortcutsError> { - let proxy = Proxy::new(conn, PORTAL_DESTINATION, session_handle, SESSION_INTERFACE) - .await - .map_err(|_| GlobalShortcutsError::DbusError)?; - proxy - .call::<_, _, ()>("Close", &()) - .await - .map_err(|_| GlobalShortcutsError::SendFailed) -} - -#[cfg(target_os = "linux")] -fn owned_path(path: &str) -> Result { - OwnedObjectPath::try_from(path.to_string()).map_err(|_| GlobalShortcutsError::InvalidReply) -} - -#[cfg(target_os = "linux")] -fn unbox_value<'a>(value: &'a Value<'a>) -> &'a Value<'a> { - match value { - Value::Value(inner) => unbox_value(inner), - other => other, - } -} - -#[cfg(target_os = "linux")] -fn string_or_object_path(value: &Value<'_>) -> Option { - match unbox_value(value) { - Value::Str(v) => Some(v.as_str().to_string()), - Value::ObjectPath(v) => Some(v.as_str().to_string()), - _ => None, - } -} - -#[cfg(target_os = "linux")] -pub fn get_portal_version() -> Option { - let conn = zbus::blocking::connection::Builder::session() - .ok()? - .method_timeout(Duration::from_millis(1_500)) - .build() - .ok()?; - let proxy = zbus::blocking::Proxy::new( - &conn, - PORTAL_DESTINATION, - PORTAL_PATH, - GLOBAL_SHORTCUTS_INTERFACE, - ) - .ok()?; - proxy.get_property("version").ok() -} - -#[cfg(target_os = "linux")] -pub fn is_available() -> bool { - get_portal_version().is_some() -} - -#[cfg(not(target_os = "linux"))] -pub struct Subscription; - -#[cfg(not(target_os = "linux"))] -impl Subscription { - pub fn configure( - _entries: Vec, - _callback: Arc, - ) -> Result<(Self, ConfigureResult), GlobalShortcutsError> { - Err(GlobalShortcutsError::DbusError) } - pub fn close(&self) {} -} - -#[cfg(not(target_os = "linux"))] -pub fn get_portal_version() -> Option { - None -} - -#[cfg(not(target_os = "linux"))] -pub fn is_available() -> bool { - false + fn string_or_object_path(value: &Value<'_>) -> Option { + match unbox_value(value) { + Value::Str(v) => Some(v.as_str().to_string()), + Value::ObjectPath(v) => Some(v.as_str().to_string()), + _ => None, + } + } } #[cfg(test)] mod tests { use super::*; - #[test] - fn shortcut_id_match_requires_same_ids() { - let entries = vec![ShortcutEntry { - id: "one".into(), - description: "One".into(), - preferred_trigger: Some("CTRL+o".into()), - }]; - let persisted = vec![BoundShortcut { - id: "one".into(), - description: None, - trigger_description: None, - }]; - assert!(shortcut_ids_match(&entries, &persisted)); + fn binding(id: &str, trigger: Option<&str>) -> ShortcutBinding { + ShortcutBinding::new(id.into(), None, trigger.map(Into::into)) + } + + fn definition(id: &str, description: &str) -> ShortcutDefinition { + ShortcutDefinition { + id: id.into(), + description: description.into(), + preferred_trigger: None, + } } #[test] - fn shortcut_id_match_rejects_empty() { - assert!(!shortcut_ids_match(&[], &[])); + fn bind_step_prefers_returned_shortcuts_over_code() { + let bound = vec![binding("voice_push_to_talk", Some("Ctrl+Shift+P"))]; + for code in [0, 1, 2, 9] { + assert_eq!( + bind_step(code, bound.clone()), + BindStep::Done(BindOutcome::Bound(bound.clone())) + ); + } + } + + #[test] + fn bind_step_maps_empty_results_by_code() { + assert_eq!(bind_step(0, Vec::new()), BindStep::List); + assert_eq!( + bind_step(1, Vec::new()), + BindStep::Done(BindOutcome::Cancelled) + ); + assert_eq!( + bind_step(2, Vec::new()), + BindStep::Done(BindOutcome::Denied) + ); + assert_eq!( + bind_step(3, Vec::new()), + BindStep::Done(BindOutcome::Failed(3)) + ); + } + + #[test] + fn listed_outcome_binds_only_when_the_portal_lists_shortcuts() { + assert_eq!(listed_outcome(Vec::new()), BindOutcome::Denied); + let listed = vec![binding("voice_push_to_talk", None)]; + assert_eq!(listed_outcome(listed.clone()), BindOutcome::Bound(listed)); + } + + #[test] + fn binding_drops_empty_strings() { + let parsed = ShortcutBinding::new("a".into(), Some(String::new()), Some(String::new())); + assert_eq!(parsed.description, None); + assert_eq!(parsed.trigger_description, None); + let kept = ShortcutBinding::new("a".into(), Some("A".into()), Some("F13".into())); + assert_eq!(kept.description.as_deref(), Some("A")); + assert_eq!(kept.trigger_description.as_deref(), Some("F13")); + } + + #[test] + fn definitions_must_be_non_empty_unique_and_described() { + assert_eq!(validate_definitions(&[]), Err(DefinitionError::Empty)); + assert_eq!( + validate_definitions(&[definition("", "Mute")]), + Err(DefinitionError::EmptyId) + ); + assert_eq!( + validate_definitions(&[definition("voice_toggle_mute", " ")]), + Err(DefinitionError::EmptyDescription) + ); + assert_eq!( + validate_definitions(&[ + definition("voice_toggle_mute", "Mute"), + definition("voice_toggle_mute", "Mute again"), + ]), + Err(DefinitionError::DuplicateId) + ); + assert_eq!( + validate_definitions(&[ + definition("voice_toggle_mute", "Mute"), + definition("voice_push_to_talk", "Push to talk"), + ]), + Ok(()) + ); + } + + #[test] + fn tokens_only_allow_portal_safe_characters() { + assert!(is_valid_token("fluxer_global_shortcuts")); + assert!(is_valid_token("fluxer_canary_global_shortcuts")); + assert!(!is_valid_token("")); + assert!(!is_valid_token("fluxer-global")); + assert!(!is_valid_token("fluxer.global")); + } + + #[test] + fn desktop_selects_only_its_own_backend() { + assert_eq!( + Desktop::parse("kde").backend_name(), + Some("org.freedesktop.impl.portal.desktop.kde") + ); + assert_eq!( + Desktop::parse("gnome").backend_name(), + Some("org.freedesktop.impl.portal.desktop.gnome") + ); + assert_eq!( + Desktop::parse("hyprland").backend_name(), + Some("org.freedesktop.impl.portal.desktop.hyprland") + ); + assert_eq!(Desktop::parse("other").backend_name(), None); + assert_eq!(Desktop::parse("sway").backend_name(), None); + } + + #[test] + fn snap_scopes_are_detected_from_cgroups() { + assert!(cgroup_is_snap( + "0::/user.slice/user-1000.slice/user@1000.service/apps.slice/snap.fluxer.fluxer-1234.scope\n" + )); + assert!(!cgroup_is_snap( + "0::/user.slice/user-1000.slice/user@1000.service/app.slice/app-gnome-fluxer-1234.scope\n" + )); + assert!(cgroup_is_snap( + "12:pids:/user.slice\n1:name=systemd:/user.slice/snap.fluxer.fluxer-1.scope\n" + )); + assert!(!cgroup_is_snap("garbage\n")); + } + + #[test] + fn shortcut_table_drops_unknown_ids() { + let table = ShortcutTable::from_definitions(&[definition("voice_push_to_talk", "PTT")]); + assert!(table.contains("voice_push_to_talk")); + assert!(!table.contains("stray")); + assert_eq!( + table.known(vec![ + binding("stray", Some("F1")), + binding("voice_push_to_talk", Some("F13")), + ]), + vec![binding("voice_push_to_talk", Some("F13"))] + ); + } + + #[test] + fn shortcut_table_merges_deltas_and_reports_trigger_changes() { + let mut table = ShortcutTable::from_definitions(&[ + definition("voice_push_to_talk", "PTT"), + definition("voice_toggle_mute", "Mute"), + ]); + let changed = table.merge(vec![ + binding("voice_push_to_talk", Some("F13")), + binding("voice_toggle_mute", None), + binding("stray", Some("F2")), + ]); + assert_eq!(changed, vec!["voice_push_to_talk".to_string()]); + assert_eq!( + table.merge(vec![binding("voice_toggle_mute", Some("F9"))]), + vec!["voice_toggle_mute".to_string()] + ); + let bindings = table.bindings(); + assert_eq!(bindings.len(), 2); + assert_eq!( + bindings[0], + ShortcutBinding::new( + "voice_push_to_talk".into(), + Some("PTT".into()), + Some("F13".into()) + ) + ); + assert_eq!(bindings[1].trigger_description.as_deref(), Some("F9")); + assert_eq!( + table.merge(vec![binding("voice_push_to_talk", None)]), + vec!["voice_push_to_talk".to_string()] + ); + assert!( + table + .merge(vec![binding("voice_push_to_talk", None)]) + .is_empty() + ); + } + + #[test] + fn listed_table_accepts_the_listed_ids() { + let table = ShortcutTable::from_listed(&[binding("voice_push_to_talk", Some("F13"))]); + assert!(table.contains("voice_push_to_talk")); + assert!(!table.contains("voice_toggle_mute")); + assert!(ShortcutTable::default().bindings().is_empty()); + } + + #[test] + fn register_errors_are_classified() { + for name in [ + "org.freedesktop.DBus.Error.UnknownMethod", + "org.freedesktop.DBus.Error.UnknownInterface", + "org.freedesktop.portal.Error.NotAllowed", + "org.freedesktop.DBus.Error.AccessDenied", + ] { + assert_eq!( + classify_register_error(name, ""), + RegisterFailure::Unregistered + ); + } + assert_eq!( + classify_register_error( + "org.freedesktop.portal.Error.Failed", + "Can't create registered app" + ), + RegisterFailure::Retry + ); + assert_eq!( + classify_register_error( + "org.freedesktop.portal.Error.Failed", + "Connection already associated with an application ID" + ), + RegisterFailure::AlreadyAssociated + ); + for missing in [ + "org.freedesktop.DBus.Error.ServiceUnknown", + "org.freedesktop.DBus.Error.NameHasNoOwner", + "org.freedesktop.DBus.Error.NoReply", + "org.freedesktop.DBus.Error.TimedOut", + ] { + assert_eq!( + classify_register_error(missing, ""), + RegisterFailure::Fatal(PortalError::NoPortal) + ); + } + } + + #[test] + fn interface_errors_are_classified() { + for name in [ + "org.freedesktop.DBus.Error.InvalidArgs", + "org.freedesktop.DBus.Error.UnknownInterface", + "org.freedesktop.DBus.Error.UnknownMethod", + "org.freedesktop.DBus.Error.UnknownProperty", + ] { + assert_eq!(classify_interface_error(name), PortalError::Interface); + } + for name in [ + "org.freedesktop.DBus.Error.ServiceUnknown", + "org.freedesktop.DBus.Error.NameHasNoOwner", + ] { + assert_eq!(classify_interface_error(name), PortalError::NoPortal); + } + assert_eq!( + classify_interface_error("org.freedesktop.DBus.Error.AccessDenied"), + PortalError::Bus + ); + } + + #[test] + fn no_reply_and_timed_out_are_transient_after_register() { + for name in [ + "org.freedesktop.DBus.Error.NoReply", + "org.freedesktop.DBus.Error.TimedOut", + ] { + assert_eq!(classify_interface_error(name), PortalError::Timeout); + assert_eq!(classify_create_session_error(name), PortalError::Timeout); + assert_eq!(classify_configure_error(name), PortalError::Timeout); + assert_eq!(classify_owner_error(name), PortalError::Timeout); + } + } + + #[test] + fn owner_lookup_without_owner_means_no_portal() { + assert_eq!( + classify_owner_error("org.freedesktop.DBus.Error.NameHasNoOwner"), + PortalError::NoPortal + ); + } + + #[test] + fn create_session_not_allowed_means_missing_app_id() { + assert_eq!( + classify_create_session_error("org.freedesktop.portal.Error.NotAllowed"), + PortalError::AppIdRequired + ); + assert_eq!( + classify_create_session_error("org.freedesktop.DBus.Error.UnknownMethod"), + PortalError::Interface + ); + } + + #[test] + fn configure_unknown_method_or_failure_means_unsupported() { + assert_eq!( + classify_configure_error("org.freedesktop.DBus.Error.UnknownMethod"), + PortalError::Version + ); + assert_eq!( + classify_configure_error("org.freedesktop.portal.Error.Failed"), + PortalError::Version + ); + } + + #[test] + fn error_messages_carry_category_prefixes() { + assert_eq!(PortalError::NoPortal.to_string(), "unsupported:no-portal"); + assert_eq!(PortalError::Interface.to_string(), "unsupported:interface"); + assert_eq!(PortalError::Version.to_string(), "unsupported:version"); + assert_eq!(PortalError::Response(2).to_string(), "unsupported:2"); + assert_eq!( + PortalError::AppIdRequired.to_string(), + "identity:app-id-required" + ); + assert_eq!(PortalError::Timeout.to_string(), "error:timeout"); + } + + #[test] + fn portal_owner_changes_ignore_the_stored_owner_appearing() { + assert!(!portal_owner_lost(":1.5", "", ":1.5")); + } + + #[test] + fn portal_owner_changes_detect_loss_and_replacement() { + assert!(portal_owner_lost(":1.5", ":1.5", "")); + assert!(portal_owner_lost(":1.5", ":1.5", ":1.9")); + assert!(portal_owner_lost(":1.5", "", ":1.9")); + assert!(!portal_owner_lost(":1.5", ":1.7", "")); + } + + #[test] + fn portal_appears_only_when_it_gains_an_owner() { + assert!(portal_appeared("", ":1.9")); + assert!(!portal_appeared(":1.5", "")); + assert!(!portal_appeared(":1.5", ":1.9")); + assert!(!portal_appeared("", "")); + } + + #[test] + fn backend_owner_loss_requires_a_previous_owner() { + assert!(!backend_owner_lost("")); + assert!(backend_owner_lost(":1.12")); + } + + #[test] + fn active_shortcuts_track_held_ids() { + let mut active = ActiveShortcuts::default(); + active.activate("voice_push_to_talk"); + active.activate("voice_push_to_talk"); + assert!(!active.deactivate("voice_toggle_mute")); + assert!(active.deactivate("voice_push_to_talk")); + assert!(!active.deactivate("voice_push_to_talk")); + } + + #[test] + fn active_shortcuts_release_all_empties_the_set() { + let mut active = ActiveShortcuts::default(); + active.activate("voice_push_to_talk"); + active.activate("voice_push_to_mute"); + assert_eq!( + active.release_all(), + vec![ + "voice_push_to_mute".to_string(), + "voice_push_to_talk".to_string() + ] + ); + assert!(active.release_all().is_empty()); + } + + #[test] + fn responder_rejects_with_closed_when_dropped() { + let (tx, rx) = std::sync::mpsc::channel(); + drop(Responder::::new(move |result| { + let _ = tx.send(result); + })); + assert_eq!(rx.recv().unwrap(), Err(PortalError::Closed)); + } + + #[test] + fn responder_delivers_once() { + let (tx, rx) = std::sync::mpsc::channel(); + Responder::::new(move |result| { + let _ = tx.send(result); + }) + .respond(Ok(7)); + assert_eq!(rx.recv().unwrap(), Ok(7)); + assert!(rx.try_recv().is_err()); } } diff --git a/fluxer_desktop/native/linux-portals/src/lib.rs b/fluxer_desktop/native/linux-portals/src/lib.rs index 8314db06c..4be25de3a 100644 --- a/fluxer_desktop/native/linux-portals/src/lib.rs +++ b/fluxer_desktop/native/linux-portals/src/lib.rs @@ -8,6 +8,7 @@ pub mod gnome_shell; pub mod kwin; #[cfg(target_os = "linux")] pub mod portal; +pub mod session_monitor; pub mod settings; pub mod x11; @@ -19,7 +20,7 @@ mod napi_bindings { use std::sync::Arc; use napi::{ - Env, Status, + Env, JsDeferred, Status, bindgen_prelude::{Array, AsyncTask, Function, Object, Result, Task, ToNapiValue}, sys, threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode, UnknownReturnValue}, @@ -29,14 +30,18 @@ mod napi_bindings { use crate::{ background::{self, RequestOptions, RequestResult}, filechooser::{self, FileChooserResult, Filter, FilterRule, Mode, Options}, - global_shortcuts::{self, BoundShortcut, ConfigureResult, ShortcutEntry, ShortcutEvent}, + global_shortcuts::{ + BindOutcome, DEFAULT_SESSION_TOKEN, Desktop, GlobalShortcutsClient, OpenResult, + PortalEvent, PortalOptions, Responder, ShortcutBinding, ShortcutDefinition, + is_valid_token, validate_definitions, + }, gnome_shell, kwin, + session_monitor::{MonitorEvent, SessionMonitor}, settings::{self, ChangeEvent, ChangePayload, ColorScheme, Contrast}, x11, }; const SETTINGS_EVENT_QUEUE_LIMIT: usize = 128; - const SHORTCUT_EVENT_QUEUE_LIMIT: usize = 128; fn generic_error(reason: impl Into) -> napi::Error { napi::Error::new(Status::GenericFailure, reason.into()) @@ -310,73 +315,43 @@ mod napi_bindings { Ok(AsyncTask::new(BackgroundTask { options: parsed })) } - #[napi(js_name = "isAvailable")] - pub fn is_available_js() -> bool { - global_shortcuts::is_available() - } - - #[napi(js_name = "getPortalVersion")] - pub fn get_portal_version_js() -> Option { - global_shortcuts::get_portal_version() - } - - fn parse_shortcut_entries(array: Array) -> Result> { - let mut entries = Vec::with_capacity(array.len() as usize); + fn parse_shortcut_definitions(array: Array) -> Result> { + let mut shortcuts = Vec::with_capacity(array.len() as usize); for i in 0..array.len() { let object = array .get::(i) .map_err(|err| invalid_arg(err.reason.clone()))? - .ok_or_else(|| invalid_arg("shortcut entries must be objects"))?; + .ok_or_else(|| invalid_arg("shortcuts must be objects"))?; let id = read_string_field(&object, "id") .ok_or_else(|| invalid_arg("shortcut.id must be a string"))?; let description = read_string_field(&object, "description") .ok_or_else(|| invalid_arg("shortcut.description must be a string"))?; - entries.push(ShortcutEntry { + shortcuts.push(ShortcutDefinition { id, description, preferred_trigger: read_string_field(&object, "preferredTrigger"), }); } - Ok(entries) + validate_definitions(&shortcuts).map_err(|err| invalid_arg(err.message()))?; + Ok(shortcuts) } - fn bound_shortcuts_to_array(env: &Env, shortcuts: &[BoundShortcut]) -> Result> { + fn bindings_to_array(env: &Env, shortcuts: &[ShortcutBinding]) -> Result> { let mut array = env.create_array(shortcuts.len() as u32)?; for (i, shortcut) in shortcuts.iter().enumerate() { let mut obj = Object::new(env)?; obj.set("id", shortcut.id.as_str())?; - if let Some(description) = shortcut.description.as_deref() { - obj.set("description", description)?; - } - if let Some(trigger) = shortcut.trigger_description.as_deref() { - obj.set("triggerDescription", trigger)?; - } + obj.set("description", shortcut.description.as_deref())?; + obj.set( + "triggerDescription", + shortcut.trigger_description.as_deref(), + )?; array.set(i as u32, obj)?; } Ok(unsafe { std::mem::transmute::, Array<'static>>(array) }) } - pub enum NapiShortcutEvent { - Activated { id: String }, - Deactivated { id: String }, - ShortcutsChanged { shortcuts: Vec }, - Closed, - } - - impl From for NapiShortcutEvent { - fn from(event: ShortcutEvent) -> Self { - match event { - ShortcutEvent::Activated { id } => Self::Activated { id }, - ShortcutEvent::Deactivated { id } => Self::Deactivated { id }, - ShortcutEvent::ShortcutsChanged { shortcuts } => { - Self::ShortcutsChanged { shortcuts } - } - ShortcutEvent::Closed => Self::Closed, - } - } - } - - impl ToNapiValue for NapiShortcutEvent { + impl ToNapiValue for PortalEvent { unsafe fn to_napi_value(raw_env: sys::napi_env, event: Self) -> Result { let env = Env::from_raw(raw_env); let mut obj = Object::new(&env)?; @@ -391,137 +366,205 @@ mod napi_bindings { } Self::ShortcutsChanged { shortcuts } => { obj.set("type", "shortcuts-changed")?; - obj.set("shortcuts", bound_shortcuts_to_array(&env, &shortcuts)?)?; + obj.set("shortcuts", bindings_to_array(&env, &shortcuts)?)?; } - Self::Closed => { - obj.set("type", "closed")?; + Self::SessionLost { reason } => { + obj.set("type", "session-lost")?; + obj.set("reason", reason.as_str())?; + } + Self::PortalAvailable => { + obj.set("type", "portal-available")?; } } unsafe { as ToNapiValue>::to_napi_value(raw_env, obj) } } } - type ShortcutTsfn = Arc< - ThreadsafeFunction< - NapiShortcutEvent, - UnknownReturnValue, - NapiShortcutEvent, - Status, - false, - true, - SHORTCUT_EVENT_QUEUE_LIMIT, - >, - >; - - pub struct ConfigureShortcutsTask { - entries: Vec, - state: Arc>>, - callback: ShortcutTsfn, + impl ToNapiValue for OpenResult { + unsafe fn to_napi_value(raw_env: sys::napi_env, result: Self) -> Result { + let env = Env::from_raw(raw_env); + let mut obj = Object::new(&env)?; + obj.set("version", result.version)?; + obj.set("appIdSource", result.app_id_source.as_str())?; + obj.set("uniqueName", result.unique_name)?; + obj.set("listed", bindings_to_array(&env, &result.listed)?)?; + unsafe { as ToNapiValue>::to_napi_value(raw_env, obj) } + } } - impl Task for ConfigureShortcutsTask { - type Output = ConfigureResult; - type JsValue = Object<'static>; - - fn compute(&mut self) -> Result { - let tsfn_for_cb = self.callback.clone(); - let callback = Arc::new(move |event: ShortcutEvent| { - let _ = tsfn_for_cb.call( - NapiShortcutEvent::from(event), - ThreadsafeFunctionCallMode::NonBlocking, - ); - }); - let (subscription, result) = - global_shortcuts::Subscription::configure(self.entries.clone(), callback) - .map_err(|err| generic_error(format!("GlobalShortcuts portal: {err}")))?; - let mut guard = self - .state - .lock() - .map_err(|_| generic_error("global shortcuts lock poisoned"))?; - if let Some(previous) = guard.replace(subscription) { - previous.close(); - } - Ok(result) - } - - fn resolve(&mut self, env: Env, output: Self::Output) -> Result { + impl ToNapiValue for BindOutcome { + unsafe fn to_napi_value(raw_env: sys::napi_env, outcome: Self) -> Result { + let env = Env::from_raw(raw_env); let mut obj = Object::new(&env)?; - obj.set("action", output.action)?; - obj.set( - "shortcuts", - bound_shortcuts_to_array(&env, &output.shortcuts)?, - )?; - Ok(unsafe { std::mem::transmute::, Object<'static>>(obj) }) + match outcome { + Self::Bound(shortcuts) => { + obj.set("outcome", "bound")?; + obj.set("shortcuts", bindings_to_array(&env, &shortcuts)?)?; + } + Self::Cancelled => obj.set("outcome", "cancelled")?, + Self::Denied => obj.set("outcome", "denied")?, + Self::Failed(code) => { + obj.set("outcome", "failed")?; + obj.set("code", code)?; + } + } + unsafe { as ToNapiValue>::to_napi_value(raw_env, obj) } } } + type PortalEventTsfn = + ThreadsafeFunction; + + type Settle = JsDeferred Result>>; + + fn deferred_responder(deferred: Settle) -> Responder { + Responder::new(move |result| match result { + Ok(value) => deferred.resolve(Box::new(move |_env| Ok(value))), + Err(err) => deferred.reject(generic_error(err.to_string())), + }) + } + + fn create_promise( + env: &Env, + ) -> Result<(Responder, Object<'static>)> { + let (deferred, promise) = env.create_deferred:: Result>>()?; + Ok((deferred_responder(deferred), unsafe { + std::mem::transmute::, Object<'static>>(promise) + })) + } + #[napi] pub struct GlobalShortcutsPortal { - subscription: Arc>>, - callback: ShortcutTsfn, - #[allow(dead_code)] - app_id: Option, + client: GlobalShortcutsClient, } #[napi] impl GlobalShortcutsPortal { #[napi(constructor)] pub fn new( - on_event: Function, - app_id: Option, + on_event: Function, + options: Object, ) -> Result { - let callback: ShortcutTsfn = Arc::new( - on_event - .build_threadsafe_function::() - .weak::() - .callee_handled::() - .max_queue_size::() - .build() - .map_err(|err| { - generic_error(format!( - "failed to create global shortcuts callback: {}", - err.reason - )) - })?, - ); - Ok(Self { - subscription: Arc::new(std::sync::Mutex::new(None)), - callback, - app_id, - }) - } - - #[napi] - pub fn configure(&self, entries: Array) -> Result> { - let parsed = parse_shortcut_entries(entries)?; - Ok(AsyncTask::new(ConfigureShortcutsTask { - entries: parsed, - state: self.subscription.clone(), - callback: self.callback.clone(), - })) - } - - #[napi] - pub fn close(&self) -> Result<()> { - if let Some(subscription) = self - .subscription - .lock() - .map_err(|_| generic_error("global shortcuts lock poisoned"))? - .take() - { - subscription.close(); + let sandboxed = read_bool_field(&options, "sandboxed") + .ok_or_else(|| invalid_arg("options.sandboxed must be a boolean"))?; + let portal_app_id = read_string_field(&options, "portalAppId"); + let session_token = read_string_field(&options, "sessionToken") + .unwrap_or_else(|| DEFAULT_SESSION_TOKEN.to_string()); + if !is_valid_token(&session_token) { + return Err(invalid_arg( + "options.sessionToken must only contain A-Z, a-z, 0-9 and _", + )); } - Ok(()) + let desktop = read_string_field(&options, "desktop") + .map_or(Desktop::Other, |value| Desktop::parse(&value)); + let callback: PortalEventTsfn = on_event + .build_threadsafe_function::() + .weak::() + .callee_handled::() + .build() + .map_err(|err| { + generic_error(format!( + "failed to create global shortcuts callback: {}", + err.reason + )) + })?; + let sink = Arc::new(move |event: PortalEvent| { + let _ = callback.call(event, ThreadsafeFunctionCallMode::Blocking); + }); + let client = GlobalShortcutsClient::spawn( + PortalOptions { + portal_app_id, + sandboxed, + session_token, + desktop, + }, + sink, + ) + .map_err(|err| generic_error(format!("failed to start global shortcuts: {err}")))?; + Ok(Self { client }) + } + + #[napi] + pub fn open(&self, env: Env) -> Result> { + let (reply, promise) = create_promise::(&env)?; + self.client.open(reply); + Ok(promise) + } + + #[napi] + pub fn bind( + &self, + env: Env, + shortcuts: Array, + parent_window: String, + ) -> Result> { + let shortcuts = parse_shortcut_definitions(shortcuts)?; + let (reply, promise) = create_promise::(&env)?; + self.client.bind(shortcuts, parent_window, reply); + Ok(promise) + } + + #[napi] + pub fn configure(&self, env: Env, parent_window: String) -> Result> { + let (reply, promise) = create_promise::<()>(&env)?; + self.client.configure(parent_window, reply); + Ok(promise) + } + + #[napi] + pub fn close(&self) { + self.client.close(); } } - impl Drop for GlobalShortcutsPortal { - fn drop(&mut self) { - if let Ok(mut guard) = self.subscription.lock() - && let Some(subscription) = guard.take() - { - subscription.close(); - } + impl ToNapiValue for MonitorEvent { + unsafe fn to_napi_value(raw_env: sys::napi_env, event: Self) -> Result { + let env = Env::from_raw(raw_env); + let mut obj = Object::new(&env)?; + obj.set( + "type", + match event { + Self::ScreenLocked => "screen-locked", + Self::ScreenUnlocked => "screen-unlocked", + }, + )?; + unsafe { as ToNapiValue>::to_napi_value(raw_env, obj) } + } + } + + type MonitorEventTsfn = + ThreadsafeFunction; + + #[napi] + pub struct SessionStateMonitor { + monitor: SessionMonitor, + } + + #[napi] + impl SessionStateMonitor { + #[napi(constructor)] + pub fn new(on_event: Function) -> Result { + let callback: MonitorEventTsfn = on_event + .build_threadsafe_function::() + .weak::() + .callee_handled::() + .build() + .map_err(|err| { + generic_error(format!( + "failed to create session monitor callback: {}", + err.reason + )) + })?; + let monitor = SessionMonitor::spawn(Arc::new(move |event: MonitorEvent| { + let _ = callback.call(event, ThreadsafeFunctionCallMode::Blocking); + })) + .map_err(|err| generic_error(format!("failed to start session monitor: {err}")))?; + Ok(Self { monitor }) + } + + #[napi] + pub fn close(&self) { + self.monitor.close(); } } diff --git a/fluxer_desktop/native/linux-portals/src/session_monitor.rs b/fluxer_desktop/native/linux-portals/src/session_monitor.rs new file mode 100644 index 000000000..5ca481435 --- /dev/null +++ b/fluxer_desktop/native/linux-portals/src/session_monitor.rs @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +use std::time::Duration; + +pub const PORTAL_DESTINATION: &str = "org.freedesktop.portal.Desktop"; +pub const PORTAL_PATH: &str = "/org/freedesktop/portal/desktop"; +pub const INHIBIT_INTERFACE: &str = "org.freedesktop.portal.Inhibit"; +pub const SESSION_INTERFACE: &str = "org.freedesktop.portal.Session"; +pub const BUS_NAME: &str = "org.freedesktop.DBus"; +pub const BUS_PATH: &str = "/org/freedesktop/DBus"; +pub const SETUP_TIMEOUT: Duration = Duration::from_secs(30); +pub const SHORT_TIMEOUT: Duration = Duration::from_secs(2); +pub const QUERY_END: u32 = 2; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MonitorEvent { + ScreenLocked, + ScreenUnlocked, +} + +#[derive(Debug, Default)] +pub struct LockTracker { + locked: bool, +} + +impl LockTracker { + pub fn update(&mut self, screensaver_active: bool) -> Option { + if screensaver_active == self.locked { + return None; + } + self.locked = screensaver_active; + Some(if screensaver_active { + MonitorEvent::ScreenLocked + } else { + MonitorEvent::ScreenUnlocked + }) + } +} + +#[cfg(target_os = "linux")] +pub use worker::SessionMonitor; + +#[cfg(target_os = "linux")] +mod worker { + use std::{cell::RefCell, collections::HashMap, sync::Arc, thread}; + + use async_channel::{Receiver, Sender}; + use async_io::Timer; + use futures_lite::{FutureExt, StreamExt}; + use zbus::{ + Connection, MatchRule, Message, MessageStream, + message::Type as MessageType, + names::OwnedUniqueName, + zvariant::{DynamicType, OwnedObjectPath, OwnedValue, Value}, + }; + + use super::*; + use crate::portal::{REQUEST_INTERFACE, mint_token, request_path}; + + type EventSink = Arc; + type Results = HashMap; + + pub struct SessionMonitor { + shutdown: Sender<()>, + } + + impl SessionMonitor { + pub fn spawn(sink: EventSink) -> std::io::Result { + let (shutdown, shutdown_rx) = async_channel::bounded(1); + thread::Builder::new() + .name("fluxer-linux-portals-session-monitor".to_string()) + .spawn(move || async_io::block_on(run(sink, shutdown_rx)))?; + Ok(Self { shutdown }) + } + + pub fn close(&self) { + self.shutdown.close(); + } + } + + impl Drop for SessionMonitor { + fn drop(&mut self) { + self.close(); + } + } + + struct Monitor { + owner: OwnedUniqueName, + handle: OwnedObjectPath, + } + + enum Next { + Owner(Message), + State(Message), + End, + } + + async fn run(sink: EventSink, shutdown: Receiver<()>) { + let Ok(conn) = Connection::session().await else { + return; + }; + let current = RefCell::new(None::); + watch(&conn, &sink, ¤t) + .or(async { + let _ = shutdown.recv().await; + }) + .await; + if let Some(monitor) = current.take() { + let _ = call( + &conn, + PORTAL_DESTINATION, + monitor.handle.as_str(), + SESSION_INTERFACE, + "Close", + &(), + SHORT_TIMEOUT, + ) + .await; + } + } + + async fn watch(conn: &Connection, sink: &EventSink, current: &RefCell>) { + let Ok(mut owners) = subscribe(conn, owner_rule()).await else { + return; + }; + let Ok(mut states) = subscribe(conn, state_rule()).await else { + return; + }; + let mut tracker = LockTracker::default(); + let mut attempt = true; + loop { + if attempt { + attempt = false; + let created = create_monitor(conn).await; + current.replace(created); + } + let next = async { + match owners.next().await { + Some(Ok(message)) => Next::Owner(message), + _ => Next::End, + } + } + .or(async { + match states.next().await { + Some(Ok(message)) => Next::State(message), + _ => Next::End, + } + }) + .await; + match next { + Next::End => return, + Next::Owner(message) => { + let Some(new_owner) = new_portal_owner(&message) else { + continue; + }; + let unchanged = current + .borrow() + .as_ref() + .is_some_and(|monitor| monitor.owner.as_str() == new_owner); + if unchanged { + continue; + } + current.replace(None); + attempt = !new_owner.is_empty(); + } + Next::State(message) => { + let Some((handle, active, session_state)) = + state_change(&message, current.borrow().as_ref()) + else { + continue; + }; + if session_state == QUERY_END { + let _ = call( + conn, + PORTAL_DESTINATION, + PORTAL_PATH, + INHIBIT_INTERFACE, + "QueryEndResponse", + &(&handle,), + SHORT_TIMEOUT, + ) + .await; + } + if let Some(event) = tracker.update(active) { + sink(event); + } + } + } + } + } + + async fn subscribe( + conn: &Connection, + rule: zbus::Result>, + ) -> zbus::Result { + MessageStream::for_match_rule(rule?, conn, None).await + } + + fn owner_rule() -> zbus::Result> { + Ok(MatchRule::builder() + .msg_type(MessageType::Signal) + .sender(BUS_NAME)? + .interface(BUS_NAME)? + .member("NameOwnerChanged")? + .path(BUS_PATH)? + .arg(0, PORTAL_DESTINATION)? + .build()) + } + + fn state_rule() -> zbus::Result> { + Ok(MatchRule::builder() + .msg_type(MessageType::Signal) + .sender(PORTAL_DESTINATION)? + .interface(INHIBIT_INTERFACE)? + .member("StateChanged")? + .path(PORTAL_PATH)? + .build()) + } + + fn new_portal_owner(message: &Message) -> Option { + let header = message.header(); + if header + .sender() + .is_none_or(|sender| sender.as_str() != BUS_NAME) + { + return None; + } + let (_name, _old, new): (String, String, String) = message.body().deserialize().ok()?; + Some(new) + } + + fn state_change( + message: &Message, + monitor: Option<&Monitor>, + ) -> Option<(OwnedObjectPath, bool, u32)> { + let monitor = monitor?; + if message.header().sender() != Some(monitor.owner.inner()) { + return None; + } + let (handle, state): (OwnedObjectPath, Results) = message.body().deserialize().ok()?; + if handle != monitor.handle { + return None; + } + let active = matches!( + state.get("screensaver-active").map(unbox), + Some(Value::Bool(true)) + ); + let session_state = state + .get("session-state") + .and_then(|value| crate::kwin::integer_from_variant(unbox(value))) + .unwrap_or(0); + Some((handle, active, session_state)) + } + + fn unbox(value: &OwnedValue) -> &Value<'_> { + let mut value = crate::kwin::value_of_owned(value); + while let Value::Value(inner) = value { + value = inner; + } + value + } + + async fn create_monitor(conn: &Connection) -> Option { + let owner_reply = call( + conn, + BUS_NAME, + BUS_PATH, + BUS_NAME, + "GetNameOwner", + &(PORTAL_DESTINATION,), + SHORT_TIMEOUT, + ) + .await?; + let owner: OwnedUniqueName = owner_reply.body().deserialize().ok()?; + let unique_name = conn.unique_name()?.to_string(); + let token = mint_token("fluxer_sm"); + let session_token = mint_token("fluxer_sm_session"); + let namespace = request_path(&unique_name, ""); + let rule = MatchRule::builder() + .msg_type(MessageType::Signal) + .sender(PORTAL_DESTINATION) + .and_then(|rule| rule.interface(REQUEST_INTERFACE)) + .and_then(|rule| rule.member("Response")) + .and_then(|rule| rule.path_namespace(namespace.trim_end_matches('/').to_string())) + .map(|rule| rule.build()); + let mut responses = subscribe(conn, rule).await.ok()?; + let mut options: HashMap<&str, Value<'_>> = HashMap::new(); + options.insert("handle_token", Value::new(token.as_str())); + options.insert("session_handle_token", Value::new(session_token.as_str())); + let reply = call( + conn, + PORTAL_DESTINATION, + PORTAL_PATH, + INHIBIT_INTERFACE, + "CreateMonitor", + &("", options), + SETUP_TIMEOUT, + ) + .await?; + let request = reply + .body() + .deserialize::() + .ok() + .map(|path| path.to_string()) + .unwrap_or_else(|| request_path(&unique_name, &token)); + let response = async { + while let Some(Ok(message)) = responses.next().await { + let header = message.header(); + if header.sender() != Some(owner.inner()) + || header.path().map(|path| path.as_str()) != Some(request.as_str()) + { + continue; + } + return message.body().deserialize::<(u32, Results)>().ok(); + } + None + } + .or(async { + Timer::after(SETUP_TIMEOUT).await; + None + }) + .await; + let (code, results) = response?; + if code != 0 { + return None; + } + let handle = match results.get("session_handle").map(unbox)? { + Value::Str(path) => OwnedObjectPath::try_from(path.as_str()).ok()?, + Value::ObjectPath(path) => OwnedObjectPath::from(path.to_owned()), + _ => return None, + }; + Some(Monitor { owner, handle }) + } + + async fn call( + conn: &Connection, + destination: &str, + path: &str, + interface: &str, + method: &str, + body: &B, + timeout: Duration, + ) -> Option + where + B: zbus::export::serde::Serialize + DynamicType, + { + async { + conn.call_method(Some(destination), path, Some(interface), method, body) + .await + .ok() + } + .or(async { + Timer::after(timeout).await; + None + }) + .await + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn lock_tracker_reports_only_transitions() { + let mut tracker = LockTracker::default(); + assert_eq!(tracker.update(false), None); + assert_eq!(tracker.update(true), Some(MonitorEvent::ScreenLocked)); + assert_eq!(tracker.update(true), None); + assert_eq!(tracker.update(false), Some(MonitorEvent::ScreenUnlocked)); + assert_eq!(tracker.update(false), None); + } +} diff --git a/fluxer_desktop/native/linux-screen-capture/src/portal.rs b/fluxer_desktop/native/linux-screen-capture/src/portal.rs index ab5089b70..b0ef7fa92 100644 --- a/fluxer_desktop/native/linux-screen-capture/src/portal.rs +++ b/fluxer_desktop/native/linux-screen-capture/src/portal.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::env; use std::os::fd::OwnedFd; +use std::path::Path; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, mpsc}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; @@ -27,7 +28,8 @@ pub const REGISTRY_INTERFACE: &str = "org.freedesktop.host.portal.Registry"; pub const REQUEST_TIMEOUT: Duration = Duration::from_secs(5 * 60); const SIGNAL_POLL_INTERVAL: Duration = Duration::from_millis(200); const MIN_PORTAL_VERSION: u32 = 4; -const DESKTOP_ENTRY_ID_ENV: &str = "FLUXER_LINUX_DESKTOP_ENTRY_ID"; +const PORTAL_APP_ID_ENV: &str = "FLUXER_LINUX_PORTAL_APP_ID"; +const FLATPAK_INFO_PATH: &str = "/.flatpak-info"; pub const CURSOR_MODE_HIDDEN: u32 = 1; pub const CURSOR_MODE_EMBEDDED: u32 = 2; @@ -198,14 +200,17 @@ fn normalize_desktop_entry_app_id(value: &str) -> Option { valid.then(|| app_id.to_string()) } -fn configured_desktop_entry_app_id() -> Option { - env::var(DESKTOP_ENTRY_ID_ENV) +fn configured_portal_app_id() -> Option { + if Path::new(FLATPAK_INFO_PATH).exists() { + return None; + } + env::var(PORTAL_APP_ID_ENV) .ok() .and_then(|value| normalize_desktop_entry_app_id(&value)) } fn register_portal_app_id(conn: &BlockingConnection) { - let Some(app_id) = configured_desktop_entry_app_id() else { + let Some(app_id) = configured_portal_app_id() else { return; }; let Ok(proxy) = BlockingProxy::new(conn, PORTAL_DESTINATION, PORTAL_PATH, REGISTRY_INTERFACE) diff --git a/fluxer_desktop/native/macos-input-hook/index.d.ts b/fluxer_desktop/native/macos-input-hook/index.d.ts index 7538a7d8d..fe16a60d7 100644 --- a/fluxer_desktop/native/macos-input-hook/index.d.ts +++ b/fluxer_desktop/native/macos-input-hook/index.d.ts @@ -19,26 +19,6 @@ export type InputEvent = metaKey: boolean; x?: number; y?: number; - } - | { - type: 'mousemove'; - x: number; - y: number; - ctrlKey: boolean; - altKey: boolean; - shiftKey: boolean; - metaKey: boolean; - } - | { - type: 'wheel'; - x?: number; - y?: number; - deltaX: number; - deltaY: number; - ctrlKey: boolean; - altKey: boolean; - shiftKey: boolean; - metaKey: boolean; }; export declare class InputHook { diff --git a/fluxer_desktop/native/macos-input-hook/src/modifiers.rs b/fluxer_desktop/native/macos-input-hook/src/modifiers.rs index 8b102e9d7..6a033743a 100644 --- a/fluxer_desktop/native/macos-input-hook/src/modifiers.rs +++ b/fluxer_desktop/native/macos-input-hook/src/modifiers.rs @@ -5,6 +5,15 @@ pub const CONTROL_MASK: u64 = 1 << 18; pub const ALTERNATE_MASK: u64 = 1 << 19; pub const COMMAND_MASK: u64 = 1 << 20; +const DEVICE_LEFT_CONTROL_MASK: u64 = 0x0000_0001; +const DEVICE_LEFT_SHIFT_MASK: u64 = 0x0000_0002; +const DEVICE_RIGHT_SHIFT_MASK: u64 = 0x0000_0004; +const DEVICE_LEFT_COMMAND_MASK: u64 = 0x0000_0008; +const DEVICE_RIGHT_COMMAND_MASK: u64 = 0x0000_0010; +const DEVICE_LEFT_ALTERNATE_MASK: u64 = 0x0000_0020; +const DEVICE_RIGHT_ALTERNATE_MASK: u64 = 0x0000_0040; +const DEVICE_RIGHT_CONTROL_MASK: u64 = 0x0000_2000; + const LEFT_SHIFT_KEYCODE: u16 = 0x38; const RIGHT_SHIFT_KEYCODE: u16 = 0x3c; const LEFT_CONTROL_KEYCODE: u16 = 0x3b; @@ -31,14 +40,64 @@ pub fn from_flags(flags: u64) -> Modifiers { } } -pub fn modifier_key_down_from_flags(keycode: u16, flags: u64) -> Option { - match keycode { - LEFT_SHIFT_KEYCODE | RIGHT_SHIFT_KEYCODE => Some((flags & SHIFT_MASK) != 0), - LEFT_CONTROL_KEYCODE | RIGHT_CONTROL_KEYCODE => Some((flags & CONTROL_MASK) != 0), - LEFT_OPTION_KEYCODE | RIGHT_OPTION_KEYCODE => Some((flags & ALTERNATE_MASK) != 0), - LEFT_COMMAND_KEYCODE | RIGHT_COMMAND_KEYCODE => Some((flags & COMMAND_MASK) != 0), - _ => None, +struct ModifierSide { + group_mask: u64, + side_mask: u64, + other_side_mask: u64, +} + +fn modifier_side(keycode: u16) -> Option { + let (group_mask, side_mask, other_side_mask) = match keycode { + LEFT_SHIFT_KEYCODE => (SHIFT_MASK, DEVICE_LEFT_SHIFT_MASK, DEVICE_RIGHT_SHIFT_MASK), + RIGHT_SHIFT_KEYCODE => (SHIFT_MASK, DEVICE_RIGHT_SHIFT_MASK, DEVICE_LEFT_SHIFT_MASK), + LEFT_CONTROL_KEYCODE => ( + CONTROL_MASK, + DEVICE_LEFT_CONTROL_MASK, + DEVICE_RIGHT_CONTROL_MASK, + ), + RIGHT_CONTROL_KEYCODE => ( + CONTROL_MASK, + DEVICE_RIGHT_CONTROL_MASK, + DEVICE_LEFT_CONTROL_MASK, + ), + LEFT_OPTION_KEYCODE => ( + ALTERNATE_MASK, + DEVICE_LEFT_ALTERNATE_MASK, + DEVICE_RIGHT_ALTERNATE_MASK, + ), + RIGHT_OPTION_KEYCODE => ( + ALTERNATE_MASK, + DEVICE_RIGHT_ALTERNATE_MASK, + DEVICE_LEFT_ALTERNATE_MASK, + ), + LEFT_COMMAND_KEYCODE => ( + COMMAND_MASK, + DEVICE_LEFT_COMMAND_MASK, + DEVICE_RIGHT_COMMAND_MASK, + ), + RIGHT_COMMAND_KEYCODE => ( + COMMAND_MASK, + DEVICE_RIGHT_COMMAND_MASK, + DEVICE_LEFT_COMMAND_MASK, + ), + _ => return None, + }; + Some(ModifierSide { + group_mask, + side_mask, + other_side_mask, + }) +} + +pub fn modifier_key_down_from_flags(keycode: u16, flags: u64, was_held: bool) -> Option { + let side = modifier_side(keycode)?; + if flags & side.group_mask == 0 { + return Some(false); } + if flags & (side.side_mask | side.other_side_mask) != 0 { + return Some(flags & side.side_mask != 0); + } + Some(!was_held) } #[cfg(test)] @@ -85,19 +144,84 @@ mod tests { } #[test] - fn modifier_key_down_uses_matching_aggregate_flag() { + fn modifier_key_down_uses_the_device_side_bits() { + let both_shifts = SHIFT_MASK | DEVICE_LEFT_SHIFT_MASK | DEVICE_RIGHT_SHIFT_MASK; assert_eq!( - modifier_key_down_from_flags(LEFT_SHIFT_KEYCODE, SHIFT_MASK), + modifier_key_down_from_flags(LEFT_SHIFT_KEYCODE, both_shifts, false), Some(true) ); + let right_only = SHIFT_MASK | DEVICE_RIGHT_SHIFT_MASK; assert_eq!( - modifier_key_down_from_flags(RIGHT_SHIFT_KEYCODE, 0), + modifier_key_down_from_flags(LEFT_SHIFT_KEYCODE, right_only, true), Some(false) ); assert_eq!( - modifier_key_down_from_flags(LEFT_COMMAND_KEYCODE, COMMAND_MASK), + modifier_key_down_from_flags(RIGHT_SHIFT_KEYCODE, right_only, false), Some(true) ); - assert_eq!(modifier_key_down_from_flags(0x39, 0), None); + assert_eq!( + modifier_key_down_from_flags(RIGHT_SHIFT_KEYCODE, 0, true), + Some(false) + ); + } + + #[test] + fn releasing_one_side_while_the_other_is_held_is_a_keyup_for_every_group() { + let cases = [ + ( + LEFT_CONTROL_KEYCODE, + RIGHT_CONTROL_KEYCODE, + CONTROL_MASK, + DEVICE_LEFT_CONTROL_MASK, + DEVICE_RIGHT_CONTROL_MASK, + ), + ( + LEFT_OPTION_KEYCODE, + RIGHT_OPTION_KEYCODE, + ALTERNATE_MASK, + DEVICE_LEFT_ALTERNATE_MASK, + DEVICE_RIGHT_ALTERNATE_MASK, + ), + ( + LEFT_COMMAND_KEYCODE, + RIGHT_COMMAND_KEYCODE, + COMMAND_MASK, + DEVICE_LEFT_COMMAND_MASK, + DEVICE_RIGHT_COMMAND_MASK, + ), + ]; + for (left, right, group, left_bit, right_bit) in cases { + assert_eq!( + modifier_key_down_from_flags(left, group | left_bit, false), + Some(true) + ); + assert_eq!( + modifier_key_down_from_flags(right, group | left_bit | right_bit, false), + Some(true) + ); + assert_eq!( + modifier_key_down_from_flags(left, group | right_bit, true), + Some(false) + ); + assert_eq!(modifier_key_down_from_flags(right, 0, true), Some(false)); + } + } + + #[test] + fn missing_device_bits_fall_back_to_toggling_the_held_state() { + assert_eq!( + modifier_key_down_from_flags(RIGHT_OPTION_KEYCODE, ALTERNATE_MASK, false), + Some(true) + ); + assert_eq!( + modifier_key_down_from_flags(RIGHT_OPTION_KEYCODE, ALTERNATE_MASK, true), + Some(false) + ); + } + + #[test] + fn non_modifier_keycodes_are_not_decided() { + assert_eq!(modifier_key_down_from_flags(0x39, 0, false), None); + assert_eq!(modifier_key_down_from_flags(0x00, SHIFT_MASK, true), None); } } diff --git a/fluxer_desktop/native/macos-input-hook/src/mouse.rs b/fluxer_desktop/native/macos-input-hook/src/mouse.rs index 6d0538d2b..fb90b2cac 100644 --- a/fluxer_desktop/native/macos-input-hook/src/mouse.rs +++ b/fluxer_desktop/native/macos-input-hook/src/mouse.rs @@ -7,16 +7,11 @@ pub enum CgEventType { LeftMouseUp = 2, RightMouseDown = 3, RightMouseUp = 4, - MouseMoved = 5, - LeftMouseDragged = 6, - RightMouseDragged = 7, KeyDown = 10, KeyUp = 11, FlagsChanged = 12, - ScrollWheel = 22, OtherMouseDown = 25, OtherMouseUp = 26, - OtherMouseDragged = 27, } impl CgEventType { @@ -26,16 +21,11 @@ impl CgEventType { 2 => Self::LeftMouseUp, 3 => Self::RightMouseDown, 4 => Self::RightMouseUp, - 5 => Self::MouseMoved, - 6 => Self::LeftMouseDragged, - 7 => Self::RightMouseDragged, 10 => Self::KeyDown, 11 => Self::KeyUp, 12 => Self::FlagsChanged, - 22 => Self::ScrollWheel, 25 => Self::OtherMouseDown, 26 => Self::OtherMouseUp, - 27 => Self::OtherMouseDragged, _ => return None, }) } @@ -117,13 +107,15 @@ mod tests { assert!(is_down(CgEventType::LeftMouseDown)); assert!(!is_down(CgEventType::LeftMouseUp)); assert!(is_down(CgEventType::OtherMouseDown)); - assert!(!is_down(CgEventType::MouseMoved)); + assert!(!is_down(CgEventType::KeyDown)); } #[test] fn from_u32_maps_known_event_types() { assert_eq!(Some(CgEventType::LeftMouseDown), CgEventType::from_u32(1)); - assert_eq!(Some(CgEventType::ScrollWheel), CgEventType::from_u32(22)); + assert_eq!(Some(CgEventType::OtherMouseUp), CgEventType::from_u32(26)); + assert_eq!(None, CgEventType::from_u32(5)); + assert_eq!(None, CgEventType::from_u32(22)); assert_eq!(None, CgEventType::from_u32(999)); } } diff --git a/fluxer_desktop/native/macos-input-hook/src/platform.rs b/fluxer_desktop/native/macos-input-hook/src/platform.rs index cc1068412..d466db319 100644 --- a/fluxer_desktop/native/macos-input-hook/src/platform.rs +++ b/fluxer_desktop/native/macos-input-hook/src/platform.rs @@ -1,9 +1,10 @@ // SPDX-License-Identifier: AGPL-3.0-or-later +use std::collections::BTreeSet; use std::ffi::c_void; use std::ptr; use std::sync::{ - Arc, Mutex, + Arc, Mutex, MutexGuard, atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering}, mpsc::{Receiver, Sender, channel}, }; @@ -33,14 +34,12 @@ unsafe extern "C" { fn CGEventTapEnable(tap: CFMachPortRef, enable: bool); } -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] enum InputEventKind { KeyDown, KeyUp, MouseDown, MouseUp, - MouseMove, - Wheel, } #[derive(Debug)] @@ -50,8 +49,6 @@ pub struct InputEventPayload { keycode: u32, key_name: String, button: u8, - delta_x: i32, - delta_y: i32, x: i32, y: i32, has_xy: bool, @@ -66,8 +63,6 @@ impl ToNapiValue for InputEventPayload { InputEventKind::KeyUp => "keyup", InputEventKind::MouseDown => "mousedown", InputEventKind::MouseUp => "mouseup", - InputEventKind::MouseMove => "mousemove", - InputEventKind::Wheel => "wheel", }; obj.set("type", kind_str)?; obj.set("ctrlKey", value.mods.ctrl)?; @@ -87,18 +82,6 @@ impl ToNapiValue for InputEventPayload { obj.set("y", value.y)?; } } - InputEventKind::MouseMove => { - obj.set("x", value.x)?; - obj.set("y", value.y)?; - } - InputEventKind::Wheel => { - obj.set("deltaX", value.delta_x)?; - obj.set("deltaY", value.delta_y)?; - if value.has_xy { - obj.set("x", value.x)?; - obj.set("y", value.y)?; - } - } } unsafe { as ToNapiValue>::to_napi_value(raw_env, obj) } } @@ -112,12 +95,9 @@ type EventTsfn = Arc< Status, false, true, - EVENT_QUEUE_LIMIT, >, >; -const EVENT_QUEUE_LIMIT: usize = 1024; - struct WorkerState { run_loop: CFRunLoop, join: JoinHandle<()>, @@ -138,7 +118,6 @@ impl InputHook { .build_threadsafe_function::() .weak::() .callee_handled::() - .max_queue_size::() .build() .map_err(|err| { Error::new( @@ -235,11 +214,6 @@ const EVENTS_OF_INTEREST: &[CGEventType] = &[ CGEventType::RightMouseUp, CGEventType::OtherMouseDown, CGEventType::OtherMouseUp, - CGEventType::MouseMoved, - CGEventType::LeftMouseDragged, - CGEventType::RightMouseDragged, - CGEventType::OtherMouseDragged, - CGEventType::ScrollWheel, ]; const CAPS_LOCK_KEYCODE: u16 = 0x39; @@ -253,6 +227,7 @@ fn worker_main(tsfn: EventTsfn, loop_tx: Sender InputEventPayload { keycode: u32::from(CAPS_LOCK_KEYCODE), key_name: keymap::keycode_name_or_fallback(CAPS_LOCK_KEYCODE), button: 0, - delta_x: 0, - delta_y: 0, + x: 0, + y: 0, + has_xy: false, + } +} + +#[derive(Default)] +struct HeldInputs { + keys: BTreeSet, + buttons: BTreeSet, +} + +impl HeldInputs { + fn track(&mut self, payload: &InputEventPayload) { + #[allow(clippy::cast_possible_truncation)] + let keycode = payload.keycode as u16; + match payload.kind { + InputEventKind::KeyDown => { + self.keys.insert(keycode); + } + InputEventKind::KeyUp => { + self.keys.remove(&keycode); + } + InputEventKind::MouseDown => { + self.buttons.insert(payload.button); + } + InputEventKind::MouseUp => { + self.buttons.remove(&payload.button); + } + } + } + + fn take_releases(&mut self) -> Vec { + let mods = modifiers::from_flags(0); + let keys = std::mem::take(&mut self.keys) + .into_iter() + .map(|keycode| key_payload(InputEventKind::KeyUp, keycode, mods)); + let buttons = + std::mem::take(&mut self.buttons) + .into_iter() + .map(|button| InputEventPayload { + kind: InputEventKind::MouseUp, + mods, + keycode: 0, + key_name: String::new(), + button, + x: 0, + y: 0, + has_xy: false, + }); + keys.chain(buttons).collect() + } +} + +fn key_payload(kind: InputEventKind, keycode: u16, mods: Modifiers) -> InputEventPayload { + InputEventPayload { + kind, + mods, + keycode: u32::from(keycode), + key_name: keymap::keycode_name_or_fallback(keycode), + button: 0, x: 0, y: 0, has_xy: false, @@ -332,6 +366,7 @@ struct EventDispatcher { tap_ref: Arc>, last_flags: Arc, caps_lock_via_hid: Arc, + held: Mutex, } impl EventDispatcher { @@ -344,6 +379,7 @@ impl EventDispatcher { match event_type { CGEventType::TapDisabledByTimeout | CGEventType::TapDisabledByUserInput => { self.reenable_tap(); + self.release_all(); return CallbackResult::Keep; } _ => {} @@ -380,7 +416,9 @@ impl EventDispatcher { self.send_key(InputEventKind::KeyUp, keycode, mods); return CallbackResult::Keep; } - let Some(is_down) = modifiers::modifier_key_down_from_flags(keycode, flags.bits()) + let was_held = self.held().keys.contains(&keycode); + let Some(is_down) = + modifiers::modifier_key_down_from_flags(keycode, flags.bits(), was_held) else { return CallbackResult::Keep; }; @@ -414,8 +452,6 @@ impl EventDispatcher { keycode: 0, key_name: String::new(), button: b, - delta_x: 0, - delta_y: 0, x: point.x as i32, y: point.y as i32, has_xy: true, @@ -423,74 +459,37 @@ impl EventDispatcher { self.send(payload); } } - CgEventType::MouseMoved - | CgEventType::LeftMouseDragged - | CgEventType::RightMouseDragged - | CgEventType::OtherMouseDragged => { - let point = event.location(); - #[allow(clippy::cast_possible_truncation)] - let payload = InputEventPayload { - kind: InputEventKind::MouseMove, - mods, - keycode: 0, - key_name: String::new(), - button: 0, - delta_x: 0, - delta_y: 0, - x: point.x as i32, - y: point.y as i32, - has_xy: true, - }; - self.send(payload); - } - CgEventType::ScrollWheel => { - let dy = event.get_integer_value_field(EventField::SCROLL_WHEEL_EVENT_DELTA_AXIS_1) - as i32; - let dx = event.get_integer_value_field(EventField::SCROLL_WHEEL_EVENT_DELTA_AXIS_2) - as i32; - let point = event.location(); - #[allow(clippy::cast_possible_truncation)] - let payload = InputEventPayload { - kind: InputEventKind::Wheel, - mods, - keycode: 0, - key_name: String::new(), - button: 0, - delta_x: dx, - delta_y: dy, - x: point.x as i32, - y: point.y as i32, - has_xy: true, - }; - self.send(payload); - } } CallbackResult::Keep } fn send_key(&self, kind: InputEventKind, keycode: u16, mods: Modifiers) { - let payload = InputEventPayload { - kind, - mods, - keycode: u32::from(keycode), - key_name: keymap::keycode_name_or_fallback(keycode), - button: 0, - delta_x: 0, - delta_y: 0, - x: 0, - y: 0, - has_xy: false, - }; - self.send(payload); + self.send(key_payload(kind, keycode, mods)); + } + + fn held(&self) -> MutexGuard<'_, HeldInputs> { + self.held + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()) } fn send(&self, payload: InputEventPayload) { + self.held().track(&payload); let _ = self .tsfn .call(payload, ThreadsafeFunctionCallMode::NonBlocking); } + fn release_all(&self) { + let releases = self.held().take_releases(); + for payload in releases { + let _ = self + .tsfn + .call(payload, ThreadsafeFunctionCallMode::NonBlocking); + } + } + fn reenable_tap(&self) { let tap: CFMachPortRef = self.tap_ref.load(Ordering::Acquire).cast(); if !tap.is_null() { @@ -502,3 +501,43 @@ impl EventDispatcher { pub fn has_accessibility_permission() -> bool { objc2_core_graphics::CGPreflightListenEventAccess() } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn held_inputs_release_every_pressed_key_and_button_once() { + let mods = modifiers::from_flags(0); + let mut held = HeldInputs::default(); + held.track(&key_payload(InputEventKind::KeyDown, 0x31, mods)); + held.track(&key_payload(InputEventKind::KeyDown, 0x3b, mods)); + held.track(&key_payload(InputEventKind::KeyDown, 0x00, mods)); + held.track(&key_payload(InputEventKind::KeyUp, 0x00, mods)); + held.track(&InputEventPayload { + kind: InputEventKind::MouseDown, + mods, + keycode: 0, + key_name: String::new(), + button: 3, + x: 0, + y: 0, + has_xy: true, + }); + let releases = held.take_releases(); + let summary: Vec<(InputEventKind, u32, u8)> = releases + .iter() + .map(|payload| (payload.kind, payload.keycode, payload.button)) + .collect(); + assert_eq!( + summary, + vec![ + (InputEventKind::KeyUp, 0x31, 0), + (InputEventKind::KeyUp, 0x3b, 0), + (InputEventKind::MouseUp, 0, 3), + ] + ); + assert!(releases.iter().all(|payload| !payload.mods.ctrl)); + assert!(held.take_releases().is_empty()); + } +} diff --git a/fluxer_desktop/native/rust/src/input/modifiers.rs b/fluxer_desktop/native/rust/src/input/modifiers.rs index 3011e3694..ea5a1eb5f 100644 --- a/fluxer_desktop/native/rust/src/input/modifiers.rs +++ b/fluxer_desktop/native/rust/src/input/modifiers.rs @@ -63,6 +63,28 @@ pub mod windows { meta: ((lwin_state | rwin_state) & HIGH_BIT) != 0, } } + + pub fn with_pressed_key(modifiers: Modifiers, vk: u16) -> Modifiers { + match vk { + 0x10 | 0xa0 | 0xa1 => Modifiers { + shift: true, + ..modifiers + }, + 0x11 | 0xa2 | 0xa3 => Modifiers { + ctrl: true, + ..modifiers + }, + 0x12 | 0xa4 | 0xa5 => Modifiers { + alt: true, + ..modifiers + }, + 0x5b | 0x5c => Modifiers { + meta: true, + ..modifiers + }, + _ => modifiers, + } + } } #[cfg(test)] @@ -196,6 +218,72 @@ mod tests { ); } + #[test] + fn windows_pressed_modifier_adds_its_own_flag() { + let ctrl = Modifiers { + ctrl: true, + ..Modifiers::default() + }; + assert_eq!( + Modifiers { + ctrl: true, + shift: true, + ..Modifiers::default() + }, + windows::with_pressed_key(ctrl, 0xa0) + ); + assert_eq!( + Modifiers { + shift: true, + ..Modifiers::default() + }, + windows::with_pressed_key(Modifiers::default(), 0xa1) + ); + assert_eq!(ctrl, windows::with_pressed_key(Modifiers::default(), 0xa3)); + assert_eq!( + Modifiers { + alt: true, + ..Modifiers::default() + }, + windows::with_pressed_key(Modifiers::default(), 0xa5) + ); + assert_eq!( + Modifiers { + meta: true, + ..Modifiers::default() + }, + windows::with_pressed_key(Modifiers::default(), 0x5c) + ); + assert_eq!( + Modifiers { + ctrl: true, + alt: true, + shift: true, + meta: false, + }, + windows::with_pressed_key( + windows::with_pressed_key( + windows::with_pressed_key(Modifiers::default(), 0x10), + 0x11 + ), + 0x12 + ) + ); + } + + #[test] + fn windows_pressed_non_modifier_keeps_sampled_flags() { + let ctrl = Modifiers { + ctrl: true, + ..Modifiers::default() + }; + assert_eq!(ctrl, windows::with_pressed_key(ctrl, 0x41)); + assert_eq!( + Modifiers::default(), + windows::with_pressed_key(Modifiers::default(), 0x14) + ); + } + #[test] fn windows_either_win_key_sets_meta() { assert!(windows::from_sampled(0, 0, 0, windows::HIGH_BIT, 0).meta); diff --git a/fluxer_desktop/native/rust/src/input/windows_mouse.rs b/fluxer_desktop/native/rust/src/input/windows_mouse.rs index 73e92ee72..85d98cd8d 100644 --- a/fluxer_desktop/native/rust/src/input/windows_mouse.rs +++ b/fluxer_desktop/native/rust/src/input/windows_mouse.rs @@ -12,23 +12,16 @@ pub const WM_XBUTTONDOWN: u32 = 0x020b; pub const WM_XBUTTONUP: u32 = 0x020c; pub const WM_MOUSEHWHEEL: u32 = 0x020e; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Axis { - Vertical, - Horizontal, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Action { Button { down: bool, button: u8 }, - Move, - Wheel { axis: Axis, delta: i16 }, + Activity, Ignored, } -pub fn classify(msg: u32, xbutton: u16, wheel_delta: i16) -> Action { +pub fn classify(msg: u32, xbutton: u16) -> Action { match msg { - WM_MOUSEMOVE => Action::Move, + WM_MOUSEMOVE | WM_MOUSEWHEEL | WM_MOUSEHWHEEL => Action::Activity, WM_LBUTTONDOWN => Action::Button { down: true, button: 0, @@ -75,14 +68,6 @@ pub fn classify(msg: u32, xbutton: u16, wheel_delta: i16) -> Action { }, _ => Action::Ignored, }, - WM_MOUSEWHEEL => Action::Wheel { - axis: Axis::Vertical, - delta: wheel_delta, - }, - WM_MOUSEHWHEEL => Action::Wheel { - axis: Axis::Horizontal, - delta: wheel_delta, - }, _ => Action::Ignored, } } @@ -98,21 +83,21 @@ mod tests { down: true, button: 0 }, - classify(WM_LBUTTONDOWN, 0, 0) + classify(WM_LBUTTONDOWN, 0) ); assert_eq!( Action::Button { down: false, button: 2 }, - classify(WM_RBUTTONUP, 0, 0) + classify(WM_RBUTTONUP, 0) ); assert_eq!( Action::Button { down: true, button: 1 }, - classify(WM_MBUTTONDOWN, 0, 0) + classify(WM_MBUTTONDOWN, 0) ); } @@ -123,50 +108,23 @@ mod tests { down: true, button: 3 }, - classify(WM_XBUTTONDOWN, 1, 0) + classify(WM_XBUTTONDOWN, 1) ); assert_eq!( Action::Button { down: false, button: 4 }, - classify(WM_XBUTTONUP, 2, 0) + classify(WM_XBUTTONUP, 2) ); - assert_eq!(Action::Ignored, classify(WM_XBUTTONDOWN, 7, 0)); + assert_eq!(Action::Ignored, classify(WM_XBUTTONDOWN, 7)); } #[test] - fn vertical_wheel_preserves_signed_delta() { - assert_eq!( - Action::Wheel { - axis: Axis::Vertical, - delta: 120, - }, - classify(WM_MOUSEWHEEL, 0, 120) - ); - assert_eq!( - Action::Wheel { - axis: Axis::Vertical, - delta: -240, - }, - classify(WM_MOUSEWHEEL, 0, -240) - ); - } - - #[test] - fn horizontal_wheel_reports_horizontal_axis() { - assert_eq!( - Action::Wheel { - axis: Axis::Horizontal, - delta: 120, - }, - classify(WM_MOUSEHWHEEL, 0, 120) - ); - } - - #[test] - fn mouse_move_and_unknown_messages_distinguish_move_vs_ignored() { - assert_eq!(Action::Move, classify(WM_MOUSEMOVE, 0, 0)); - assert_eq!(Action::Ignored, classify(0xdead, 0, 0)); + fn move_and_wheel_are_activity_and_unknown_messages_are_ignored() { + assert_eq!(Action::Activity, classify(WM_MOUSEMOVE, 0)); + assert_eq!(Action::Activity, classify(WM_MOUSEWHEEL, 0)); + assert_eq!(Action::Activity, classify(WM_MOUSEHWHEEL, 0)); + assert_eq!(Action::Ignored, classify(0xdead, 0)); } } diff --git a/fluxer_desktop/native/rust/src/linux_portals/decide.rs b/fluxer_desktop/native/rust/src/linux_portals/decide.rs deleted file mode 100644 index df8f74110..000000000 --- a/fluxer_desktop/native/rust/src/linux_portals/decide.rs +++ /dev/null @@ -1,232 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PortalEntry { - pub id: String, - pub description: String, - pub preferred_trigger: String, -} - -impl PortalEntry { - pub fn new(id: &str, description: &str) -> Self { - Self { - id: id.to_owned(), - description: description.to_owned(), - preferred_trigger: String::new(), - } - } - - pub fn with_trigger(id: &str, description: &str, preferred_trigger: &str) -> Self { - Self { - id: id.to_owned(), - description: description.to_owned(), - preferred_trigger: preferred_trigger.to_owned(), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PortalBoundShortcut { - pub id: String, - pub description: String, - pub trigger_description: String, -} - -impl PortalBoundShortcut { - pub fn new(id: &str, description: &str, trigger_description: &str) -> Self { - Self { - id: id.to_owned(), - description: description.to_owned(), - trigger_description: trigger_description.to_owned(), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum BindReason { - NoPersistedShortcuts, - NewIdsAdded, -} - -impl BindReason { - pub fn name(self) -> &'static str { - match self { - Self::NoPersistedShortcuts => "no-persisted-shortcuts", - Self::NewIdsAdded => "new-ids-added", - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ConfigureAction { - Reuse(Vec), - Bind(BindReason), -} - -pub fn decide(requested: &[PortalEntry], persisted: &[PortalBoundShortcut]) -> ConfigureAction { - if persisted.is_empty() { - return ConfigureAction::Bind(BindReason::NoPersistedShortcuts); - } - if requested - .iter() - .any(|entry| !has_persisted_shortcut(&entry.id, persisted)) - { - return ConfigureAction::Bind(BindReason::NewIdsAdded); - } - ConfigureAction::Reuse(persisted.to_vec()) -} - -pub fn has_persisted_shortcut(id: &str, persisted: &[PortalBoundShortcut]) -> bool { - persisted.iter().any(|shortcut| shortcut.id == id) -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum PathError { - NoSpaceLeft, -} - -pub fn request_path( - unique_bus_name: &str, - handle_token: &str, - capacity: usize, -) -> Result { - let trimmed = unique_bus_name.strip_prefix(':').unwrap_or(unique_bus_name); - let normalized = trimmed.replace('.', "_"); - let path = format!("/org/freedesktop/portal/desktop/request/{normalized}/{handle_token}"); - if path.len() > capacity { - Err(PathError::NoSpaceLeft) - } else { - Ok(path) - } -} - -pub fn stable_key(entries: &[PortalEntry]) -> String { - let mut sorted = entries.to_vec(); - sorted.sort_by(|a, b| a.id.cmp(&b.id)); - let mut out = String::from("["); - for (index, entry) in sorted.iter().enumerate() { - if index != 0 { - out.push(','); - } - out.push_str("{id="); - out.push_str(&entry.id); - out.push_str(",desc="); - out.push_str(&entry.description); - out.push_str(",trig="); - out.push_str(&entry.preferred_trigger); - out.push('}'); - } - out.push(']'); - out -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn decide_empty_persisted_set_requests_first_time_bind() { - let action = decide(&[PortalEntry::new("mute", "Toggle mute")], &[]); - assert_eq!( - ConfigureAction::Bind(BindReason::NoPersistedShortcuts), - action - ); - } - - #[test] - fn decide_matching_ids_on_restart_reuses_persisted_bindings() { - let persisted = [ - PortalBoundShortcut::new("mute", "Toggle mute", "Ctrl+Shift+M"), - PortalBoundShortcut::new("deafen", "Toggle deafen", "Ctrl+Shift+D"), - ]; - let requested = [ - PortalEntry::new("mute", "Toggle mute"), - PortalEntry::new("deafen", "Toggle deafen"), - ]; - let action = decide(&requested, &persisted); - assert!(matches!(action, ConfigureAction::Reuse(shortcuts) if shortcuts.len() == 2)); - } - - #[test] - fn decide_new_id_added_since_last_launch_triggers_fresh_bind() { - let persisted = [PortalBoundShortcut::new( - "mute", - "Toggle mute", - "Ctrl+Shift+M", - )]; - let requested = [ - PortalEntry::new("mute", "Toggle mute"), - PortalEntry::new("push_to_talk", "Push to talk"), - ]; - assert_eq!( - ConfigureAction::Bind(BindReason::NewIdsAdded), - decide(&requested, &persisted) - ); - } - - #[test] - fn decide_removing_previously_persisted_id_does_not_rebind() { - let persisted = [ - PortalBoundShortcut::new("mute", "Toggle mute", "Ctrl+Shift+M"), - PortalBoundShortcut::new("deafen", "Toggle deafen", "Ctrl+Shift+D"), - ]; - let requested = [PortalEntry::new("mute", "Toggle mute")]; - assert!(matches!( - decide(&requested, &persisted), - ConfigureAction::Reuse(_) - )); - } - - #[test] - fn decide_empty_requested_with_empty_persisted_reports_no_persisted() { - assert_eq!( - ConfigureAction::Bind(BindReason::NoPersistedShortcuts), - decide(&[], &[]) - ); - } - - #[test] - fn request_path_well_known_shape_per_portal_spec() { - assert_eq!( - "/org/freedesktop/portal/desktop/request/1_42/fluxer_gs_create_xyz", - request_path(":1.42", "fluxer_gs_create_xyz", 256).unwrap() - ); - } - - #[test] - fn request_path_handles_unique_names_without_leading_colon() { - assert_eq!( - "/org/freedesktop/portal/desktop/request/1_0_7/tok", - request_path("1.0.7", "tok", 256).unwrap() - ); - } - - #[test] - fn request_path_rejects_too_small_buffer() { - assert_eq!( - Err(PathError::NoSpaceLeft), - request_path(":1.42", "tok", 16) - ); - } - - #[test] - fn stable_key_sorts_by_id_and_includes_description_and_trigger() { - let key = stable_key(&[ - PortalEntry::new("mute", "Toggle mute"), - PortalEntry::with_trigger("deafen", "Toggle deafen", "Ctrl+D"), - ]); - assert_eq!( - "[{id=deafen,desc=Toggle deafen,trig=Ctrl+D},{id=mute,desc=Toggle mute,trig=}]", - key - ); - } - - #[test] - fn bind_reason_name_matches_js_side_strings() { - assert_eq!( - "no-persisted-shortcuts", - BindReason::NoPersistedShortcuts.name() - ); - assert_eq!("new-ids-added", BindReason::NewIdsAdded.name()); - } -} diff --git a/fluxer_desktop/native/rust/src/linux_portals/mod.rs b/fluxer_desktop/native/rust/src/linux_portals/mod.rs index 5f470d0d9..c2377002c 100644 --- a/fluxer_desktop/native/rust/src/linux_portals/mod.rs +++ b/fluxer_desktop/native/rust/src/linux_portals/mod.rs @@ -1,7 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -pub mod decide; pub mod pid_payload; -pub mod portal_snapshot; pub mod window_pid; pub mod x11_window_pid; diff --git a/fluxer_desktop/native/rust/src/linux_portals/portal_snapshot.rs b/fluxer_desktop/native/rust/src/linux_portals/portal_snapshot.rs deleted file mode 100644 index e0744ef00..000000000 --- a/fluxer_desktop/native/rust/src/linux_portals/portal_snapshot.rs +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -use std::sync::atomic::{AtomicU64, Ordering}; -use std::time::{SystemTime, UNIX_EPOCH}; - -use super::decide::{PortalBoundShortcut, PortalEntry, has_persisted_shortcut, stable_key}; - -static TOKEN_SEQ: AtomicU64 = AtomicU64::new(1); - -pub fn mint_token(prefix: &str) -> String { - let seq = TOKEN_SEQ.fetch_add(1, Ordering::Relaxed); - let ms = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|duration| duration.as_millis()) - .unwrap_or(0); - format!("{prefix}_{ms:x}_{seq:x}") -} - -pub fn compute_stable_key(entries: &[PortalEntry]) -> String { - stable_key(entries) -} - -pub fn merge_shortcut_snapshots( - persisted: &[PortalBoundShortcut], - bound: &[PortalBoundShortcut], -) -> Vec { - let mut out = persisted.to_vec(); - for shortcut in bound { - if !has_persisted_shortcut(&shortcut.id, &out) { - out.push(shortcut.clone()); - } - } - out -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::linux_portals::decide::{PortalBoundShortcut, PortalEntry}; - - #[test] - fn compute_stable_key_is_deterministic_across_orderings() { - let a = compute_stable_key(&[ - PortalEntry::new("mute", "Toggle mute"), - PortalEntry::new("deafen", "Toggle deafen"), - ]); - let b = compute_stable_key(&[ - PortalEntry::new("deafen", "Toggle deafen"), - PortalEntry::new("mute", "Toggle mute"), - ]); - assert_eq!(a, b); - } - - #[test] - fn mint_token_is_well_formed_and_monotonically_distinct_within_process() { - let a = mint_token("fluxer_gs_create"); - let b = mint_token("fluxer_gs_create"); - assert_ne!(a, b); - assert!(a.starts_with("fluxer_gs_create_")); - } - - #[test] - fn merge_shortcut_snapshots_preserves_existing_and_adds_newly_bound_ids() { - let persisted = [PortalBoundShortcut::new( - "mute", - "Toggle mute", - "Ctrl+Shift+M", - )]; - let bound = [PortalBoundShortcut::new( - "push_to_talk", - "Push to talk", - "Ctrl+Shift+Space", - )]; - let merged = merge_shortcut_snapshots(&persisted, &bound); - assert_eq!(2, merged.len()); - assert_eq!("mute", merged[0].id); - assert_eq!("push_to_talk", merged[1].id); - } -} diff --git a/fluxer_desktop/native/windows-input-hook/index.d.ts b/fluxer_desktop/native/windows-input-hook/index.d.ts index 5e9c118f5..37f95b4a4 100644 --- a/fluxer_desktop/native/windows-input-hook/index.d.ts +++ b/fluxer_desktop/native/windows-input-hook/index.d.ts @@ -4,6 +4,8 @@ export type InputEvent = | { type: 'keydown' | 'keyup'; keycode: number; + scanCode: number; + extended: boolean; keyName: string; ctrlKey: boolean; altKey: boolean; @@ -19,26 +21,6 @@ export type InputEvent = metaKey: boolean; x?: number; y?: number; - } - | { - type: 'mousemove'; - x: number; - y: number; - ctrlKey: boolean; - altKey: boolean; - shiftKey: boolean; - metaKey: boolean; - } - | { - type: 'wheel'; - x?: number; - y?: number; - deltaX: number; - deltaY: number; - ctrlKey: boolean; - altKey: boolean; - shiftKey: boolean; - metaKey: boolean; }; export declare class InputHook { diff --git a/fluxer_desktop/native/windows-input-hook/src/lib.rs b/fluxer_desktop/native/windows-input-hook/src/lib.rs index 333c7f6b6..c0b0ea94f 100644 --- a/fluxer_desktop/native/windows-input-hook/src/lib.rs +++ b/fluxer_desktop/native/windows-input-hook/src/lib.rs @@ -10,20 +10,17 @@ use napi::{ }; use napi_derive::napi; -const RING_CAPACITY: usize = 1024; const KEY_NAME_BUF: usize = 32; const LLKHF_EXTENDED_FLAG: u32 = 0x01; +const VK_PACKET: u32 = 0xe7; #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum EventKind { + #[default] KeyDown, KeyUp, MouseDown, MouseUp, - - #[default] - MouseMove, - Wheel, } impl EventKind { @@ -33,8 +30,6 @@ impl EventKind { Self::KeyUp => "keyup", Self::MouseDown => "mousedown", Self::MouseUp => "mouseup", - Self::MouseMove => "mousemove", - Self::Wheel => "wheel", } } } @@ -43,9 +38,9 @@ impl EventKind { pub struct QueuedEvent { pub kind: EventKind, pub keycode: u32, + pub scan_code: u32, + pub extended: bool, pub button: u8, - pub delta_x: i32, - pub delta_y: i32, pub x: i32, pub y: i32, pub has_xy: bool, @@ -62,6 +57,59 @@ impl QueuedEvent { let len = (self.key_name_len as usize).min(KEY_NAME_BUF); std::str::from_utf8(&self.key_name_buf[..len]).unwrap_or("") } + + fn same_input(&self, other: &QueuedEvent) -> bool { + match self.kind { + EventKind::KeyDown | EventKind::KeyUp => { + matches!(other.kind, EventKind::KeyDown | EventKind::KeyUp) + && self.keycode == other.keycode + && self.scan_code == other.scan_code + && self.extended == other.extended + } + EventKind::MouseDown | EventKind::MouseUp => { + matches!(other.kind, EventKind::MouseDown | EventKind::MouseUp) + && self.button == other.button + } + } + } + + fn release(&self) -> QueuedEvent { + QueuedEvent { + kind: match self.kind { + EventKind::KeyDown | EventKind::KeyUp => EventKind::KeyUp, + EventKind::MouseDown | EventKind::MouseUp => EventKind::MouseUp, + }, + has_xy: false, + x: 0, + y: 0, + ctrl: false, + alt: false, + shift: false, + meta: false, + ..*self + } + } +} + +#[derive(Default)] +struct HeldInputs { + pressed: Vec, +} + +impl HeldInputs { + fn track(&mut self, event: &QueuedEvent) { + self.pressed.retain(|held| !held.same_input(event)); + if matches!(event.kind, EventKind::KeyDown | EventKind::MouseDown) { + self.pressed.push(*event); + } + } + + fn take_releases(&mut self) -> Vec { + std::mem::take(&mut self.pressed) + .iter() + .map(QueuedEvent::release) + .collect() + } } fn write_ascii_key_name(buf: &mut [u8; KEY_NAME_BUF], len_out: &mut u8, name: &str) { @@ -99,6 +147,17 @@ pub fn write_key_name(buf: &mut [u8; KEY_NAME_BUF], len_out: &mut u8, vk: u16) { } } +pub fn write_packet_key_name(buf: &mut [u8; KEY_NAME_BUF], len_out: &mut u8, unit: u32) { + match char::from_u32(unit) { + Some(' ') => write_ascii_key_name(buf, len_out, "Space"), + Some(ch) if ch.is_ascii_alphanumeric() => { + let mut utf8 = [0u8; 4]; + write_ascii_key_name(buf, len_out, ch.to_ascii_uppercase().encode_utf8(&mut utf8)); + } + _ => *len_out = 0, + } +} + pub fn write_key_name_from_hook( buf: &mut [u8; KEY_NAME_BUF], len_out: &mut u8, @@ -125,6 +184,8 @@ impl ToNapiValue for QueuedEvent { EventKind::KeyDown | EventKind::KeyUp => { object.set("keycode", event.keycode)?; object.set("keyName", event.key_name())?; + object.set("scanCode", event.scan_code)?; + object.set("extended", event.extended)?; } EventKind::MouseDown | EventKind::MouseUp => { object.set("button", u32::from(event.button))?; @@ -133,36 +194,24 @@ impl ToNapiValue for QueuedEvent { object.set("y", event.y)?; } } - EventKind::MouseMove => { - object.set("x", event.x)?; - object.set("y", event.y)?; - } - EventKind::Wheel => { - object.set("deltaX", event.delta_x)?; - object.set("deltaY", event.delta_y)?; - if event.has_xy { - object.set("x", event.x)?; - object.set("y", event.y)?; - } - } } unsafe { as ToNapiValue>::to_napi_value(raw_env, object) } } } -type EventThreadsafeFunction = ThreadsafeFunction< - QueuedEvent, - UnknownReturnValue, - QueuedEvent, - napi::Status, - false, - true, - RING_CAPACITY, ->; +type EventThreadsafeFunction = + ThreadsafeFunction; #[cfg(target_os = "windows")] mod platform { - use super::{EventKind, EventThreadsafeFunction, QueuedEvent, write_key_name_from_hook}; + use super::{ + EventKind, EventThreadsafeFunction, HeldInputs, QueuedEvent, VK_PACKET, + write_key_name_from_hook, write_packet_key_name, + }; + use fluxer_desktop_native::input::modifiers::{ + Modifiers, + windows::{from_sampled, with_pressed_key}, + }; use napi::threadsafe_function::ThreadsafeFunctionCallMode; use std::sync::{ Arc, Mutex, MutexGuard, OnceLock, @@ -204,6 +253,7 @@ mod platform { pub(crate) last_event_ms: AtomicU64, pub(crate) last_cursor_change_ms: AtomicU64, pub(crate) reinstall_count: AtomicU64, + held: Mutex, } unsafe impl Send for HookHandle {} @@ -238,17 +288,15 @@ mod platform { unsafe { windows::Win32::System::SystemInformation::GetTickCount64() } } - fn sample_modifiers() -> (bool, bool, bool, bool) { - use fluxer_desktop_native::input::modifiers::windows::from_sampled; + fn sample_modifiers() -> Modifiers { unsafe { - let m = from_sampled( + from_sampled( GetKeyState(VK_SHIFT.0 as i32) as u16, GetKeyState(VK_CONTROL.0 as i32) as u16, GetKeyState(VK_MENU.0 as i32) as u16, GetKeyState(VK_LWIN.0 as i32) as u16, GetKeyState(VK_RWIN.0 as i32) as u16, - ); - (m.ctrl, m.alt, m.shift, m.meta) + ) } } @@ -276,6 +324,11 @@ mod platform { fn enqueue(handle: &HookHandle, event: QueuedEvent) { handle.last_event_ms.store(now_ms(), Ordering::Release); + lock_recover(&handle.held).track(&event); + send(handle, event); + } + + fn send(handle: &HookHandle, event: QueuedEvent) { let status = handle .tsfn .call(event, ThreadsafeFunctionCallMode::NonBlocking); @@ -294,7 +347,17 @@ mod platform { let is_down = msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN; let is_up = msg == WM_KEYUP || msg == WM_SYSKEYUP; if is_down || is_up { - let (ctrl, alt, shift, meta) = sample_modifiers(); + let sampled = sample_modifiers(); + let Modifiers { + ctrl, + alt, + shift, + meta, + } = if is_down { + with_pressed_key(sampled, vk) + } else { + sampled + }; let mut event = QueuedEvent { kind: if is_down { EventKind::KeyDown @@ -302,19 +365,29 @@ mod platform { EventKind::KeyUp }, keycode: info.vkCode, + scan_code: info.scanCode, + extended: (info.flags.0 & super::LLKHF_EXTENDED_FLAG) != 0, ctrl, alt, shift, meta, ..QueuedEvent::default() }; - let layout_vk = layout_aware_vk_from_scan_code(vk, info.scanCode, info.flags.0); - write_key_name_from_hook( - &mut event.key_name_buf, - &mut event.key_name_len, - layout_vk, - info.flags.0, - ); + if info.vkCode == VK_PACKET { + write_packet_key_name( + &mut event.key_name_buf, + &mut event.key_name_len, + info.scanCode, + ); + } else { + let layout_vk = layout_aware_vk_from_scan_code(vk, info.scanCode, info.flags.0); + write_key_name_from_hook( + &mut event.key_name_buf, + &mut event.key_name_len, + layout_vk, + info.flags.0, + ); + } enqueue(&handle, event); } } @@ -322,18 +395,22 @@ mod platform { } unsafe extern "system" fn mouse_proc(code: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { - use fluxer_desktop_native::input::windows_mouse::{Action, Axis, classify}; + use fluxer_desktop_native::input::windows_mouse::{Action, classify}; if code >= 0 && let Some(handle) = current_handle() { let info = unsafe { &*(lparam.0 as *const MSLLHOOKSTRUCT) }; let md = info.mouseData; let x_button = ((md >> 16) & 0xffff) as u16; - let wheel_delta = ((md >> 16) & 0xffff) as i16; - let (ctrl, alt, shift, meta) = sample_modifiers(); - let action = classify(wparam.0 as u32, x_button, wheel_delta); + let action = classify(wparam.0 as u32, x_button); match action { Action::Button { down, button } => { + let Modifiers { + ctrl, + alt, + shift, + meta, + } = sample_modifiers(); enqueue( &handle, QueuedEvent { @@ -354,43 +431,8 @@ mod platform { }, ); } - Action::Move => { - enqueue( - &handle, - QueuedEvent { - kind: EventKind::MouseMove, - x: info.pt.x, - y: info.pt.y, - has_xy: true, - ctrl, - alt, - shift, - meta, - ..QueuedEvent::default() - }, - ); - } - Action::Wheel { axis, delta } => { - let (dx, dy) = match axis { - Axis::Horizontal => (i32::from(delta), 0), - Axis::Vertical => (0, i32::from(delta)), - }; - enqueue( - &handle, - QueuedEvent { - kind: EventKind::Wheel, - delta_x: dx, - delta_y: dy, - x: info.pt.x, - y: info.pt.y, - has_xy: true, - ctrl, - alt, - shift, - meta, - ..QueuedEvent::default() - }, - ); + Action::Activity => { + handle.last_event_ms.store(now_ms(), Ordering::Release); } Action::Ignored => {} } @@ -460,6 +502,10 @@ mod platform { break; } if msg.message == WM_APP_REINSTALL { + let releases = lock_recover(&handle.held).take_releases(); + for event in releases { + send(&handle, event); + } uninstall_hooks(&handle); if install_hooks(&handle).is_ok() { handle.last_event_ms.store(now_ms(), Ordering::Release); @@ -622,6 +668,7 @@ mod platform { last_event_ms: AtomicU64::new(0), last_cursor_change_ms: AtomicU64::new(0), reinstall_count: AtomicU64::new(0), + held: Mutex::new(HeldInputs::default()), }) } } @@ -668,7 +715,6 @@ impl InputHook { .build_threadsafe_function::() .weak::() .callee_handled::() - .max_queue_size::() .build() .map_err(|err| { napi::Error::new( @@ -771,9 +817,26 @@ mod tests { } #[test] - fn queued_event_default_is_mouse_move_kind() { + fn write_packet_key_name_uses_the_injected_character() { + let name = |unit: u32| { + let mut buf = [0xffu8; KEY_NAME_BUF]; + let mut len = 7u8; + write_packet_key_name(&mut buf, &mut len, unit); + String::from_utf8(buf[..len as usize].to_vec()).unwrap() + }; + assert_eq!(name(u32::from('g')), "G"); + assert_eq!(name(u32::from('G')), "G"); + assert_eq!(name(u32::from('7')), "7"); + assert_eq!(name(u32::from(' ')), "Space"); + assert_eq!(name(u32::from('.')), ""); + assert_eq!(name(0x00f6), ""); + assert_eq!(name(0xd83d), ""); + } + + #[test] + fn queued_event_default_is_empty_keydown() { let event = QueuedEvent::default(); - assert_eq!(event.kind, EventKind::MouseMove); + assert_eq!(event.kind, EventKind::KeyDown); assert_eq!(event.key_name_len, 0); } @@ -783,8 +846,53 @@ mod tests { assert_eq!(EventKind::KeyUp.as_str(), "keyup"); assert_eq!(EventKind::MouseDown.as_str(), "mousedown"); assert_eq!(EventKind::MouseUp.as_str(), "mouseup"); - assert_eq!(EventKind::MouseMove.as_str(), "mousemove"); - assert_eq!(EventKind::Wheel.as_str(), "wheel"); + } + + #[test] + fn held_inputs_release_each_pressed_input_once_without_modifiers() { + let key = |kind, keycode, scan_code, extended| QueuedEvent { + kind, + keycode, + scan_code, + extended, + ctrl: true, + ..QueuedEvent::default() + }; + let mut held = HeldInputs::default(); + held.track(&key(EventKind::KeyDown, 0x41, 0x1e, false)); + held.track(&key(EventKind::KeyDown, 0x41, 0x1e, false)); + held.track(&key(EventKind::KeyDown, 0x0d, 0x1c, true)); + held.track(&key(EventKind::KeyDown, 0x0d, 0x1c, false)); + held.track(&key(EventKind::KeyUp, 0x0d, 0x1c, false)); + held.track(&QueuedEvent { + kind: EventKind::MouseDown, + button: 3, + has_xy: true, + ..QueuedEvent::default() + }); + let releases = held.take_releases(); + let summary: Vec<(EventKind, u32, bool, u8, bool, bool)> = releases + .iter() + .map(|event| { + ( + event.kind, + event.keycode, + event.extended, + event.button, + event.ctrl, + event.has_xy, + ) + }) + .collect(); + assert_eq!( + summary, + vec![ + (EventKind::KeyUp, 0x41, false, 0, false, false), + (EventKind::KeyUp, 0x0d, true, 0, false, false), + (EventKind::MouseUp, 0, false, 3, false, false), + ] + ); + assert!(held.take_releases().is_empty()); } #[test] @@ -799,12 +907,12 @@ mod tests { unsafe { let slots = ring.slots.as_ptr() as *mut QueuedEvent; - (*slots.add(idx)).kind = EventKind::Wheel; - (*slots.add(idx)).delta_y = 120; + (*slots.add(idx)).kind = EventKind::MouseUp; + (*slots.add(idx)).button = 4; } let pop_idx = ring.pop().expect("pop slot") as usize; - assert_eq!(ring.slots[pop_idx].kind, EventKind::Wheel); - assert_eq!(ring.slots[pop_idx].delta_y, 120); + assert_eq!(ring.slots[pop_idx].kind, EventKind::MouseUp); + assert_eq!(ring.slots[pop_idx].button, 4); ring.release(); assert!(ring.pop().is_none()); } diff --git a/fluxer_desktop/packaging/arch/PKGBUILD b/fluxer_desktop/packaging/arch/PKGBUILD index fd5b54d86..eecab436c 100644 --- a/fluxer_desktop/packaging/arch/PKGBUILD +++ b/fluxer_desktop/packaging/arch/PKGBUILD @@ -2,10 +2,12 @@ _channel="${FLUXER_CHANNEL:-stable}" if [[ $_channel == canary ]]; then pkgname=fluxer-canary + _desktopid=app.fluxer.FluxerDesktopCanary _appname='Fluxer Canary' _comment='Canary build of Fluxer' else pkgname=fluxer + _desktopid=app.fluxer.FluxerDesktop _appname='Fluxer' _comment='Instant messaging and VoIP' fi @@ -71,14 +73,15 @@ package() { ln -s "/opt/$pkgname/$pkgname" "$pkgdir/usr/bin/$pkgname" install -dm755 "$pkgdir/usr/share/applications" - cat > "$pkgdir/usr/share/applications/$pkgname.desktop" <<-EOF + cat > "$pkgdir/usr/share/applications/$_desktopid.desktop" <<-EOF [Desktop Entry] Name=$_appname Exec=/opt/$pkgname/$pkgname %U + TryExec=/opt/$pkgname/$pkgname Terminal=false Type=Application Icon=$pkgname - StartupWMClass=$pkgname + StartupWMClass=$_desktopid GenericName=Instant Messenger Comment=$_comment Keywords=chat;im;messaging;messenger;voip;voice;video;call; @@ -97,7 +100,7 @@ package() { Name=New Direct Message Exec=/opt/$pkgname/$pkgname --fluxer-task=new-dm %U EOF - chmod 644 "$pkgdir/usr/share/applications/$pkgname.desktop" + chmod 644 "$pkgdir/usr/share/applications/$_desktopid.desktop" for size in 16 24 32 48 64 128 256 512; do install -Dm644 "$opt/resources/icons/${size}x${size}.png" \ diff --git a/fluxer_desktop/packaging/linux/app.fluxer.Fluxer.yml b/fluxer_desktop/packaging/linux/app.fluxer.Fluxer.yml index 2633b2339..e5e3e902f 100644 --- a/fluxer_desktop/packaging/linux/app.fluxer.Fluxer.yml +++ b/fluxer_desktop/packaging/linux/app.fluxer.Fluxer.yml @@ -22,7 +22,6 @@ finish-args: - --talk-name=org.kde.StatusNotifierWatcher - --talk-name=org.ayatana.indicator.application - --talk-name=com.canonical.indicator.application - - --env=ELECTRON_OZONE_PLATFORM_HINT=auto - --env=XCURSOR_PATH=/run/host/user-share/icons:/run/host/share/icons build-options: strip: false diff --git a/fluxer_desktop/packaging/linux/app.fluxer.FluxerCanary.yml b/fluxer_desktop/packaging/linux/app.fluxer.FluxerCanary.yml index 603561f28..9ee06898c 100644 --- a/fluxer_desktop/packaging/linux/app.fluxer.FluxerCanary.yml +++ b/fluxer_desktop/packaging/linux/app.fluxer.FluxerCanary.yml @@ -22,7 +22,6 @@ finish-args: - --talk-name=org.kde.StatusNotifierWatcher - --talk-name=org.ayatana.indicator.application - --talk-name=com.canonical.indicator.application - - --env=ELECTRON_OZONE_PLATFORM_HINT=auto - --env=XCURSOR_PATH=/run/host/user-share/icons:/run/host/share/icons build-options: strip: false diff --git a/fluxer_desktop/src/common/DesktopConfig.ts b/fluxer_desktop/src/common/DesktopConfig.ts index 1b5e9957e..5e31289c2 100644 --- a/fluxer_desktop/src/common/DesktopConfig.ts +++ b/fluxer_desktop/src/common/DesktopConfig.ts @@ -10,6 +10,11 @@ import { STABLE_APP_URL, STABLE_MIGRATED_APP_ORIGIN, } from '@electron/common/Constants'; +import { + GLOBAL_SHORTCUT_DESCRIPTION_MAX_LENGTH, + type GlobalShortcutAction, + isGlobalShortcutAction, +} from '@electron/common/GlobalShortcutActions'; import type {DesktopTroubleshootingSettings, DesktopWindowBehaviorSettings} from '@electron/common/Types'; import log from 'electron-log'; @@ -25,6 +30,29 @@ interface DesktopConfig extends Record { troubleshooting?: PersistedDesktopTroubleshootingSettings; theme_allowed_local_files?: Array; app_origin?: string; + global_shortcuts?: PersistedGlobalShortcutsSettings; +} + +export type GlobalShortcutsPortalConsent = 'unset' | 'granted' | 'declined'; + +export interface PersistedGlobalShortcutAction { + action: GlobalShortcutAction; + description: string; + preferredTrigger: string | null; +} + +interface PersistedGlobalShortcutsSettings { + portal_consent?: GlobalShortcutsPortalConsent; + direct_input_enabled?: boolean; + migrated?: boolean; + last_actions?: Array; +} + +export interface GlobalShortcutsSettings { + portalConsent: GlobalShortcutsPortalConsent; + directInputEnabled: boolean; + migrated: boolean; + lastActions: Array; } export type ChromiumSwitchesSetting = ReadonlyArray | Record; @@ -145,6 +173,70 @@ function sanitizeChromiumSwitchesSetting(value: unknown): ChromiumSwitchesSettin return undefined; } +const PREFERRED_TRIGGER_MAX_LENGTH = 100; + +function sanitizePersistedGlobalShortcutActions(value: unknown): Array | undefined { + if (!Array.isArray(value)) { + return undefined; + } + const actions: Array = []; + const seen = new Set(); + for (const item of value) { + if (!isRecord(item) || !isGlobalShortcutAction(item.action) || seen.has(item.action)) continue; + if (typeof item.description !== 'string') continue; + const description = item.description.trim(); + if (description.length === 0 || description.length > GLOBAL_SHORTCUT_DESCRIPTION_MAX_LENGTH) continue; + const preferredTrigger = + typeof item.preferredTrigger === 'string' && + item.preferredTrigger.length > 0 && + item.preferredTrigger.length <= PREFERRED_TRIGGER_MAX_LENGTH + ? item.preferredTrigger + : null; + seen.add(item.action); + actions.push({action: item.action, description, preferredTrigger}); + } + return actions.length > 0 ? actions : undefined; +} + +export function sanitizePersistedGlobalShortcutsSettings(value: unknown): PersistedGlobalShortcutsSettings | undefined { + if (!isRecord(value)) { + return undefined; + } + const settings: PersistedGlobalShortcutsSettings = {}; + if (value.portal_consent === 'unset' || value.portal_consent === 'granted' || value.portal_consent === 'declined') { + settings.portal_consent = value.portal_consent; + } + if (typeof value.direct_input_enabled === 'boolean') { + settings.direct_input_enabled = value.direct_input_enabled; + } + if (typeof value.migrated === 'boolean') { + settings.migrated = value.migrated; + } + const lastActions = sanitizePersistedGlobalShortcutActions(value.last_actions); + if (lastActions) { + settings.last_actions = lastActions; + } + return Object.keys(settings).length > 0 ? settings : undefined; +} + +function normalizeGlobalShortcutsSettings(settings?: PersistedGlobalShortcutsSettings): GlobalShortcutsSettings { + return { + portalConsent: settings?.portal_consent ?? 'unset', + directInputEnabled: settings?.direct_input_enabled ?? false, + migrated: settings?.migrated ?? false, + lastActions: settings?.last_actions ? settings.last_actions.map((entry) => ({...entry})) : [], + }; +} + +function serializeGlobalShortcutsSettings(settings: GlobalShortcutsSettings): PersistedGlobalShortcutsSettings { + return { + portal_consent: settings.portalConsent, + direct_input_enabled: settings.directInputEnabled, + migrated: settings.migrated, + ...(settings.lastActions.length > 0 ? {last_actions: settings.lastActions.map((entry) => ({...entry}))} : {}), + }; +} + function getLegacyAppUrl(): string { return BUILD_CHANNEL === 'canary' ? CANARY_APP_URL : STABLE_APP_URL; } @@ -191,6 +283,12 @@ function sanitizeDesktopConfig(value: unknown): DesktopConfig { } else { delete nextConfig.troubleshooting; } + const globalShortcuts = sanitizePersistedGlobalShortcutsSettings(value.global_shortcuts); + if (globalShortcuts) { + nextConfig.global_shortcuts = globalShortcuts; + } else { + delete nextConfig.global_shortcuts; + } if (Array.isArray(value.theme_allowed_local_files)) { nextConfig.theme_allowed_local_files = value.theme_allowed_local_files.filter( (item): item is string => typeof item === 'string' && item.trim().length > 0, @@ -422,6 +520,19 @@ export function setDesktopTroubleshootingSettings( return getDesktopTroubleshootingSettings(); } +export function getGlobalShortcutsSettings(): GlobalShortcutsSettings { + return normalizeGlobalShortcutsSettings(config.global_shortcuts); +} + +export function setGlobalShortcutsSettings(settings: Partial): GlobalShortcutsSettings { + config.global_shortcuts = serializeGlobalShortcutsSettings({ + ...getGlobalShortcutsSettings(), + ...settings, + }); + saveDesktopConfig(); + return getGlobalShortcutsSettings(); +} + export function getAllowedThemeLocalFiles(): Array { return Array.isArray(config.theme_allowed_local_files) ? [...config.theme_allowed_local_files] : []; } diff --git a/fluxer_desktop/src/common/DesktopIdentity.ts b/fluxer_desktop/src/common/DesktopIdentity.ts index d3b5694ca..6f7eeb9be 100644 --- a/fluxer_desktop/src/common/DesktopIdentity.ts +++ b/fluxer_desktop/src/common/DesktopIdentity.ts @@ -4,7 +4,12 @@ import {BUILD_CHANNEL} from '@electron/common/BuildChannel'; export const DESKTOP_APP_NAME = BUILD_CHANNEL === 'canary' ? 'Fluxer Canary' : 'Fluxer'; export const MACOS_BUNDLE_ID = BUILD_CHANNEL === 'canary' ? 'app.fluxer.canary' : 'app.fluxer'; -export const LINUX_DESKTOP_ENTRY_ID = BUILD_CHANNEL === 'canary' ? 'fluxer-canary' : 'fluxer'; +export const LINUX_DESKTOP_ENTRY_ID = + BUILD_CHANNEL === 'canary' ? 'app.fluxer.FluxerDesktopCanary' : 'app.fluxer.FluxerDesktop'; +export const LINUX_PORTAL_SESSION_TOKEN = + BUILD_CHANNEL === 'canary' ? 'fluxer_canary_global_shortcuts' : 'fluxer_global_shortcuts'; +export const LEGACY_LINUX_DESKTOP_ENTRY_ID = BUILD_CHANNEL === 'canary' ? 'fluxer-canary' : 'fluxer'; +export const LINUX_ICON_NAME = BUILD_CHANNEL === 'canary' ? 'fluxer-canary' : 'fluxer'; export const WINDOWS_SHORTCUT_AUTHOR = 'Fluxer Platform AB'; export const WINDOWS_VELOPACK_ID = BUILD_CHANNEL === 'canary' ? 'fluxer_desktop_canary' : 'fluxer_desktop'; export const WINDOWS_LEGACY_SQUIRREL_ID = 'fluxer_app'; diff --git a/fluxer_desktop/src/common/GlobalShortcutActions.ts b/fluxer_desktop/src/common/GlobalShortcutActions.ts new file mode 100644 index 000000000..5e109bb61 --- /dev/null +++ b/fluxer_desktop/src/common/GlobalShortcutActions.ts @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +export const GLOBAL_SHORTCUT_ACTIONS = [ + 'voice_toggle_mute', + 'voice_toggle_deafen', + 'voice_push_to_talk', + 'voice_push_to_talk_priority', + 'voice_push_to_mute', + 'voice_priority_vad', + 'voice_toggle_vad', + 'voice_toggle_camera', + 'voice_switch_channel', + 'voice_disconnect', +] as const; + +export type GlobalShortcutAction = (typeof GLOBAL_SHORTCUT_ACTIONS)[number]; + +export const GLOBAL_SHORTCUT_HOLD_ACTIONS: ReadonlySet = new Set([ + 'voice_push_to_talk', + 'voice_push_to_talk_priority', + 'voice_push_to_mute', + 'voice_priority_vad', +]); + +export const GLOBAL_SHORTCUT_DEFAULT_DESCRIPTIONS: Readonly> = { + voice_toggle_mute: 'Toggle mute', + voice_toggle_deafen: 'Toggle deafen', + voice_push_to_talk: 'Push to talk', + voice_push_to_talk_priority: 'Push to talk (priority)', + voice_push_to_mute: 'Push to mute', + voice_priority_vad: 'Voice activity priority', + voice_toggle_vad: 'Toggle voice activity', + voice_toggle_camera: 'Toggle camera', + voice_switch_channel: 'Switch voice channel', + voice_disconnect: 'Disconnect from voice', +}; + +export const GLOBAL_SHORTCUT_DESCRIPTION_MAX_LENGTH = 200; + +export function isGlobalShortcutAction(value: unknown): value is GlobalShortcutAction { + return typeof value === 'string' && (GLOBAL_SHORTCUT_ACTIONS as ReadonlyArray).includes(value); +} diff --git a/fluxer_desktop/src/common/Types.ts b/fluxer_desktop/src/common/Types.ts index 03b14faac..eda994d08 100644 --- a/fluxer_desktop/src/common/Types.ts +++ b/fluxer_desktop/src/common/Types.ts @@ -763,6 +763,7 @@ export interface ElectronAPI { onGlobalKeyEvent: (callback: (event: GlobalKeyEvent) => void) => () => void; onGlobalMouseEvent: (callback: (event: GlobalMouseEvent) => void) => () => void; onGlobalKeybindTriggered: (callback: (event: GlobalKeybindTriggeredEvent) => void) => () => void; + globalShortcuts: GlobalShortcutsApi; spellcheckGetState: () => Promise; spellcheckSetState: (state: Partial) => Promise; spellcheckGetAvailableLanguages: () => Promise>; @@ -795,6 +796,117 @@ export interface ElectronAPI { getAppMetrics?: () => Promise; } +export type GlobalShortcutsBackend = 'portal' | 'x11' | 'evdev' | 'windows' | 'macos' | 'none'; + +export type GlobalShortcutsPortalState = + | 'unknown' + | 'probing' + | 'unsupported' + | 'not-set-up' + | 'binding' + | 'bound' + | 'declined' + | 'error'; + +export interface GlobalShortcutsPortalShortcut { + action: string; + triggerDescription: string | null; +} + +export interface GlobalShortcutsPortalStatus { + state: GlobalShortcutsPortalState; + version: number | null; + canConfigure: boolean; + recovering: boolean; + canRecheck: boolean; + portalAppId: string | null; + shortcuts: Array; + error: string | null; +} + +export type LinuxSessionType = 'wayland' | 'x11' | 'unknown'; + +export type LinuxDesktopKind = 'kde' | 'gnome' | 'hyprland' | 'other'; + +export interface GlobalShortcutsLinuxStatus { + session: LinuxSessionType; + sandbox: 'flatpak' | 'none'; + desktop: LinuxDesktopKind; + portal: GlobalShortcutsPortalStatus | null; + directInput: {available: boolean; enabled: boolean; locked: boolean}; +} + +export interface GlobalShortcutsStatus { + backend: GlobalShortcutsBackend; + platform: 'linux' | 'windows' | 'macos'; + linux: GlobalShortcutsLinuxStatus | null; + hooksActive: boolean; + hookError: 'permission' | 'start-failed' | null; + supportsMouseButtons: boolean; + supportsModifierOnly: boolean; +} + +export interface GlobalShortcutCombo { + code?: string; + key: string; + ctrl: boolean; + alt: boolean; + shift: boolean; + meta: boolean; + mouseButton?: number; + modifierOnly?: boolean; + bothSides?: boolean; +} + +export interface GlobalShortcutBinding { + sourceId: string; + action: string; + combo: GlobalShortcutCombo; +} + +export interface GlobalShortcutActionDefinition { + action: string; + description: string; + preferredCombo: GlobalShortcutCombo | null; +} + +export interface GlobalShortcutsSyncPayload { + bindings: Array; + actions: Array; +} + +export interface GlobalShortcutEvent { + action: string; + sourceId: string; + phase: 'press' | 'release'; +} + +export interface GlobalShortcutCaptureEvent { + type: 'keydown' | 'keyup' | 'mousedown' | 'mouseup'; + code: string | null; + key: string | null; + button: number | null; + ctrl: boolean; + alt: boolean; + shift: boolean; + meta: boolean; +} + +export interface GlobalShortcutsApi { + sync: (payload: GlobalShortcutsSyncPayload) => Promise; + setPaused: (paused: boolean) => Promise; + getStatus: () => Promise; + onStatus: (callback: (status: GlobalShortcutsStatus) => void) => () => void; + onEvent: (callback: (event: GlobalShortcutEvent) => void) => () => void; + setUp: () => Promise; + recheck: () => Promise; + configure: () => Promise; + setDirectInputEnabled: (enabled: boolean) => Promise; + startCapture: () => Promise; + stopCapture: (captureId: number) => Promise; + onCapture: (callback: (event: GlobalShortcutCaptureEvent) => void) => () => void; +} + export interface GlobalKeyHookRegisterOptions { id: string; description?: string; diff --git a/fluxer_desktop/src/main/Autostart.test.mjs b/fluxer_desktop/src/main/Autostart.test.mjs new file mode 100644 index 000000000..49962e238 --- /dev/null +++ b/fluxer_desktop/src/main/Autostart.test.mjs @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import {describe, test} from 'node:test'; +import {loadTsModule} from './fixtures/TsModuleLoader.mjs'; + +const autostart = loadTsModule('@electron/main/Autostart', { + stubs: { + 'node:fs': fs, + 'node:module': {createRequire: () => () => ({})}, + 'node:os': os, + 'node:path': path, + electron: {app: {}, ipcMain: {handle: () => {}}}, + 'electron-log': {info: () => {}, warn: () => {}, error: () => {}}, + '@electron/common/BuildChannel': {BUILD_CHANNEL: 'stable'}, + '@electron/common/UserDataPath': {isPortableMode: () => false}, + '@electron/main/LinuxDesktopEntry': {GENERATED_MARKER: '# X-Generated-By=fluxer-desktop'}, + '@electron/main/LinuxLaunchPath': {getStableLinuxLaunchPath: () => '/opt/Fluxer/fluxer'}, + '@electron/main/LinuxSandbox': {isFlatpakRuntime: () => false}, + '@electron/main/MainI18n': {t: (key) => key}, + }, +}); + +const LEGACY = [ + '[Desktop Entry]', + 'Type=Application', + 'Name=Fluxer', + 'Exec="/opt/Fluxer/fluxer" --autostart --ozone-platform=x11', + 'TryExec=/opt/Fluxer/fluxer', + 'StartupWMClass=fluxer', + 'X-GNOME-Autostart-enabled=false', + 'X-GNOME-Autostart-Delay=10', + '', + '[Desktop Action other]', + 'StartupWMClass=fluxer', + '', +].join('\n'); + +describe('rewriteLegacyLinuxAutostartContents', () => { + test('keeps user edits and only rewrites the window class when the command still exists', () => { + const rewritten = autostart.rewriteLegacyLinuxAutostartContents(LEGACY, '/opt/Fluxer/fluxer', false); + assert.equal( + rewritten, + LEGACY.replace('StartupWMClass=fluxer\nX-GNOME', 'StartupWMClass=app.fluxer.FluxerDesktop\nX-GNOME'), + ); + }); + + test('repoints Exec and TryExec when the old command is gone', () => { + const rewritten = autostart.rewriteLegacyLinuxAutostartContents(LEGACY, '/home/u/Fluxer.AppImage', true); + const lines = rewritten.split('\n'); + assert.ok(lines.includes('Exec="/home/u/Fluxer.AppImage" --autostart')); + assert.ok(lines.includes('TryExec=/home/u/Fluxer.AppImage')); + assert.ok(lines.includes('X-GNOME-Autostart-enabled=false')); + assert.ok(lines.includes('X-GNOME-Autostart-Delay=10')); + assert.equal(lines.filter((line) => line === 'StartupWMClass=fluxer').length, 1); + }); +}); + +describe('Linux autostart with an entry left under the previous desktop id', () => { + function loadLinuxAutostart() { + const handlers = new Map(); + const platform = Object.getOwnPropertyDescriptor(process, 'platform'); + Object.defineProperty(process, 'platform', {...platform, value: 'linux'}); + try { + const module = loadTsModule('@electron/main/Autostart', { + stubs: { + 'node:fs': fs, + 'node:module': {createRequire: () => () => ({})}, + 'node:os': os, + 'node:path': path, + electron: {app: {}, ipcMain: {handle: (channel, handler) => handlers.set(channel, handler)}}, + 'electron-log': {info: () => {}, warn: () => {}, error: () => {}}, + '@electron/common/BuildChannel': {BUILD_CHANNEL: 'stable'}, + '@electron/common/UserDataPath': {isPortableMode: () => false}, + '@electron/main/LinuxDesktopEntry': {GENERATED_MARKER: '# X-Generated-By=fluxer-desktop'}, + '@electron/main/LinuxLaunchPath': {getStableLinuxLaunchPath: () => process.execPath}, + '@electron/main/LinuxSandbox': {isFlatpakRuntime: () => false}, + '@electron/main/MainI18n': {t: (key) => key}, + }, + }); + return {module, handlers}; + } finally { + Object.defineProperty(process, 'platform', platform); + } + } + + test('a generated legacy entry the migration could not move still shows as on and turning it off removes it', async (t) => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'fluxer-autostart-')); + const previousConfig = process.env.XDG_CONFIG_HOME; + process.env.XDG_CONFIG_HOME = root; + const dir = path.join(root, 'autostart'); + t.after(() => { + fs.rmSync(root, {recursive: true, force: true}); + if (previousConfig === undefined) delete process.env.XDG_CONFIG_HOME; + else process.env.XDG_CONFIG_HOME = previousConfig; + }); + fs.mkdirSync(dir, {recursive: true}); + const legacyPath = path.join(dir, 'fluxer.desktop'); + fs.writeFileSync( + legacyPath, + [ + '[Desktop Entry]', + `Exec="${process.execPath}" --autostart`, + `TryExec=${process.execPath}`, + 'StartupWMClass=fluxer', + '', + ].join('\n'), + ); + fs.mkdirSync(path.join(dir, `app.fluxer.FluxerDesktop.desktop.${process.pid}.tmp`, 'blocker'), {recursive: true}); + const {module, handlers} = loadLinuxAutostart(); + module.registerAutostartHandlers(); + await new Promise((resolve) => setTimeout(resolve, 50)); + assert.equal(fs.existsSync(legacyPath), true); + assert.equal(await handlers.get('autostart-is-enabled')(), true); + await handlers.get('autostart-disable')(); + assert.equal(fs.existsSync(legacyPath), false); + assert.equal(await handlers.get('autostart-is-enabled')(), false); + }); + + test('a hand-written legacy entry is left alone', async (t) => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'fluxer-autostart-')); + const previousConfig = process.env.XDG_CONFIG_HOME; + process.env.XDG_CONFIG_HOME = root; + t.after(() => { + fs.rmSync(root, {recursive: true, force: true}); + if (previousConfig === undefined) delete process.env.XDG_CONFIG_HOME; + else process.env.XDG_CONFIG_HOME = previousConfig; + }); + const dir = path.join(root, 'autostart'); + fs.mkdirSync(dir, {recursive: true}); + const legacyPath = path.join(dir, 'fluxer.desktop'); + fs.writeFileSync(legacyPath, ['[Desktop Entry]', `Exec="${process.execPath}" --autostart`, ''].join('\n')); + const {module, handlers} = loadLinuxAutostart(); + module.registerAutostartHandlers(); + await new Promise((resolve) => setTimeout(resolve, 50)); + assert.equal(await handlers.get('autostart-is-enabled')(), false); + await handlers.get('autostart-disable')(); + assert.equal(fs.existsSync(legacyPath), true); + }); +}); diff --git a/fluxer_desktop/src/main/Autostart.ts b/fluxer_desktop/src/main/Autostart.ts index 823c0e60b..d21df499a 100644 --- a/fluxer_desktop/src/main/Autostart.ts +++ b/fluxer_desktop/src/main/Autostart.ts @@ -6,11 +6,15 @@ import os from 'node:os'; import path from 'node:path'; import { DESKTOP_APP_NAME, + LEGACY_LINUX_DESKTOP_ENTRY_ID, LINUX_DESKTOP_ENTRY_ID, + LINUX_ICON_NAME, WINDOWS_APP_USER_MODEL_ID, WINDOWS_LEGACY_APP_USER_MODEL_IDS, } from '@electron/common/DesktopIdentity'; import {isPortableMode} from '@electron/common/UserDataPath'; +import {getLinuxPortalsMode} from '@electron/main/LaunchOptions'; +import {GENERATED_MARKER} from '@electron/main/LinuxDesktopEntry'; import {getStableLinuxLaunchPath} from '@electron/main/LinuxLaunchPath'; import {isFlatpakRuntime} from '@electron/main/LinuxSandbox'; import {t} from '@electron/main/MainI18n'; @@ -74,6 +78,7 @@ const isLinux = process.platform === 'linux'; const APP_NAME = DESKTOP_APP_NAME; const LINUX_DESKTOP_FILE_BASENAME = `${LINUX_DESKTOP_ENTRY_ID}.desktop`; const LINUX_STARTUP_WM_CLASS = LINUX_DESKTOP_ENTRY_ID; +const LEGACY_LINUX_DESKTOP_FILE_BASENAME = `${LEGACY_LINUX_DESKTOP_ENTRY_ID}.desktop`; const AUTOSTART_LAUNCH_ARG = '--autostart'; interface AutoLaunchConfig { @@ -120,7 +125,7 @@ function loadWinShell(): WinShellBinding | null { } function loadLinuxPortals(): LinuxPortalsBinding | null { - if (!isLinux) return null; + if (!isLinux || getLinuxPortalsMode(process.argv) === 'off') return null; if (cachedLinuxPortalsBinding !== undefined) return cachedLinuxPortalsBinding; try { cachedLinuxPortalsBinding = requireModule('@fluxer/linux-portals') as LinuxPortalsBinding; @@ -395,6 +400,7 @@ function buildLinuxDesktopFileContents(): string { const execLine = `${quoteDesktopExecArg(execPath)} ${AUTOSTART_LAUNCH_ARG}`; return [ '[Desktop Entry]', + GENERATED_MARKER, 'Type=Application', `Name=${escapeDesktopEntry(APP_NAME)}`, 'Comment=Fluxer', @@ -411,12 +417,16 @@ function buildLinuxDesktopFileContents(): string { } async function enableLinuxAutostart(): Promise { + await writeLinuxAutostartFile(buildLinuxDesktopFileContents()); +} + +async function writeLinuxAutostartFile(contents: string): Promise { const dir = getLinuxAutostartDir(); const filePath = getLinuxDesktopFilePath(); const tempPath = `${filePath}.${process.pid}.tmp`; try { await fs.promises.mkdir(dir, {recursive: true, mode: 0o700}); - await fs.promises.writeFile(tempPath, buildLinuxDesktopFileContents(), {encoding: 'utf8', mode: 0o644}); + await fs.promises.writeFile(tempPath, contents, {encoding: 'utf8', mode: 0o644}); await fs.promises.rename(tempPath, filePath); log.info('[Autostart] Wrote Linux autostart entry', {filePath}); } catch (error) { @@ -426,10 +436,28 @@ async function enableLinuxAutostart(): Promise { } } +function getLegacyLinuxDesktopFilePath(): string { + return path.join(getLinuxAutostartDir(), LEGACY_LINUX_DESKTOP_FILE_BASENAME); +} + +function readLegacyGeneratedLinuxAutostartEntry(): Map | null { + let contents: string; + try { + contents = fs.readFileSync(getLegacyLinuxDesktopFilePath(), 'utf8'); + } catch { + return null; + } + const entry = tryParseDesktopEntry(contents); + return isLegacyGeneratedLinuxAutostartEntry(contents, entry) ? entry : null; +} + async function disableLinuxAutostart(): Promise { const filePath = getLinuxDesktopFilePath(); try { await fs.promises.rm(filePath, {force: true}); + if (readLegacyGeneratedLinuxAutostartEntry() !== null) { + await fs.promises.rm(getLegacyLinuxDesktopFilePath(), {force: true}); + } log.info('[Autostart] Removed Linux autostart entry', {filePath}); } catch (error) { log.error('[Autostart] Failed to remove Linux autostart entry:', error); @@ -482,13 +510,13 @@ async function setFlatpakAutostart(enabled: boolean): Promise { function isLinuxAutostartEnabled(): boolean { if (isFlatpakRuntime()) return readFlatpakAutostartState(); - let contents: string; + let entry: Map | null; try { - contents = fs.readFileSync(getLinuxDesktopFilePath(), 'utf8'); + entry = tryParseDesktopEntry(fs.readFileSync(getLinuxDesktopFilePath(), 'utf8')); } catch { - return false; + entry = readLegacyGeneratedLinuxAutostartEntry(); } - const entry = tryParseDesktopEntry(contents); + if (entry === null) return false; if (desktopEntryBoolean(entry.get('Hidden'))) return false; return linuxDesktopEntryTargetsExistingCommand(entry); } @@ -611,8 +639,63 @@ async function repairLinuxAutostartEntry(): Promise { } } +function isLegacyGeneratedLinuxAutostartEntry(contents: string, entry: Map): boolean { + return contents.includes(GENERATED_MARKER) || entry.get('StartupWMClass')?.trim() === LEGACY_LINUX_DESKTOP_ENTRY_ID; +} + +export function rewriteLegacyLinuxAutostartContents(contents: string, execPath: string, execStale: boolean): string { + const replacements = new Map([ + ['StartupWMClass', LINUX_STARTUP_WM_CLASS], + ['Icon', LINUX_ICON_NAME], + ]); + if (execStale) { + replacements.set('Exec', escapeDesktopEntry(`${quoteDesktopExecArg(execPath)} ${AUTOSTART_LAUNCH_ARG}`)); + replacements.set('TryExec', escapeDesktopEntry(execPath)); + } + let inDesktopEntry = false; + return contents + .split('\n') + .map((rawLine) => { + const line = rawLine.trim(); + if (line.startsWith('[') && line.endsWith(']')) { + inDesktopEntry = line === '[Desktop Entry]'; + return rawLine; + } + const separator = line.indexOf('='); + if (!inDesktopEntry || separator <= 0) return rawLine; + const replacement = replacements.get(line.slice(0, separator).trim()); + return replacement === undefined ? rawLine : `${line.slice(0, separator)}=${replacement}`; + }) + .join('\n'); +} + +async function migrateLegacyLinuxAutostartEntry(): Promise { + if (!isLinux || isFlatpakRuntime() || isPortableMode()) return; + const legacyPath = getLegacyLinuxDesktopFilePath(); + let contents: string; + try { + contents = await fs.promises.readFile(legacyPath, 'utf8'); + } catch { + return; + } + const entry = tryParseDesktopEntry(contents); + if (!isLegacyGeneratedLinuxAutostartEntry(contents, entry)) return; + try { + if (!fs.existsSync(getLinuxDesktopFilePath())) { + const execStale = !linuxDesktopEntryTargetsExistingCommand(entry); + await writeLinuxAutostartFile( + rewriteLegacyLinuxAutostartContents(contents, getStableLinuxLaunchPath(), execStale), + ); + } + await fs.promises.rm(legacyPath, {force: true}); + log.info('[Autostart] Migrated the Linux autostart entry to the new desktop id', {legacyPath}); + } catch (error) { + log.warn('[Autostart] Failed to migrate the legacy Linux autostart entry:', error); + } +} + export function registerAutostartHandlers(): void { - void repairLinuxAutostartEntry(); + void migrateLegacyLinuxAutostartEntry().then(repairLinuxAutostartEntry); ipcMain.handle('autostart-enable', async (): Promise => { await enableAutostart(); }); diff --git a/fluxer_desktop/src/main/DeepLinks.ts b/fluxer_desktop/src/main/DeepLinks.ts index 64b076c95..acd99f149 100644 --- a/fluxer_desktop/src/main/DeepLinks.ts +++ b/fluxer_desktop/src/main/DeepLinks.ts @@ -2,7 +2,6 @@ import {APP_PROTOCOL} from '@electron/common/Constants'; import {parseJumpListTaskFromArgv} from '@electron/main/JumpList'; -import {ensureLinuxProtocolDesktopEntry} from '@electron/main/LinuxDesktopEntry'; import {recordRecentDeepLink} from '@electron/main/RecentDocuments'; import {getMainWindow, showWindow} from '@electron/main/Window'; import {app, ipcMain} from 'electron'; @@ -62,7 +61,6 @@ function normalizeDeepLinkForRenderer(rawUrl: string): string | null { } export function initializeDeepLinks(): void { - ensureLinuxProtocolDesktopEntry(); if (process.platform === 'linux') { registerInitialDeepLinkHandler(); return; diff --git a/fluxer_desktop/src/main/DesktopConfig.test.mjs b/fluxer_desktop/src/main/DesktopConfig.test.mjs new file mode 100644 index 000000000..bebe6a2a3 --- /dev/null +++ b/fluxer_desktop/src/main/DesktopConfig.test.mjs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import assert from 'node:assert/strict'; +import {describe, test} from 'node:test'; +import {loadTsModule} from './fixtures/TsModuleLoader.mjs'; + +const desktopConfig = loadTsModule('@electron/common/DesktopConfig', { + stubs: { + 'node:fs': {}, + 'node:path': {}, + 'electron-log': {debug: () => {}, info: () => {}, warn: () => {}, error: () => {}}, + '@electron/common/BuildChannel': {BUILD_CHANNEL: 'stable'}, + }, +}); + +describe('sanitizePersistedGlobalShortcutsSettings', () => { + test('keeps valid fields', () => { + assert.deepEqual( + desktopConfig.sanitizePersistedGlobalShortcutsSettings({ + portal_consent: 'granted', + direct_input_enabled: true, + migrated: true, + last_actions: [{action: 'voice_push_to_talk', description: ' Push to talk ', preferredTrigger: 'F13'}], + }), + { + portal_consent: 'granted', + direct_input_enabled: true, + migrated: true, + last_actions: [{action: 'voice_push_to_talk', description: 'Push to talk', preferredTrigger: 'F13'}], + }, + ); + }); + + test('drops invalid fields and unknown or duplicate actions', () => { + assert.deepEqual( + desktopConfig.sanitizePersistedGlobalShortcutsSettings({ + portal_consent: 'maybe', + direct_input_enabled: 'yes', + last_actions: [ + {action: 'misc_help', description: 'Help', preferredTrigger: null}, + {action: 'voice_toggle_mute', description: '', preferredTrigger: null}, + {action: 'voice_toggle_mute', description: 'Toggle mute', preferredTrigger: 42}, + {action: 'voice_toggle_mute', description: 'Again', preferredTrigger: 'F1'}, + ], + }), + {last_actions: [{action: 'voice_toggle_mute', description: 'Toggle mute', preferredTrigger: null}]}, + ); + assert.equal(desktopConfig.sanitizePersistedGlobalShortcutsSettings({portal_consent: 'nope'}), undefined); + assert.equal(desktopConfig.sanitizePersistedGlobalShortcutsSettings('granted'), undefined); + }); + + test('defaults to an unset, disabled, unmigrated state', () => { + assert.deepEqual(desktopConfig.getGlobalShortcutsSettings(), { + portalConsent: 'unset', + directInputEnabled: false, + migrated: false, + lastActions: [], + }); + }); +}); diff --git a/fluxer_desktop/src/main/DisplayMedia.ts b/fluxer_desktop/src/main/DisplayMedia.ts index 09bb5e858..7c3b28564 100644 --- a/fluxer_desktop/src/main/DisplayMedia.ts +++ b/fluxer_desktop/src/main/DisplayMedia.ts @@ -7,6 +7,7 @@ import { normalizeDesktopSourceTypes, shouldHonorSelectedAudio, } from '@electron/main/DisplayMediaValidation'; +import {isWaylandSession} from '@electron/main/LinuxSession'; import {startWindowsScreenCaptureGuardForSource} from '@electron/main/WindowsScreenCaptureGuard'; import {BrowserWindow, desktopCapturer, ipcMain, screen} from 'electron'; import log from 'electron-log'; @@ -116,12 +117,6 @@ function _isOwnWindowSourceId(sourceId: string): boolean { return collectOwnWindowMediaSourceIds().has(sourceId); } -function isWaylandSession(): boolean { - return ( - process.platform === 'linux' && (Boolean(process.env.WAYLAND_DISPLAY) || process.env.XDG_SESSION_TYPE === 'wayland') - ); -} - function consumeWaylandPortalSurfacePreference(): DisplayMediaPortalSurfacePreference | null { const preference = nextWaylandPortalSurfacePreference; nextWaylandPortalSurfacePreference = null; @@ -173,10 +168,6 @@ async function resolveWaylandPortalDisplayMedia( return {video}; } -function _isWaylandPortalShareActive(): boolean { - return isWaylandSession(); -} - export function drainPendingDisplayMediaRequests(reason: string): void { if (pendingDisplayMediaRequests.size === 0) return; log.warn('[DisplayMedia] Draining pending requests', {reason, count: pendingDisplayMediaRequests.size}); diff --git a/fluxer_desktop/src/main/DomainMigration.test.mjs b/fluxer_desktop/src/main/DomainMigration.test.mjs index ad87e4a2a..ddda02f47 100644 --- a/fluxer_desktop/src/main/DomainMigration.test.mjs +++ b/fluxer_desktop/src/main/DomainMigration.test.mjs @@ -25,6 +25,7 @@ function transform(relativePath) { const constantsSource = transform('../common/Constants.ts'); const desktopConfigSource = transform('../common/DesktopConfig.ts'); +const globalShortcutActionsSource = transform('../common/GlobalShortcutActions.ts'); const domainMigrationSource = transform('./DomainMigration.ts'); const silentLog = {debug() {}, info() {}, warn() {}, error() {}}; @@ -53,8 +54,12 @@ function loadDesktop({channel = 'stable', settings} = {}) { const constants = runModule(constantsSource, (specifier) => { throw new Error(`Unexpected import: ${specifier}`); }); + const globalShortcutActions = runModule(globalShortcutActionsSource, (specifier) => { + throw new Error(`Unexpected import: ${specifier}`); + }); const desktopConfig = runModule(desktopConfigSource, (specifier) => { if (specifier === 'node:fs') return fs; + if (specifier === '@electron/common/GlobalShortcutActions') return globalShortcutActions; if (specifier === 'node:path') return path; if (specifier === '@electron/common/BuildChannel') return {BUILD_CHANNEL: channel}; if (specifier === '@electron/common/Constants') return constants; diff --git a/fluxer_desktop/src/main/EvdevHook.ts b/fluxer_desktop/src/main/EvdevHook.ts deleted file mode 100644 index 84e0f3d34..000000000 --- a/fluxer_desktop/src/main/EvdevHook.ts +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -import {EventEmitter} from 'node:events'; -import {createRequire} from 'node:module'; -import {getLinuxInputHookMode} from '@electron/main/LaunchOptions'; - -const requireModule = createRequire(import.meta.url); - -export interface EvdevKeyEvent { - type: 'keydown' | 'keyup'; - keycode: number; - keyName: string; - ctrlKey: boolean; - altKey: boolean; - shiftKey: boolean; - metaKey: boolean; -} - -export interface EvdevMouseEvent { - type: 'mousedown' | 'mouseup'; - button: number; - ctrlKey: boolean; - altKey: boolean; - shiftKey: boolean; - metaKey: boolean; -} - -type NativeEvent = EvdevKeyEvent | EvdevMouseEvent; -type NativeEvdevHookCtor = new ( - onEvent: (event: NativeEvent) => void, -) => { - start(): boolean; - stop(): void; -}; -interface NativeEvdevHookInstance { - start(): boolean; - stop(): void; -} -type NativeEvdevModule = { - EvdevHook: NativeEvdevHookCtor | null; - nameToEvdevKeycode: ((name: string) => number) | null; - loadError: Error | null; -}; - -function loadNativeModule(): NativeEvdevModule | null { - if (process.platform !== 'linux') return null; - if (getLinuxInputHookMode(process.argv) === 'off' || getLinuxInputHookMode(process.argv) === 'native') return null; - let required: NativeEvdevModule; - try { - required = requireModule('@fluxer/linux-evdev') as NativeEvdevModule; - } catch (error) { - throw new Error( - `@fluxer/linux-evdev failed to load on Linux — this is a packaging bug, not a runtime fallback case. ` + - `Original error: ${error instanceof Error ? error.message : String(error)}`, - ); - } - if (!required.EvdevHook || !required.nameToEvdevKeycode) { - throw new Error( - `@fluxer/linux-evdev loaded but exports are missing — native binary did not register module. ` + - `Underlying loadError: ${required.loadError ? required.loadError.message : ''}`, - ); - } - return required; -} - -let nativeModuleCache: NativeEvdevModule | null | undefined; - -function getNativeModule(): NativeEvdevModule | null { - if (nativeModuleCache !== undefined) return nativeModuleCache; - nativeModuleCache = loadNativeModule(); - return nativeModuleCache; -} - -class EvdevHook extends EventEmitter { - private nativeInstance: NativeEvdevHookInstance | null = null; - private started = false; - - async start(): Promise { - if (this.started) return true; - const nativeModule = getNativeModule(); - if (!nativeModule?.EvdevHook) { - return false; - } - const Native = nativeModule.EvdevHook; - const instance = new Native((event) => this.dispatch(event)); - const opened = instance.start(); - if (!opened) { - instance.stop(); - return false; - } - this.nativeInstance = instance; - this.started = true; - return true; - } - - stop(): void { - if (!this.started) return; - this.started = false; - const instance = this.nativeInstance; - this.nativeInstance = null; - if (instance) { - try { - instance.stop(); - } catch {} - } - } - - private dispatch(event: NativeEvent): void { - if (event.type === 'keydown' || event.type === 'keyup') { - this.emit('key', event); - } else { - this.emit('mouse', event); - } - } -} - -let singleton: EvdevHook | null = null; - -export function getEvdevHook(): EvdevHook { - if (!singleton) singleton = new EvdevHook(); - return singleton; -} - -export function nameToEvdevKeycode(name: string | undefined | null): number { - if (!name) return 0; - const nativeModule = getNativeModule(); - if (nativeModule?.nameToEvdevKeycode) { - return nativeModule.nameToEvdevKeycode(name); - } - return 0; -} diff --git a/fluxer_desktop/src/main/GlobalKeyHook.ts b/fluxer_desktop/src/main/GlobalKeyHook.ts deleted file mode 100644 index 65d5214a6..000000000 --- a/fluxer_desktop/src/main/GlobalKeyHook.ts +++ /dev/null @@ -1,855 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -import {spawn} from 'node:child_process'; -import {accessSync, constants as fsConstants} from 'node:fs'; -import {readdir, readFile} from 'node:fs/promises'; -import {createRequire} from 'node:module'; -import {userInfo} from 'node:os'; -import path from 'node:path'; -import {createChildLogger} from '@electron/common/Logger'; -import {type EvdevKeyEvent, type EvdevMouseEvent, getEvdevHook, nameToEvdevKeycode} from '@electron/main/EvdevHook'; -import {GlobalKeyHookLifecycle} from '@electron/main/GlobalKeyHookLifecycle'; -import {getLinuxInputHookMode} from '@electron/main/LaunchOptions'; -import { - hasLinuxEvdevAccess, - isFullKeyboardKeyCapabilities, - type LinuxEvdevAccessProbe, -} from '@electron/main/LinuxInputAccess'; -import {isFlatpakRuntime} from '@electron/main/LinuxSandbox'; -import {getTccStatus} from '@electron/main/MacTcc'; -import {getMainWindow} from '@electron/main/Window'; -import {ipcMain} from 'electron'; - -const logger = createChildLogger('GlobalKeyHook'); -const requireModule = createRequire(import.meta.url); - -interface NativeInputEvent { - type: 'keydown' | 'keyup' | 'mousedown' | 'mouseup' | 'mousemove' | 'wheel'; - keycode?: number; - keyName?: string; - button?: number; - deltaX?: number; - deltaY?: number; - x?: number; - y?: number; - ctrlKey: boolean; - altKey: boolean; - shiftKey: boolean; - metaKey: boolean; -} - -interface NativeInputHookCtor { - new ( - callback: (event: NativeInputEvent) => void, - ): { - start(): void; - stop(): void; - }; -} - -interface EvdevHookController { - start(): Promise; - on(event: 'key', listener: (event: EvdevKeyEvent) => void): this; - on(event: 'mouse', listener: (event: EvdevMouseEvent) => void): this; -} - -interface NativeInputHookModule { - InputHook: NativeInputHookCtor | null; - isAvailable: () => boolean; - hasAccessibilityPermission?: () => boolean; - loadError: Error | null; -} - -interface KeybindRegistration { - id: string; - description: string | null; - keycode: number; - keyName: string | null; - mouseButton?: number; - modifiers: { - ctrl: boolean; - alt: boolean; - shift: boolean; - meta: boolean; - }; -} - -type Backend = 'evdev' | 'native' | null; - -const registeredKeybinds = new Map(); -const activeKeybindPresses = new Set(); - -let activeBackend: Backend = null; -let activeNativeInstance: {start(): void; stop(): void} | null = null; - -function nativeModuleNameForPlatform(): string | null { - switch (process.platform) { - case 'linux': - return '@fluxer/linux-input-hook'; - case 'darwin': - return '@fluxer/macos-input-hook'; - case 'win32': - return '@fluxer/windows-input-hook'; - default: - return null; - } -} - -function loadNativeInputHookModule(): NativeInputHookModule | null { - const moduleName = nativeModuleNameForPlatform(); - if (moduleName === null) return null; - if (process.platform === 'linux') { - const mode = getLinuxInputHookMode(process.argv); - if (mode === 'off' || mode === 'evdev') return null; - } - let required: NativeInputHookModule; - try { - required = requireModule(moduleName) as NativeInputHookModule; - } catch (error) { - throw new Error( - `${moduleName} failed to load — this is a packaging bug, not a runtime fallback case. ` + - `Original error: ${error instanceof Error ? error.message : String(error)}`, - ); - } - if (!required.InputHook) { - throw new Error( - `${moduleName} loaded but exports no InputHook — native binary did not register module. ` + - `Underlying loadError: ${required.loadError ? required.loadError.message : ''}`, - ); - } - return required; -} - -function keycodeToKeyName(keycode: number): string { - return `Key${keycode}`; -} - -const EVDEV_CHARACTER_KEY_NAMES: Record = { - ' ': 'Space', - '-': 'Minus', - _: 'Minus', - '=': 'Equal', - '+': 'Equal', - ',': 'Comma', - '<': 'Comma', - '.': 'Period', - '>': 'Period', - ';': 'Semicolon', - ':': 'Semicolon', - "'": 'Quote', - '"': 'Quote', - '/': 'Slash', - '?': 'Slash', - '\\': 'Backslash', - '|': 'Backslash', - '[': 'BracketLeft', - '{': 'BracketLeft', - ']': 'BracketRight', - '}': 'BracketRight', - '`': 'Backquote', - '~': 'Backquote', -}; - -function normalizeEvdevKeyName(name: string | null): string | null { - if (!name) return null; - const mappedCharacter = EVDEV_CHARACTER_KEY_NAMES[name]; - if (mappedCharacter) return mappedCharacter; - const keyCodeMatch = /^Key([A-Z])$/.exec(name); - if (keyCodeMatch) return keyCodeMatch[1] ?? null; - const digitCodeMatch = /^Digit([0-9])$/.exec(name); - if (digitCodeMatch) return digitCodeMatch[1] ?? null; - if (name === 'Esc') return 'Escape'; - if (name === 'Spacebar') return 'Space'; - if (name === 'Break') return 'Pause'; - if (/^[a-z]$/.test(name)) return name.toUpperCase(); - return name; -} - -function dispatchKeyEvent(event: { - type: 'keydown' | 'keyup'; - keycode: number; - keyName: string; - ctrlKey: boolean; - altKey: boolean; - shiftKey: boolean; - metaKey: boolean; -}): void { - const mainWindow = getMainWindow(); - if (!mainWindow) return; - const eventWithBackend = {...event, backend: activeBackend}; - mainWindow.webContents.send('global-key-event', eventWithBackend); - for (const [id, keybind] of registeredKeybinds) { - if (!keyEventMatchesRegistration(keybind, eventWithBackend)) continue; - if (event.type === 'keyup') { - if (!activeKeybindPresses.delete(id)) continue; - dispatchGlobalKeybindTriggered(id, event.type); - continue; - } - if (activeKeybindPresses.has(id)) continue; - const modifiersMatch = - keybind.modifiers.ctrl === event.ctrlKey && - keybind.modifiers.alt === event.altKey && - keybind.modifiers.shift === event.shiftKey && - keybind.modifiers.meta === event.metaKey; - if (modifiersMatch || !Object.values(keybind.modifiers).some(Boolean)) { - activeKeybindPresses.add(id); - dispatchGlobalKeybindTriggered(id, event.type); - } - } -} - -function dispatchGlobalKeybindTriggered(id: string, type: 'keydown' | 'keyup'): void { - const mainWindow = getMainWindow(); - if (!mainWindow) return; - mainWindow.webContents.send('global-keybind-triggered', {id, type}); -} - -function shouldPreferPhysicalKeyNameForRegistration(): boolean { - return activeBackend === 'evdev' || (activeBackend === 'native' && process.platform === 'darwin'); -} - -const observedMouseButtons = new Set(); - -function keyEventMatchesRegistration( - keybind: Pick, - event: Pick, -): boolean { - if (keybind.keyName !== null && event.keyName !== null && keybind.keyName === event.keyName) { - return true; - } - return keybind.keycode !== 0 && keybind.keycode === event.keycode; -} - -function registrationModifiersMatch( - expected: KeybindRegistration['modifiers'], - event: { - ctrlKey: boolean; - altKey: boolean; - shiftKey: boolean; - metaKey: boolean; - }, -): boolean { - const exact = - expected.ctrl === event.ctrlKey && - expected.alt === event.altKey && - expected.shift === event.shiftKey && - expected.meta === event.metaKey; - return exact || !Object.values(expected).some(Boolean); -} - -function dispatchMouseEvent(event: { - type: 'mousedown' | 'mouseup'; - button: number; - ctrlKey: boolean; - altKey: boolean; - shiftKey: boolean; - metaKey: boolean; -}): void { - if (event.type === 'mousedown' && !observedMouseButtons.has(event.button)) { - observedMouseButtons.add(event.button); - } - const mainWindow = getMainWindow(); - if (!mainWindow) return; - mainWindow.webContents.send('global-mouse-event', event); - for (const [id, keybind] of registeredKeybinds) { - if (keybind.mouseButton !== event.button) continue; - if (event.type === 'mouseup') { - if (!activeKeybindPresses.delete(id)) continue; - dispatchGlobalKeybindTriggered(id, 'keyup'); - continue; - } - if (activeKeybindPresses.has(id)) continue; - if (registrationModifiersMatch(keybind.modifiers, event)) { - activeKeybindPresses.add(id); - dispatchGlobalKeybindTriggered(id, 'keydown'); - } - } -} - -function handleNativeEvent(event: NativeInputEvent): void { - switch (event.type) { - case 'keydown': - case 'keyup': { - dispatchKeyEvent({ - type: event.type, - keycode: event.keycode ?? 0, - keyName: event.keyName ?? keycodeToKeyName(event.keycode ?? 0), - ctrlKey: event.ctrlKey, - altKey: event.altKey, - shiftKey: event.shiftKey, - metaKey: event.metaKey, - }); - return; - } - case 'mousedown': - case 'mouseup': { - if (event.button === undefined) return; - dispatchMouseEvent({ - type: event.type, - button: event.button, - ctrlKey: event.ctrlKey, - altKey: event.altKey, - shiftKey: event.shiftKey, - metaKey: event.metaKey, - }); - return; - } - case 'mousemove': - case 'wheel': - return; - } -} - -async function startEvdevBackend(): Promise { - if (process.platform !== 'linux') return false; - let evdev: EvdevHookController | null = null; - let ok = false; - try { - evdev = getEvdevHook(); - ok = await evdev.start(); - } catch (error) { - logger.warn('Failed to start evdev input backend', {error}); - return false; - } - if (!ok || !evdev) { - return false; - } - evdev.on('key', (event: EvdevKeyEvent) => dispatchKeyEvent(event)); - evdev.on('mouse', (event: EvdevMouseEvent) => dispatchMouseEvent(event)); - logger.info('Global key hook running on evdev backend (Linux)'); - return true; -} - -async function reopenEvdevDevices(): Promise { - if (activeBackend !== 'evdev') return; - const evdev = getEvdevHook(); - evdev.stop(); - if (!(await evdev.start())) { - logger.warn('evdev input backend did not reopen after input access changed'); - } -} - -function startNativeBackend(): boolean { - const moduleName = nativeModuleNameForPlatform(); - if (moduleName === null) return false; - let module: NativeInputHookModule; - try { - const loaded = loadNativeInputHookModule(); - if (!loaded?.InputHook) return false; - module = loaded; - } catch (error) { - logger.error('Failed to load native input hook module:', error); - return false; - } - try { - const Ctor = module.InputHook; - if (!Ctor) return false; - const instance = new Ctor((event: NativeInputEvent) => { - try { - handleNativeEvent(event); - } catch (err) { - logger.error('handleNativeEvent threw:', err); - } - }); - instance.start(); - activeNativeInstance = instance; - logger.info(`Global key hook running on ${moduleName}`); - return true; - } catch (error) { - logger.error('Failed to start native input hook:', error); - return false; - } -} - -async function startHook(): Promise { - if (activeBackend !== null) return true; - if (process.platform === 'linux') { - const mode = getLinuxInputHookMode(process.argv); - const wayland = isWaylandSession(); - if (mode === 'off') { - logger.warn('Global key hook disabled by launch diagnostics'); - return false; - } - if (mode === 'evdev') { - if (await startEvdevBackend()) { - activeBackend = 'evdev'; - return true; - } - return false; - } - if (mode === 'native') { - if (wayland) { - logger.warn('Refusing X11 native input hook on Wayland; evdev input access is required'); - return false; - } - if (startNativeBackend()) { - activeBackend = 'native'; - return true; - } - return false; - } - const probe = await probeLinuxEvdevAccess(); - const keyboardsReadable = hasLinuxEvdevAccess(probe); - if (!keyboardsReadable) { - logger.warn('Not every keyboard input device is readable', { - keyboardDevices: probe.keyboardDevices, - readableKeyboardDevices: probe.readableKeyboardDevices, - readableEventDevices: probe.readableEventDevices, - }); - } - if ((keyboardsReadable || wayland) && (await startEvdevBackend())) { - activeBackend = 'evdev'; - return true; - } - if (wayland) { - logger.warn('No evdev input devices are readable; system-wide shortcuts are unavailable on Wayland'); - return false; - } - } - if (startNativeBackend()) { - activeBackend = 'native'; - return true; - } - return false; -} - -function isWaylandSession(): boolean { - if (process.platform !== 'linux') return false; - return Boolean(process.env.WAYLAND_DISPLAY) || process.env.XDG_SESSION_TYPE === 'wayland'; -} - -const hookLifecycle = new GlobalKeyHookLifecycle({ - start: startHook, - stop: stopHook, -}); - -const trackedSenderIds = new Set(); - -function releaseSenderState(senderId: number): void { - registeredKeybinds.clear(); - activeKeybindPresses.clear(); - void hookLifecycle.releaseAllForOwner(senderId); -} - -function trackSender(sender: Electron.WebContents): void { - if (trackedSenderIds.has(sender.id)) return; - trackedSenderIds.add(sender.id); - const senderId = sender.id; - sender.once('destroyed', () => { - trackedSenderIds.delete(senderId); - releaseSenderState(senderId); - }); - sender.on('did-navigate', () => { - releaseSenderState(senderId); - }); - sender.on('render-process-gone', () => { - releaseSenderState(senderId); - }); -} - -function stopHook(): void { - observedMouseButtons.clear(); - activeKeybindPresses.clear(); - if (activeBackend === 'evdev') { - const evdev = getEvdevHook(); - evdev.removeAllListeners('key'); - evdev.removeAllListeners('mouse'); - evdev.stop(); - } else if (activeBackend === 'native' && activeNativeInstance) { - try { - activeNativeInstance.stop(); - } catch (error) { - logger.error('Failed to stop native input hook:', error); - } - activeNativeInstance = null; - } - activeBackend = null; -} - -function getInputMonitoringStatus(): string | null { - try { - return getTccStatus('input-monitoring'); - } catch (error) { - logger.error('Failed to query Input Monitoring auth status:', error); - return null; - } -} - -function preflightInputMonitoringAccess(): boolean | null { - let module: NativeInputHookModule | null = null; - try { - module = loadNativeInputHookModule(); - } catch (error) { - logger.warn('Failed to load native input hook module for Input Monitoring preflight:', error); - return null; - } - if (!module?.hasAccessibilityPermission) return null; - try { - return module.hasAccessibilityPermission(); - } catch (error) { - logger.warn('Input Monitoring preflight threw:', error); - return null; - } -} - -async function checkInputMonitoringAccess(): Promise { - if (process.platform !== 'darwin') { - return true; - } - if (activeBackend !== null) { - return true; - } - const preflight = preflightInputMonitoringAccess(); - if (preflight !== null) { - if (!preflight) { - logger.warn('Input Monitoring listen-event access is not granted (CGPreflightListenEventAccess)'); - } - return preflight; - } - const status = getInputMonitoringStatus(); - if (status === null) { - logger.warn('Input Monitoring access could not be checked; refusing to start macOS native input hook'); - return false; - } - if (status === 'granted') { - return true; - } - logger.warn('Input Monitoring access is not granted, status:', status); - return false; -} - -async function probeLinuxEvdevAccess(): Promise { - const probe: LinuxEvdevAccessProbe = { - totalEventDevices: 0, - readableEventDevices: 0, - keyboardDevices: 0, - readableKeyboardDevices: 0, - inInputGroup: true, - }; - if (process.platform !== 'linux') return probe; - probe.inInputGroup = isFlatpakRuntime() ? false : await isUserInInputGroup(); - let entries: Array; - try { - entries = await readdir('/dev/input'); - } catch { - return probe; - } - for (const name of entries) { - if (!name.startsWith('event')) continue; - probe.totalEventDevices += 1; - let readable = false; - try { - accessSync(path.join('/dev/input', name), fsConstants.R_OK); - readable = true; - } catch {} - if (readable) probe.readableEventDevices += 1; - if (await isKeyboardEventDevice(name)) { - probe.keyboardDevices += 1; - if (readable) probe.readableKeyboardDevices += 1; - } - } - return probe; -} - -async function isKeyboardEventDevice(name: string): Promise { - try { - const capabilities = await readFile(path.join('/sys/class/input', name, 'device/capabilities/key'), 'utf8'); - return isFullKeyboardKeyCapabilities(capabilities); - } catch { - return false; - } -} - -async function isUserInInputGroup(): Promise { - if (process.platform !== 'linux') return false; - if (typeof process.getgroups !== 'function') return false; - let groupFile: string; - try { - groupFile = await readFile('/etc/group', 'utf8'); - } catch { - return false; - } - let inputGid: number | null = null; - for (const line of groupFile.split('\n')) { - if (!line.startsWith('input:')) continue; - const parts = line.split(':'); - const gid = Number.parseInt(parts[2] ?? '', 10); - if (Number.isFinite(gid)) inputGid = gid; - break; - } - if (inputGid === null) return false; - let groups: Array; - try { - groups = process.getgroups(); - } catch { - return false; - } - return groups.includes(inputGid); -} - -async function _collectLinuxEvdevDiagnostics(): Promise> { - if (process.platform !== 'linux') { - return {platform: process.platform}; - } - const probe = await probeLinuxEvdevAccess(); - return { - platform: process.platform, - waylandSession: isWaylandSession(), - flatpak: isFlatpakRuntime(), - totalEventDevices: probe.totalEventDevices, - readableEventDevices: probe.readableEventDevices, - keyboardDevices: probe.keyboardDevices, - readableKeyboardDevices: probe.readableKeyboardDevices, - inInputGroup: probe.inInputGroup, - }; -} - -const PKEXEC_CANDIDATE_PATHS = ['/usr/bin/pkexec', '/usr/local/bin/pkexec', '/bin/pkexec']; - -function findPkexec(): string | null { - for (const candidate of PKEXEC_CANDIDATE_PATHS) { - try { - accessSync(candidate, fsConstants.X_OK); - return candidate; - } catch {} - } - return null; -} - -interface LinuxEvdevStatus { - supported: boolean; - hasAccess: boolean; - canPrompt: boolean; - sandboxed: boolean; - username: string | null; - totalEventDevices: number; - readableEventDevices: number; - inInputGroup: boolean; -} - -async function getLinuxEvdevStatus(): Promise { - if (process.platform !== 'linux') { - return { - supported: false, - hasAccess: true, - canPrompt: false, - sandboxed: false, - username: null, - totalEventDevices: 0, - readableEventDevices: 0, - inInputGroup: true, - }; - } - const probe = await probeLinuxEvdevAccess(); - const hasAccess = hasLinuxEvdevAccess(probe); - let username: string | null = null; - try { - username = userInfo().username; - } catch { - username = process.env.USER ?? null; - } - return { - supported: true, - hasAccess, - canPrompt: !isFlatpakRuntime() && !probe.inInputGroup && findPkexec() !== null && !!username, - sandboxed: isFlatpakRuntime(), - username, - totalEventDevices: probe.totalEventDevices, - readableEventDevices: probe.readableEventDevices, - inInputGroup: probe.inInputGroup, - }; -} - -interface LinuxEvdevGrantResult { - success: boolean; - needsRelogin: boolean; - error?: string; -} - -function pkexecFailureReason(code: number | null, signal: NodeJS.Signals | null): string { - return code === 126 || code === 127 - ? 'Authorization was cancelled or no polkit agent is running.' - : signal - ? `pkexec was terminated by ${signal}` - : `pkexec exited with code ${code}`; -} - -function runPkexec(args: Array): Promise<{success: boolean; error?: string}> { - const pkexec = findPkexec(); - if (!pkexec) { - return Promise.resolve({success: false, error: 'Authorization helper is not installed.'}); - } - return new Promise((resolve) => { - const child = spawn(pkexec, args, {stdio: 'ignore'}); - child.on('error', (error) => { - logger.error('Failed to spawn pkexec', error); - resolve({success: false, error: error.message}); - }); - child.on('exit', (code, signal) => { - if (code === 0) { - resolve({success: true}); - return; - } - resolve({success: false, error: pkexecFailureReason(code, signal)}); - }); - }); -} - -async function waitForLinuxEvdevAccessRefresh(): Promise { - await new Promise((resolve) => setTimeout(resolve, 700)); - return probeLinuxEvdevAccess(); -} - -function linuxUaccessGrantScript(): string { - return [ - 'set -eu', - 'target_user="$1"', - 'install -d -m 0755 /etc/udev/rules.d', - "cat > /etc/udev/rules.d/70-fluxer-input.rules <<'EOF'", - '# Grants the active local desktop user access to input devices for Fluxer system-wide shortcuts.', - 'KERNEL=="event*", SUBSYSTEM=="input", TAG+="uaccess"', - 'EOF', - 'if command -v udevadm >/dev/null 2>&1; then', - ' udevadm control --reload-rules || true', - ' udevadm trigger --subsystem-match=input --action=change || true', - ' udevadm settle --timeout=3 || true', - 'fi', - 'if command -v setfacl >/dev/null 2>&1; then', - ' for device in /dev/input/event*; do', - ' [ -e "$device" ] || continue', - ` setfacl -m "u:\${target_user}:r" "$device" || true`, - ' done', - 'fi', - ].join('\n'); -} - -async function grantLinuxEvdevAccess(): Promise { - if (process.platform !== 'linux') { - return {success: false, needsRelogin: false, error: 'Not a Linux session'}; - } - if (isFlatpakRuntime()) { - return {success: false, needsRelogin: false, error: 'Enable Flatpak input device access, then restart Fluxer.'}; - } - if (!findPkexec()) { - return { - success: false, - needsRelogin: false, - error: 'Authorization helper is not installed.', - }; - } - const before = await probeLinuxEvdevAccess(); - if (hasLinuxEvdevAccess(before)) { - return {success: true, needsRelogin: false}; - } - const username = (() => { - try { - return userInfo().username; - } catch { - return process.env.USER ?? ''; - } - })(); - if (!username) { - return {success: false, needsRelogin: false, error: 'Could not resolve current username'}; - } - const uaccess = await runPkexec(['/bin/sh', '-c', linuxUaccessGrantScript(), 'fluxer-input-access', username]); - if (!uaccess.success) { - return {success: false, needsRelogin: false, error: uaccess.error}; - } - const afterUaccess = await waitForLinuxEvdevAccessRefresh(); - if (hasLinuxEvdevAccess(afterUaccess)) { - await reopenEvdevDevices(); - return {success: true, needsRelogin: false}; - } - const groupFallback = await runPkexec(['usermod', '-aG', 'input', username]); - if (groupFallback.success) { - return {success: true, needsRelogin: true}; - } - return { - success: false, - needsRelogin: false, - error: groupFallback.error ?? 'Input access could not be enabled automatically.', - }; -} - -export function registerGlobalKeyHookHandlers(): void { - ipcMain.handle('global-key-hook-start', async (event): Promise => { - if (!(await checkInputMonitoringAccess())) { - return false; - } - trackSender(event.sender); - return hookLifecycle.acquire(event.sender.id); - }); - ipcMain.handle('global-key-hook-stop', async (event): Promise => { - await hookLifecycle.release(event.sender.id); - }); - ipcMain.handle('global-key-hook-is-running', (): boolean => { - return hookLifecycle.isRunning(); - }); - ipcMain.handle('check-input-monitoring-access', async (): Promise => { - return checkInputMonitoringAccess(); - }); - ipcMain.handle( - 'global-key-hook-register', - ( - _event, - options: { - id: string; - keycode?: number; - keyName?: string; - physicalKeyName?: string; - mouseButton?: number; - description?: string; - ctrl?: boolean; - alt?: boolean; - shift?: boolean; - meta?: boolean; - }, - ): void => { - let keyName: string | null = options.keyName ?? null; - if (shouldPreferPhysicalKeyNameForRegistration() && options.physicalKeyName) { - keyName = options.physicalKeyName; - } - if (!keyName && options.keycode != null && options.keycode !== 0) { - keyName = keycodeToKeyName(options.keycode); - } - if (activeBackend === 'evdev') { - keyName = normalizeEvdevKeyName(keyName); - } - let evdevKeycode = 0; - if (keyName) evdevKeycode = nameToEvdevKeycode(keyName); - registeredKeybinds.set(options.id, { - id: options.id, - description: options.description ?? null, - keycode: activeBackend === 'evdev' ? evdevKeycode : (options.keycode ?? 0), - keyName, - mouseButton: options.mouseButton, - modifiers: { - ctrl: options.ctrl ?? false, - alt: options.alt ?? false, - shift: options.shift ?? false, - meta: options.meta ?? false, - }, - }); - }, - ); - ipcMain.handle('global-key-hook-unregister', (_event, id: string): void => { - registeredKeybinds.delete(id); - activeKeybindPresses.delete(id); - }); - ipcMain.handle('global-key-hook-unregister-all', (): void => { - registeredKeybinds.clear(); - activeKeybindPresses.clear(); - }); - ipcMain.handle('linux-evdev-status', async (): Promise => { - return getLinuxEvdevStatus(); - }); - ipcMain.handle('linux-evdev-grant-access', async (): Promise => { - return grantLinuxEvdevAccess(); - }); -} - -export function cleanupGlobalKeyHook(): void { - void hookLifecycle.forceStop(); - registeredKeybinds.clear(); - activeKeybindPresses.clear(); -} diff --git a/fluxer_desktop/src/main/GlobalKeyHookLifecycle.test.mjs b/fluxer_desktop/src/main/GlobalKeyHookLifecycle.test.mjs deleted file mode 100644 index 04ed9b927..000000000 --- a/fluxer_desktop/src/main/GlobalKeyHookLifecycle.test.mjs +++ /dev/null @@ -1,176 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -import assert from 'node:assert/strict'; -import {readFileSync} from 'node:fs'; -import {createRequire} from 'node:module'; -import {describe, test} from 'node:test'; -import {fileURLToPath} from 'node:url'; -import vm from 'node:vm'; - -const require = createRequire(import.meta.url); -const esbuild = require('esbuild'); - -const sourcePath = fileURLToPath(new URL('./GlobalKeyHookLifecycle.ts', import.meta.url)); -const source = readFileSync(sourcePath, 'utf8'); -const transformedSource = esbuild.transformSync(source, { - loader: 'ts', - format: 'cjs', - platform: 'node', - target: 'node20', -}).code; - -function loadGlobalKeyHookLifecycle() { - const module = {exports: {}}; - const context = vm.createContext({ - module, - exports: module.exports, - }); - vm.runInContext(transformedSource, context, {filename: sourcePath}); - return module.exports; -} - -const {GlobalKeyHookLifecycle} = loadGlobalKeyHookLifecycle(); - -function createBackend({startResults = [true]} = {}) { - const calls = []; - let startIndex = 0; - return { - calls, - backend: { - start() { - calls.push('start'); - const result = startResults[Math.min(startIndex, startResults.length - 1)]; - startIndex += 1; - return Promise.resolve(result); - }, - stop() { - calls.push('stop'); - }, - }, - }; -} - -describe('GlobalKeyHookLifecycle', () => { - test('first acquire starts the backend once and reports running', async () => { - const {backend, calls} = createBackend(); - const lifecycle = new GlobalKeyHookLifecycle(backend); - assert.equal(await lifecycle.acquire(1), true); - assert.equal(await lifecycle.acquire(1), true); - assert.deepEqual(calls, ['start']); - assert.equal(lifecycle.isRunning(), true); - assert.equal(lifecycle.acquisitionCount(), 2); - }); - - test('release keeps the backend running while other acquisitions remain', async () => { - const {backend, calls} = createBackend(); - const lifecycle = new GlobalKeyHookLifecycle(backend); - await lifecycle.acquire(1); - await lifecycle.acquire(1); - await lifecycle.release(1); - assert.deepEqual(calls, ['start']); - assert.equal(lifecycle.isRunning(), true); - await lifecycle.release(1); - assert.deepEqual(calls, ['start', 'stop']); - assert.equal(lifecycle.isRunning(), false); - }); - - test('recorder and runtime sharing one owner survive the recorder closing', async () => { - const {backend, calls} = createBackend(); - const lifecycle = new GlobalKeyHookLifecycle(backend); - assert.equal(await lifecycle.acquire(7), true); - assert.equal(await lifecycle.acquire(7), true); - await lifecycle.release(7); - assert.equal(lifecycle.isRunning(), true); - assert.deepEqual(calls, ['start']); - }); - - test('failed start does not record an acquisition and allows retry', async () => { - const {backend, calls} = createBackend({startResults: [false, true]}); - const lifecycle = new GlobalKeyHookLifecycle(backend); - assert.equal(await lifecycle.acquire(1), false); - assert.equal(lifecycle.isRunning(), false); - assert.equal(lifecycle.acquisitionCount(), 0); - assert.equal(await lifecycle.acquire(1), true); - assert.equal(lifecycle.isRunning(), true); - assert.deepEqual(calls, ['start', 'start']); - }); - - test('start that throws is treated as a failed acquire', async () => { - const lifecycle = new GlobalKeyHookLifecycle({ - start() { - return Promise.reject(new Error('boom')); - }, - stop() {}, - }); - assert.equal(await lifecycle.acquire(1), false); - assert.equal(lifecycle.isRunning(), false); - }); - - test('release without a matching acquire is a no-op', async () => { - const {backend, calls} = createBackend(); - const lifecycle = new GlobalKeyHookLifecycle(backend); - await lifecycle.release(1); - assert.deepEqual(calls, []); - await lifecycle.acquire(1); - await lifecycle.release(2); - assert.equal(lifecycle.isRunning(), true); - await lifecycle.release(1); - assert.equal(lifecycle.isRunning(), false); - assert.deepEqual(calls, ['start', 'stop']); - }); - - test('releaseAllForOwner drops only that owner', async () => { - const {backend, calls} = createBackend(); - const lifecycle = new GlobalKeyHookLifecycle(backend); - await lifecycle.acquire(1); - await lifecycle.acquire(1); - await lifecycle.acquire(2); - await lifecycle.releaseAllForOwner(1); - assert.equal(lifecycle.isRunning(), true); - assert.equal(lifecycle.acquisitionCount(), 1); - await lifecycle.releaseAllForOwner(2); - assert.equal(lifecycle.isRunning(), false); - assert.deepEqual(calls, ['start', 'stop']); - }); - - test('forceStop clears every acquisition and stops the backend', async () => { - const {backend, calls} = createBackend(); - const lifecycle = new GlobalKeyHookLifecycle(backend); - await lifecycle.acquire(1); - await lifecycle.acquire(2); - await lifecycle.forceStop(); - assert.equal(lifecycle.isRunning(), false); - assert.equal(lifecycle.acquisitionCount(), 0); - assert.deepEqual(calls, ['start', 'stop']); - }); - - test('operations issued while a slow start is pending stay ordered', async () => { - const calls = []; - let resolveStart; - const lifecycle = new GlobalKeyHookLifecycle({ - start() { - calls.push('start'); - return new Promise((resolve) => { - resolveStart = resolve; - }); - }, - stop() { - calls.push('stop'); - }, - }); - const first = lifecycle.acquire(1); - const second = lifecycle.acquire(1); - const release = lifecycle.release(1); - const releaseLast = lifecycle.release(1); - while (resolveStart === undefined) { - await Promise.resolve(); - } - resolveStart(true); - assert.equal(await first, true); - assert.equal(await second, true); - await release; - await releaseLast; - assert.equal(lifecycle.isRunning(), false); - assert.deepEqual(calls, ['start', 'stop']); - }); -}); diff --git a/fluxer_desktop/src/main/GlobalKeyHookLifecycle.ts b/fluxer_desktop/src/main/GlobalKeyHookLifecycle.ts deleted file mode 100644 index b8b240190..000000000 --- a/fluxer_desktop/src/main/GlobalKeyHookLifecycle.ts +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -export interface GlobalKeyHookBackend { - start(): Promise | boolean; - stop(): void; -} - -export class GlobalKeyHookLifecycle { - private readonly acquisitionsByOwner = new Map(); - private running = false; - private queue: Promise = Promise.resolve(); - - constructor(private readonly backend: GlobalKeyHookBackend) {} - - isRunning(): boolean { - return this.running; - } - - acquisitionCount(): number { - let total = 0; - for (const count of this.acquisitionsByOwner.values()) { - total += count; - } - return total; - } - - acquire(ownerId: number): Promise { - return this.enqueue(async () => { - if (!this.running) { - let started = false; - try { - started = await this.backend.start(); - } catch { - started = false; - } - if (!started) return false; - this.running = true; - } - this.acquisitionsByOwner.set(ownerId, (this.acquisitionsByOwner.get(ownerId) ?? 0) + 1); - return true; - }); - } - - release(ownerId: number): Promise { - return this.enqueue(async () => { - const count = this.acquisitionsByOwner.get(ownerId) ?? 0; - if (count === 0) return; - if (count === 1) { - this.acquisitionsByOwner.delete(ownerId); - } else { - this.acquisitionsByOwner.set(ownerId, count - 1); - } - this.stopIfIdle(); - }); - } - - releaseAllForOwner(ownerId: number): Promise { - return this.enqueue(async () => { - if (!this.acquisitionsByOwner.delete(ownerId)) return; - this.stopIfIdle(); - }); - } - - forceStop(): Promise { - return this.enqueue(async () => { - this.acquisitionsByOwner.clear(); - this.stopIfIdle(); - }); - } - - private stopIfIdle(): void { - if (!this.running) return; - if (this.acquisitionCount() > 0) return; - this.running = false; - try { - this.backend.stop(); - } catch {} - } - - private enqueue(task: () => Promise): Promise { - const run = this.queue.then(task); - this.queue = run.then( - () => undefined, - () => undefined, - ); - return run; - } -} diff --git a/fluxer_desktop/src/main/GlobalShortcutKeys.test.mjs b/fluxer_desktop/src/main/GlobalShortcutKeys.test.mjs new file mode 100644 index 000000000..126f25e04 --- /dev/null +++ b/fluxer_desktop/src/main/GlobalShortcutKeys.test.mjs @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; +import {describe, test} from 'node:test'; +import {loadTsModule} from './fixtures/TsModuleLoader.mjs'; + +const keys = loadTsModule('@electron/main/GlobalShortcutKeys'); + +function combo(overrides) { + return {key: '', ctrl: false, alt: false, shift: false, meta: false, ...overrides}; +} + +describe('GlobalShortcutKeys physical tables', () => { + test('evdev KEY codes map to DOM codes', () => { + assert.equal(keys.evdevKeyToDomCode(30), 'KeyA'); + assert.equal(keys.evdevKeyToDomCode(50), 'KeyM'); + assert.equal(keys.evdevKeyToDomCode(3), 'Digit2'); + assert.equal(keys.evdevKeyToDomCode(57), 'Space'); + assert.equal(keys.evdevKeyToDomCode(97), 'ControlRight'); + assert.equal(keys.evdevKeyToDomCode(183), 'F13'); + assert.equal(keys.evdevKeyToDomCode(0x7fff), null); + }); + + test('every key the native evdev keymap names resolves to the same DOM code in main', () => { + const source = readFileSync(new URL('../../native/linux-evdev/src/keymap.rs', import.meta.url), 'utf8'); + const table = source.slice(source.indexOf('pub const KEY_MAP'), source.indexOf('];')); + const entries = [...table.matchAll(/\((\d+), "([^"]+)"\)/g)].map((match) => [Number(match[1]), match[2]]); + assert.ok(entries.length > 100); + for (const [keycode, name] of entries) { + const code = keys.evdevKeyToDomCode(keycode); + assert.notEqual(code, null, `evdev ${keycode} ${name}`); + assert.equal(code, keys.hookKeyNameToDomCode(name), `evdev ${keycode} ${name}`); + assert.equal(keys.x11KeycodeToDomCode(keycode + 8), code, `x11 ${keycode + 8} ${name}`); + } + assert.equal(keys.evdevKeyToDomCode(148), 'LaunchApp1'); + assert.equal(keys.evdevKeyToDomCode(149), 'LaunchApp2'); + assert.equal(keys.evdevKeyToDomCode(226), 'MediaSelect'); + assert.equal(keys.evdevKeyToDomCode(364), 'BrowserFavorites'); + }); + + test('X11 keycodes are evdev codes offset by eight', () => { + assert.equal(keys.x11KeycodeToDomCode(38), 'KeyA'); + assert.equal(keys.x11KeycodeToDomCode(58), 'KeyM'); + assert.equal(keys.x11KeycodeToDomCode(11), 'Digit2'); + assert.equal(keys.x11KeycodeToDomCode(65), 'Space'); + assert.equal(keys.x11KeycodeToDomCode(4), null); + }); + + test('Windows keys use the scan code with the extended flag', () => { + assert.equal(keys.windowsKeyToDomCode(0x1e, false, 0x41), 'KeyA'); + assert.equal(keys.windowsKeyToDomCode(0x32, false, 0x4d), 'KeyM'); + assert.equal(keys.windowsKeyToDomCode(0x1d, false, 0xa2), 'ControlLeft'); + assert.equal(keys.windowsKeyToDomCode(0x1d, true, 0xa3), 'ControlRight'); + assert.equal(keys.windowsKeyToDomCode(0x1c, true, 0x0d), 'NumpadEnter'); + assert.equal(keys.windowsKeyToDomCode(0x45, false, 0x13), 'Pause'); + assert.equal(keys.windowsKeyToDomCode(0x45, true, 0x90), 'NumLock'); + assert.equal(keys.windowsKeyToDomCode(0x54, false, 0x2c), 'PrintScreen'); + assert.equal(keys.windowsKeyToDomCode(0x46, true, 0x13), 'Pause'); + }); + + test('Windows keys fall back to the virtual key when the scan code is missing or unknown', () => { + assert.equal(keys.windowsKeyToDomCode(0, false, 0x7c), 'F13'); + assert.equal(keys.windowsKeyToDomCode(0, false, 0xa0), 'ShiftLeft'); + assert.equal(keys.windowsKeyToDomCode(0x7f, false, 0x26), 'ArrowUp'); + assert.equal(keys.windowsKeyToDomCode(0, false, 0x5a), 'KeyZ'); + assert.equal(keys.windowsKeyToDomCode(0, false, 0x39), 'Digit9'); + assert.equal(keys.windowsKeyToDomCode(0, false, 0x87), 'F24'); + assert.equal(keys.windowsKeyToDomCode(0, false, 0xff), null); + }); + + test('macOS virtual keycodes map to DOM codes', () => { + assert.equal(keys.macosKeycodeToDomCode(0x00), 'KeyA'); + assert.equal(keys.macosKeycodeToDomCode(0x2e), 'KeyM'); + assert.equal(keys.macosKeycodeToDomCode(0x31), 'Space'); + assert.equal(keys.macosKeycodeToDomCode(0x37), 'MetaLeft'); + assert.equal(keys.macosKeycodeToDomCode(0x69), 'F13'); + }); +}); + +describe('GlobalShortcutKeys layout names', () => { + test('layout keys normalise to hook names', () => { + assert.equal(keys.hookKeyNameForLayoutKey('m'), 'M'); + assert.equal(keys.hookKeyNameForLayoutKey('2'), '2'); + assert.equal(keys.hookKeyNameForLayoutKey('-'), 'Minus'); + assert.equal(keys.hookKeyNameForLayoutKey(' '), 'Space'); + assert.equal(keys.hookKeyNameForLayoutKey('é'), null); + }); + + test('legacy hook names convert to DOM codes', () => { + assert.equal(keys.hookKeyNameToDomCode('M'), 'KeyM'); + assert.equal(keys.hookKeyNameToDomCode('1'), 'Digit1'); + assert.equal(keys.hookKeyNameToDomCode('Esc'), 'Escape'); + assert.equal(keys.hookKeyNameToDomCode('NumpadEnter'), 'NumpadEnter'); + assert.equal(keys.hookKeyNameToDomCode('Key291'), null); + assert.equal(keys.hookKeyNameToDomCode('LaunchMediaPlayer'), 'MediaSelect'); + }); + + test('US layout keys map to physical codes', () => { + assert.equal(keys.usLayoutKeyToDomCode('m'), 'KeyM'); + assert.equal(keys.usLayoutKeyToDomCode('M'), 'KeyM'); + assert.equal(keys.usLayoutKeyToDomCode('/'), 'Slash'); + assert.equal(keys.usLayoutKeyToDomCode('F5'), 'F5'); + assert.equal(keys.usLayoutKeyToDomCode('ArrowUp'), 'ArrowUp'); + }); + + test('printable non-numpad keys prefer the layout key', () => { + assert.equal(keys.shouldPreferLayoutKey({key: 'm', code: 'KeyM'}), true); + assert.equal(keys.shouldPreferLayoutKey({key: '1', code: 'Numpad1'}), false); + assert.equal(keys.shouldPreferLayoutKey({key: 'F5', code: 'F5'}), false); + assert.equal(keys.shouldPreferLayoutKey({key: 'Dead', code: 'BracketLeft'}), false); + }); +}); + +describe('comboToXdgTrigger', () => { + test('uses uppercase modifiers and keysym names', () => { + assert.equal(keys.comboToXdgTrigger(combo({key: 'm', code: 'KeyM', ctrl: true, shift: true})), 'CTRL+SHIFT+m'); + assert.equal( + keys.comboToXdgTrigger(combo({key: 'a', ctrl: true, alt: true, shift: true, meta: true})), + 'CTRL+ALT+SHIFT+LOGO+a', + ); + assert.equal(keys.comboToXdgTrigger(combo({key: 'F13', code: 'F13'})), 'F13'); + assert.equal(keys.comboToXdgTrigger(combo({key: ' ', code: 'Space'})), 'space'); + assert.equal(keys.comboToXdgTrigger(combo({key: 'PageDown', code: 'PageDown', alt: true})), 'ALT+Next'); + assert.equal(keys.comboToXdgTrigger(combo({key: '-', code: 'Minus', ctrl: true})), 'CTRL+minus'); + assert.equal(keys.comboToXdgTrigger(combo({key: 'AudioVolumeMute', code: 'AudioVolumeMute'})), 'XF86AudioMute'); + assert.equal(keys.comboToXdgTrigger(combo({key: 'MediaPlayPause', code: 'MediaPlayPause'})), 'XF86AudioPlay'); + }); + + test('letters follow the layout and everything else follows the physical key', () => { + assert.equal(keys.comboToXdgTrigger(combo({key: 'z', code: 'KeyY', ctrl: true})), 'CTRL+z'); + assert.equal(keys.comboToXdgTrigger(combo({key: '!', code: 'Digit1', shift: true})), 'SHIFT+1'); + assert.equal(keys.comboToXdgTrigger(combo({key: ';', code: 'Comma', shift: true})), 'SHIFT+comma'); + assert.equal(keys.comboToXdgTrigger(combo({key: 'ä', code: 'Quote'})), 'apostrophe'); + assert.equal(keys.comboToXdgTrigger(combo({key: ',', code: 'Comma'})), 'comma'); + }); + + test('returns null for combos a portal cannot express', () => { + assert.equal(keys.comboToXdgTrigger(combo({key: '', mouseButton: 3})), null); + assert.equal( + keys.comboToXdgTrigger(combo({key: 'Shift', code: 'ShiftLeft', shift: true, modifierOnly: true})), + null, + ); + assert.equal( + keys.comboToXdgTrigger( + combo({key: 'Control', code: 'ControlLeft', ctrl: true, modifierOnly: true, bothSides: true}), + ), + null, + ); + assert.equal(keys.comboToXdgTrigger(combo({key: ''})), null); + assert.equal(keys.comboToXdgTrigger(combo({key: 'Control', code: 'ControlLeft'})), null); + assert.equal(keys.comboToXdgTrigger(combo({key: 'NumLock', code: 'NumLock'})), null); + assert.equal(keys.comboToXdgTrigger(combo({key: 'CapsLock', code: 'CapsLock'})), null); + assert.equal(keys.comboToXdgTrigger(combo({key: '1', code: 'Numpad1'})), null); + assert.equal(keys.comboToXdgTrigger(combo({key: 'a', code: 'NumpadAdd'})), null); + assert.equal(keys.comboToXdgTrigger(combo({key: '<', code: 'IntlBackslash'})), null); + assert.equal(keys.comboToXdgTrigger(combo({key: 'é', code: 'Unknown'})), null); + assert.equal(keys.comboToXdgTrigger(combo({key: 'AudioVolumeUp', code: 'AudioVolumeUp'})), null); + }); + + test('every emitted key name is a plain keysym identifier', () => { + const codes = [ + ...Array.from({length: 0x250}, (_, index) => keys.evdevKeyToDomCode(index)), + ...Array.from({length: 0x100}, (_, index) => keys.windowsKeyToDomCode(0, false, index)), + ].filter((code) => code !== null); + const pattern = /^(?:(?:CTRL|ALT|SHIFT|LOGO)\+)*[A-Za-z0-9_]+$/; + let emitted = 0; + for (const code of codes) { + for (const key of [code, 'x', '?', ' ']) { + const trigger = keys.comboToXdgTrigger(combo({key, code, ctrl: true})); + if (trigger === null) continue; + emitted += 1; + assert.match(trigger, pattern, `${key}/${code}`); + assert.ok(!/NUM|CAPS|KP_/i.test(trigger.split('+').at(-1)), trigger); + } + } + assert.ok(emitted > 100); + }); +}); diff --git a/fluxer_desktop/src/main/GlobalShortcutKeys.ts b/fluxer_desktop/src/main/GlobalShortcutKeys.ts new file mode 100644 index 000000000..fcdd6f1d1 --- /dev/null +++ b/fluxer_desktop/src/main/GlobalShortcutKeys.ts @@ -0,0 +1,762 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +const EVDEV_KEY_CODES: ReadonlyMap = new Map([ + [0x001, 'Escape'], + [0x002, 'Digit1'], + [0x003, 'Digit2'], + [0x004, 'Digit3'], + [0x005, 'Digit4'], + [0x006, 'Digit5'], + [0x007, 'Digit6'], + [0x008, 'Digit7'], + [0x009, 'Digit8'], + [0x00a, 'Digit9'], + [0x00b, 'Digit0'], + [0x00c, 'Minus'], + [0x00d, 'Equal'], + [0x00e, 'Backspace'], + [0x00f, 'Tab'], + [0x010, 'KeyQ'], + [0x011, 'KeyW'], + [0x012, 'KeyE'], + [0x013, 'KeyR'], + [0x014, 'KeyT'], + [0x015, 'KeyY'], + [0x016, 'KeyU'], + [0x017, 'KeyI'], + [0x018, 'KeyO'], + [0x019, 'KeyP'], + [0x01a, 'BracketLeft'], + [0x01b, 'BracketRight'], + [0x01c, 'Enter'], + [0x01d, 'ControlLeft'], + [0x01e, 'KeyA'], + [0x01f, 'KeyS'], + [0x020, 'KeyD'], + [0x021, 'KeyF'], + [0x022, 'KeyG'], + [0x023, 'KeyH'], + [0x024, 'KeyJ'], + [0x025, 'KeyK'], + [0x026, 'KeyL'], + [0x027, 'Semicolon'], + [0x028, 'Quote'], + [0x029, 'Backquote'], + [0x02a, 'ShiftLeft'], + [0x02b, 'Backslash'], + [0x02c, 'KeyZ'], + [0x02d, 'KeyX'], + [0x02e, 'KeyC'], + [0x02f, 'KeyV'], + [0x030, 'KeyB'], + [0x031, 'KeyN'], + [0x032, 'KeyM'], + [0x033, 'Comma'], + [0x034, 'Period'], + [0x035, 'Slash'], + [0x036, 'ShiftRight'], + [0x037, 'NumpadMultiply'], + [0x038, 'AltLeft'], + [0x039, 'Space'], + [0x03a, 'CapsLock'], + [0x03b, 'F1'], + [0x03c, 'F2'], + [0x03d, 'F3'], + [0x03e, 'F4'], + [0x03f, 'F5'], + [0x040, 'F6'], + [0x041, 'F7'], + [0x042, 'F8'], + [0x043, 'F9'], + [0x044, 'F10'], + [0x045, 'NumLock'], + [0x046, 'ScrollLock'], + [0x047, 'Numpad7'], + [0x048, 'Numpad8'], + [0x049, 'Numpad9'], + [0x04a, 'NumpadSubtract'], + [0x04b, 'Numpad4'], + [0x04c, 'Numpad5'], + [0x04d, 'Numpad6'], + [0x04e, 'NumpadAdd'], + [0x04f, 'Numpad1'], + [0x050, 'Numpad2'], + [0x051, 'Numpad3'], + [0x052, 'Numpad0'], + [0x053, 'NumpadDecimal'], + [0x055, 'Lang5'], + [0x056, 'IntlBackslash'], + [0x057, 'F11'], + [0x058, 'F12'], + [0x059, 'IntlRo'], + [0x05a, 'Lang3'], + [0x05b, 'Lang4'], + [0x05c, 'Convert'], + [0x05d, 'KanaMode'], + [0x05e, 'NonConvert'], + [0x060, 'NumpadEnter'], + [0x061, 'ControlRight'], + [0x062, 'NumpadDivide'], + [0x063, 'PrintScreen'], + [0x064, 'AltRight'], + [0x066, 'Home'], + [0x067, 'ArrowUp'], + [0x068, 'PageUp'], + [0x069, 'ArrowLeft'], + [0x06a, 'ArrowRight'], + [0x06b, 'End'], + [0x06c, 'ArrowDown'], + [0x06d, 'PageDown'], + [0x06e, 'Insert'], + [0x06f, 'Delete'], + [0x071, 'AudioVolumeMute'], + [0x072, 'AudioVolumeDown'], + [0x073, 'AudioVolumeUp'], + [0x074, 'Power'], + [0x075, 'NumpadEqual'], + [0x077, 'Pause'], + [0x078, 'ShowAllWindows'], + [0x079, 'NumpadComma'], + [0x07a, 'Lang1'], + [0x07b, 'Lang2'], + [0x07c, 'IntlYen'], + [0x07d, 'MetaLeft'], + [0x07e, 'MetaRight'], + [0x07f, 'ContextMenu'], + [0x080, 'BrowserStop'], + [0x081, 'Again'], + [0x083, 'Undo'], + [0x084, 'Select'], + [0x085, 'Copy'], + [0x086, 'Open'], + [0x087, 'Paste'], + [0x088, 'Find'], + [0x089, 'Cut'], + [0x08a, 'Help'], + [0x08c, 'LaunchApp2'], + [0x08e, 'Sleep'], + [0x08f, 'WakeUp'], + [0x090, 'LaunchApp1'], + [0x094, 'LaunchApp1'], + [0x095, 'LaunchApp2'], + [0x09b, 'LaunchMail'], + [0x09c, 'BrowserFavorites'], + [0x09e, 'BrowserBack'], + [0x09f, 'BrowserForward'], + [0x0a1, 'Eject'], + [0x0a3, 'MediaTrackNext'], + [0x0a4, 'MediaPlayPause'], + [0x0a5, 'MediaTrackPrevious'], + [0x0a6, 'MediaStop'], + [0x0a7, 'MediaRecord'], + [0x0a8, 'MediaRewind'], + [0x0ab, 'MediaSelect'], + [0x0ac, 'BrowserHome'], + [0x0ad, 'BrowserRefresh'], + [0x0b3, 'NumpadParenLeft'], + [0x0b4, 'NumpadParenRight'], + [0x0b7, 'F13'], + [0x0b8, 'F14'], + [0x0b9, 'F15'], + [0x0ba, 'F16'], + [0x0bb, 'F17'], + [0x0bc, 'F18'], + [0x0bd, 'F19'], + [0x0be, 'F20'], + [0x0bf, 'F21'], + [0x0c0, 'F22'], + [0x0c1, 'F23'], + [0x0c2, 'F24'], + [0x0c9, 'MediaPause'], + [0x0cf, 'MediaPlay'], + [0x0d0, 'MediaFastForward'], + [0x0d9, 'BrowserSearch'], + [0x0e0, 'BrightnessDown'], + [0x0e1, 'BrightnessUp'], + [0x0e2, 'MediaSelect'], + [0x0e3, 'DisplayToggleIntExt'], + [0x0e7, 'MailSend'], + [0x0e8, 'MailReply'], + [0x0e9, 'MailForward'], + [0x16c, 'BrowserFavorites'], + [0x174, 'ZoomToggle'], + [0x1d0, 'Fn'], + [0x243, 'LaunchControlPanel'], + [0x244, 'SelectTask'], + [0x245, 'LaunchScreenSaver'], + [0x247, 'LaunchAssistant'], + [0x248, 'KeyboardLayoutSelect'], +]); + +const WINDOWS_SCAN_CODES: ReadonlyMap = new Map([ + [0x0001, 'Escape'], + [0x0002, 'Digit1'], + [0x0003, 'Digit2'], + [0x0004, 'Digit3'], + [0x0005, 'Digit4'], + [0x0006, 'Digit5'], + [0x0007, 'Digit6'], + [0x0008, 'Digit7'], + [0x0009, 'Digit8'], + [0x000a, 'Digit9'], + [0x000b, 'Digit0'], + [0x000c, 'Minus'], + [0x000d, 'Equal'], + [0x000e, 'Backspace'], + [0x000f, 'Tab'], + [0x0010, 'KeyQ'], + [0x0011, 'KeyW'], + [0x0012, 'KeyE'], + [0x0013, 'KeyR'], + [0x0014, 'KeyT'], + [0x0015, 'KeyY'], + [0x0016, 'KeyU'], + [0x0017, 'KeyI'], + [0x0018, 'KeyO'], + [0x0019, 'KeyP'], + [0x001a, 'BracketLeft'], + [0x001b, 'BracketRight'], + [0x001c, 'Enter'], + [0x001d, 'ControlLeft'], + [0x001e, 'KeyA'], + [0x001f, 'KeyS'], + [0x0020, 'KeyD'], + [0x0021, 'KeyF'], + [0x0022, 'KeyG'], + [0x0023, 'KeyH'], + [0x0024, 'KeyJ'], + [0x0025, 'KeyK'], + [0x0026, 'KeyL'], + [0x0027, 'Semicolon'], + [0x0028, 'Quote'], + [0x0029, 'Backquote'], + [0x002a, 'ShiftLeft'], + [0x002b, 'Backslash'], + [0x002c, 'KeyZ'], + [0x002d, 'KeyX'], + [0x002e, 'KeyC'], + [0x002f, 'KeyV'], + [0x0030, 'KeyB'], + [0x0031, 'KeyN'], + [0x0032, 'KeyM'], + [0x0033, 'Comma'], + [0x0034, 'Period'], + [0x0035, 'Slash'], + [0x0036, 'ShiftRight'], + [0x0037, 'NumpadMultiply'], + [0x0038, 'AltLeft'], + [0x0039, 'Space'], + [0x003a, 'CapsLock'], + [0x003b, 'F1'], + [0x003c, 'F2'], + [0x003d, 'F3'], + [0x003e, 'F4'], + [0x003f, 'F5'], + [0x0040, 'F6'], + [0x0041, 'F7'], + [0x0042, 'F8'], + [0x0043, 'F9'], + [0x0044, 'F10'], + [0x0045, 'Pause'], + [0x0046, 'ScrollLock'], + [0x0047, 'Numpad7'], + [0x0048, 'Numpad8'], + [0x0049, 'Numpad9'], + [0x004a, 'NumpadSubtract'], + [0x004b, 'Numpad4'], + [0x004c, 'Numpad5'], + [0x004d, 'Numpad6'], + [0x004e, 'NumpadAdd'], + [0x004f, 'Numpad1'], + [0x0050, 'Numpad2'], + [0x0051, 'Numpad3'], + [0x0052, 'Numpad0'], + [0x0053, 'NumpadDecimal'], + [0x0054, 'PrintScreen'], + [0x0056, 'IntlBackslash'], + [0x0057, 'F11'], + [0x0058, 'F12'], + [0x0059, 'NumpadEqual'], + [0x0064, 'F13'], + [0x0065, 'F14'], + [0x0066, 'F15'], + [0x0067, 'F16'], + [0x0068, 'F17'], + [0x0069, 'F18'], + [0x006a, 'F19'], + [0x006b, 'F20'], + [0x006c, 'F21'], + [0x006d, 'F22'], + [0x006e, 'F23'], + [0x0070, 'KanaMode'], + [0x0071, 'Lang2'], + [0x0072, 'Lang1'], + [0x0073, 'IntlRo'], + [0x0076, 'F24'], + [0x0077, 'Lang4'], + [0x0078, 'Lang3'], + [0x0079, 'Convert'], + [0x007b, 'NonConvert'], + [0x007d, 'IntlYen'], + [0x007e, 'NumpadComma'], + [0xe008, 'Undo'], + [0xe00a, 'Paste'], + [0xe010, 'MediaTrackPrevious'], + [0xe017, 'Cut'], + [0xe018, 'Copy'], + [0xe019, 'MediaTrackNext'], + [0xe01c, 'NumpadEnter'], + [0xe01d, 'ControlRight'], + [0xe020, 'AudioVolumeMute'], + [0xe021, 'LaunchApp2'], + [0xe022, 'MediaPlayPause'], + [0xe024, 'MediaStop'], + [0xe02c, 'Eject'], + [0xe02e, 'AudioVolumeDown'], + [0xe030, 'AudioVolumeUp'], + [0xe032, 'BrowserHome'], + [0xe035, 'NumpadDivide'], + [0xe037, 'PrintScreen'], + [0xe038, 'AltRight'], + [0xe03b, 'Help'], + [0xe045, 'NumLock'], + [0xe046, 'Pause'], + [0xe047, 'Home'], + [0xe048, 'ArrowUp'], + [0xe049, 'PageUp'], + [0xe04b, 'ArrowLeft'], + [0xe04d, 'ArrowRight'], + [0xe04f, 'End'], + [0xe050, 'ArrowDown'], + [0xe051, 'PageDown'], + [0xe052, 'Insert'], + [0xe053, 'Delete'], + [0xe05b, 'MetaLeft'], + [0xe05c, 'MetaRight'], + [0xe05d, 'ContextMenu'], + [0xe05e, 'Power'], + [0xe05f, 'Sleep'], + [0xe063, 'WakeUp'], + [0xe065, 'BrowserSearch'], + [0xe066, 'BrowserFavorites'], + [0xe067, 'BrowserRefresh'], + [0xe068, 'BrowserStop'], + [0xe069, 'BrowserForward'], + [0xe06a, 'BrowserBack'], + [0xe06b, 'LaunchApp1'], + [0xe06c, 'LaunchMail'], + [0xe06d, 'MediaSelect'], +]); + +const WINDOWS_VIRTUAL_KEY_CODES: ReadonlyMap = new Map([ + [0x08, 'Backspace'], + [0x09, 'Tab'], + [0x0c, 'Numpad5'], + [0x0d, 'Enter'], + [0x10, 'ShiftLeft'], + [0x11, 'ControlLeft'], + [0x12, 'AltLeft'], + [0x13, 'Pause'], + [0x14, 'CapsLock'], + [0x15, 'KanaMode'], + [0x1b, 'Escape'], + [0x1c, 'Convert'], + [0x1d, 'NonConvert'], + [0x20, 'Space'], + [0x21, 'PageUp'], + [0x22, 'PageDown'], + [0x23, 'End'], + [0x24, 'Home'], + [0x25, 'ArrowLeft'], + [0x26, 'ArrowUp'], + [0x27, 'ArrowRight'], + [0x28, 'ArrowDown'], + [0x2c, 'PrintScreen'], + [0x2d, 'Insert'], + [0x2e, 'Delete'], + ...Array.from({length: 10}, (_, index): [number, string] => [0x30 + index, `Digit${index}`]), + ...Array.from({length: 26}, (_, index): [number, string] => [ + 0x41 + index, + `Key${String.fromCharCode(0x41 + index)}`, + ]), + [0x5b, 'MetaLeft'], + [0x5c, 'MetaRight'], + [0x5d, 'ContextMenu'], + [0x5f, 'Sleep'], + ...Array.from({length: 10}, (_, index): [number, string] => [0x60 + index, `Numpad${index}`]), + [0x6a, 'NumpadMultiply'], + [0x6b, 'NumpadAdd'], + [0x6c, 'NumpadComma'], + [0x6d, 'NumpadSubtract'], + [0x6e, 'NumpadDecimal'], + [0x6f, 'NumpadDivide'], + ...Array.from({length: 24}, (_, index): [number, string] => [0x70 + index, `F${index + 1}`]), + [0x90, 'NumLock'], + [0x91, 'ScrollLock'], + [0x92, 'NumpadEqual'], + [0xa0, 'ShiftLeft'], + [0xa1, 'ShiftRight'], + [0xa2, 'ControlLeft'], + [0xa3, 'ControlRight'], + [0xa4, 'AltLeft'], + [0xa5, 'AltRight'], + [0xa6, 'BrowserBack'], + [0xa7, 'BrowserForward'], + [0xa8, 'BrowserRefresh'], + [0xa9, 'BrowserStop'], + [0xaa, 'BrowserSearch'], + [0xab, 'BrowserFavorites'], + [0xac, 'BrowserHome'], + [0xad, 'AudioVolumeMute'], + [0xae, 'AudioVolumeDown'], + [0xaf, 'AudioVolumeUp'], + [0xb0, 'MediaTrackNext'], + [0xb1, 'MediaTrackPrevious'], + [0xb2, 'MediaStop'], + [0xb3, 'MediaPlayPause'], + [0xb4, 'LaunchMail'], + [0xb5, 'MediaSelect'], + [0xb6, 'LaunchApp1'], + [0xb7, 'LaunchApp2'], + [0xba, 'Semicolon'], + [0xbb, 'Equal'], + [0xbc, 'Comma'], + [0xbd, 'Minus'], + [0xbe, 'Period'], + [0xbf, 'Slash'], + [0xc0, 'Backquote'], + [0xdb, 'BracketLeft'], + [0xdc, 'Backslash'], + [0xdd, 'BracketRight'], + [0xde, 'Quote'], + [0xe2, 'IntlBackslash'], +]); + +const MACOS_KEY_CODES: ReadonlyMap = new Map([ + [0x00, 'KeyA'], + [0x01, 'KeyS'], + [0x02, 'KeyD'], + [0x03, 'KeyF'], + [0x04, 'KeyH'], + [0x05, 'KeyG'], + [0x06, 'KeyZ'], + [0x07, 'KeyX'], + [0x08, 'KeyC'], + [0x09, 'KeyV'], + [0x0a, 'IntlBackslash'], + [0x0b, 'KeyB'], + [0x0c, 'KeyQ'], + [0x0d, 'KeyW'], + [0x0e, 'KeyE'], + [0x0f, 'KeyR'], + [0x10, 'KeyY'], + [0x11, 'KeyT'], + [0x12, 'Digit1'], + [0x13, 'Digit2'], + [0x14, 'Digit3'], + [0x15, 'Digit4'], + [0x16, 'Digit6'], + [0x17, 'Digit5'], + [0x18, 'Equal'], + [0x19, 'Digit9'], + [0x1a, 'Digit7'], + [0x1b, 'Minus'], + [0x1c, 'Digit8'], + [0x1d, 'Digit0'], + [0x1e, 'BracketRight'], + [0x1f, 'KeyO'], + [0x20, 'KeyU'], + [0x21, 'BracketLeft'], + [0x22, 'KeyI'], + [0x23, 'KeyP'], + [0x24, 'Enter'], + [0x25, 'KeyL'], + [0x26, 'KeyJ'], + [0x27, 'Quote'], + [0x28, 'KeyK'], + [0x29, 'Semicolon'], + [0x2a, 'Backslash'], + [0x2b, 'Comma'], + [0x2c, 'Slash'], + [0x2d, 'KeyN'], + [0x2e, 'KeyM'], + [0x2f, 'Period'], + [0x30, 'Tab'], + [0x31, 'Space'], + [0x32, 'Backquote'], + [0x33, 'Backspace'], + [0x35, 'Escape'], + [0x36, 'MetaRight'], + [0x37, 'MetaLeft'], + [0x38, 'ShiftLeft'], + [0x39, 'CapsLock'], + [0x3a, 'AltLeft'], + [0x3b, 'ControlLeft'], + [0x3c, 'ShiftRight'], + [0x3d, 'AltRight'], + [0x3e, 'ControlRight'], + [0x40, 'F17'], + [0x41, 'NumpadDecimal'], + [0x43, 'NumpadMultiply'], + [0x45, 'NumpadAdd'], + [0x47, 'NumLock'], + [0x48, 'AudioVolumeUp'], + [0x49, 'AudioVolumeDown'], + [0x4a, 'AudioVolumeMute'], + [0x4b, 'NumpadDivide'], + [0x4c, 'NumpadEnter'], + [0x4e, 'NumpadSubtract'], + [0x4f, 'F18'], + [0x50, 'F19'], + [0x51, 'NumpadEqual'], + [0x52, 'Numpad0'], + [0x53, 'Numpad1'], + [0x54, 'Numpad2'], + [0x55, 'Numpad3'], + [0x56, 'Numpad4'], + [0x57, 'Numpad5'], + [0x58, 'Numpad6'], + [0x59, 'Numpad7'], + [0x5a, 'F20'], + [0x5b, 'Numpad8'], + [0x5c, 'Numpad9'], + [0x5d, 'IntlYen'], + [0x5e, 'IntlRo'], + [0x5f, 'NumpadComma'], + [0x60, 'F5'], + [0x61, 'F6'], + [0x62, 'F7'], + [0x63, 'F3'], + [0x64, 'F8'], + [0x65, 'F9'], + [0x66, 'Lang2'], + [0x67, 'F11'], + [0x68, 'Lang1'], + [0x69, 'F13'], + [0x6a, 'F16'], + [0x6b, 'F14'], + [0x6d, 'F10'], + [0x6e, 'ContextMenu'], + [0x6f, 'F12'], + [0x71, 'F15'], + [0x72, 'Insert'], + [0x73, 'Home'], + [0x74, 'PageUp'], + [0x75, 'Delete'], + [0x76, 'F4'], + [0x77, 'End'], + [0x78, 'F2'], + [0x79, 'PageDown'], + [0x7a, 'F1'], + [0x7b, 'ArrowLeft'], + [0x7c, 'ArrowRight'], + [0x7d, 'ArrowDown'], + [0x7e, 'ArrowUp'], +]); + +const X11_KEYCODE_OFFSET = 8; +const WINDOWS_EXTENDED_SCAN_PREFIX = 0xe000; + +const PUNCTUATION_KEY_CODES: Readonly> = { + ' ': 'Space', + '-': 'Minus', + _: 'Minus', + '=': 'Equal', + '+': 'Equal', + ',': 'Comma', + '<': 'Comma', + '.': 'Period', + '>': 'Period', + ';': 'Semicolon', + ':': 'Semicolon', + "'": 'Quote', + '"': 'Quote', + '/': 'Slash', + '?': 'Slash', + '\\': 'Backslash', + '|': 'Backslash', + '[': 'BracketLeft', + '{': 'BracketLeft', + ']': 'BracketRight', + '}': 'BracketRight', + '`': 'Backquote', + '~': 'Backquote', +}; + +const NAMED_KEY_CODES: Readonly> = { + Spacebar: 'Space', + Esc: 'Escape', + Return: 'Enter', + Break: 'Pause', + Clear: 'NumLock', + Apps: 'ContextMenu', + Up: 'ArrowUp', + Down: 'ArrowDown', + Left: 'ArrowLeft', + Right: 'ArrowRight', + Del: 'Delete', + LaunchMediaPlayer: 'MediaSelect', +}; + +const KNOWN_DOM_CODES: ReadonlySet = new Set([ + ...EVDEV_KEY_CODES.values(), + ...WINDOWS_SCAN_CODES.values(), + ...MACOS_KEY_CODES.values(), +]); + +export const MODIFIER_KEY_CODES = { + ctrl: ['ControlLeft', 'ControlRight'], + alt: ['AltLeft', 'AltRight'], + shift: ['ShiftLeft', 'ShiftRight'], + meta: ['MetaLeft', 'MetaRight'], +} as const; + +export type ModifierKind = keyof typeof MODIFIER_KEY_CODES; + +export function modifierKindForCode(code: string | null): ModifierKind | null { + if (code === null) return null; + for (const kind of Object.keys(MODIFIER_KEY_CODES) as Array) { + if ((MODIFIER_KEY_CODES[kind] as ReadonlyArray).includes(code)) return kind; + } + return null; +} + +export function evdevKeyToDomCode(keycode: number): string | null { + return EVDEV_KEY_CODES.get(keycode) ?? null; +} + +export function x11KeycodeToDomCode(keycode: number): string | null { + if (keycode < X11_KEYCODE_OFFSET) return null; + return evdevKeyToDomCode(keycode - X11_KEYCODE_OFFSET); +} + +export function windowsScanCodeKey(scanCode: number, extended: boolean): number { + return extended ? WINDOWS_EXTENDED_SCAN_PREFIX | scanCode : scanCode; +} + +export function windowsKeyToDomCode(scanCode: number, extended: boolean, virtualKey: number): string | null { + const fromScanCode = scanCode === 0 ? undefined : WINDOWS_SCAN_CODES.get(windowsScanCodeKey(scanCode, extended)); + return fromScanCode ?? WINDOWS_VIRTUAL_KEY_CODES.get(virtualKey) ?? null; +} + +export function macosKeycodeToDomCode(keycode: number): string | null { + return MACOS_KEY_CODES.get(keycode) ?? null; +} + +function isPrintableShortcutKey(key: string | null | undefined): key is string { + if (!key) return false; + if (key === 'Dead' || key === 'Unidentified' || key === 'Process') return false; + return key.length === 1; +} + +export function shouldPreferLayoutKey(combo: {key: string; code?: string}): boolean { + if (!isPrintableShortcutKey(combo.key)) return false; + return !(combo.code && /^Numpad/.test(combo.code)); +} + +export function hookKeyNameForLayoutKey(key: string): string | null { + if (key.length !== 1) return null; + const punctuation = PUNCTUATION_KEY_CODES[key]; + if (punctuation) return punctuation; + const upper = key.toUpperCase(); + return /^[A-Z0-9]$/.test(upper) ? upper : null; +} + +export function hookKeyNameToDomCode(name: string): string | null { + if (/^[A-Z]$/.test(name)) return `Key${name}`; + if (/^[0-9]$/.test(name)) return `Digit${name}`; + const named = NAMED_KEY_CODES[name]; + if (named) return named; + return KNOWN_DOM_CODES.has(name) ? name : null; +} + +export function usLayoutKeyToDomCode(key: string): string | null { + if (key.length === 1) { + const punctuation = PUNCTUATION_KEY_CODES[key]; + if (punctuation) return punctuation; + const upper = key.toUpperCase(); + if (/^[A-Z]$/.test(upper)) return `Key${upper}`; + if (/^[0-9]$/.test(upper)) return `Digit${upper}`; + return null; + } + return hookKeyNameToDomCode(key); +} + +const XDG_CODE_KEYSYMS: ReadonlyMap = new Map([ + ...Array.from({length: 26}, (_, index): [string, string] => [ + `Key${String.fromCharCode(0x41 + index)}`, + String.fromCharCode(0x61 + index), + ]), + ...Array.from({length: 10}, (_, index): [string, string] => [`Digit${index}`, `${index}`]), + ...Array.from({length: 24}, (_, index): [string, string] => [`F${index + 1}`, `F${index + 1}`]), + ['Space', 'space'], + ['Enter', 'Return'], + ['Tab', 'Tab'], + ['Escape', 'Escape'], + ['Backspace', 'BackSpace'], + ['Minus', 'minus'], + ['Equal', 'equal'], + ['BracketLeft', 'bracketleft'], + ['BracketRight', 'bracketright'], + ['Backslash', 'backslash'], + ['Semicolon', 'semicolon'], + ['Quote', 'apostrophe'], + ['Backquote', 'grave'], + ['Comma', 'comma'], + ['Period', 'period'], + ['Slash', 'slash'], + ['Home', 'Home'], + ['End', 'End'], + ['PageUp', 'Prior'], + ['PageDown', 'Next'], + ['Insert', 'Insert'], + ['Delete', 'Delete'], + ['ArrowLeft', 'Left'], + ['ArrowRight', 'Right'], + ['ArrowUp', 'Up'], + ['ArrowDown', 'Down'], + ['PrintScreen', 'Print'], + ['ScrollLock', 'Scroll_Lock'], + ['Pause', 'Pause'], + ['ContextMenu', 'Menu'], + ['MediaPlayPause', 'XF86AudioPlay'], + ['MediaTrackNext', 'XF86AudioNext'], + ['MediaTrackPrevious', 'XF86AudioPrev'], + ['MediaStop', 'XF86AudioStop'], + ['AudioVolumeMute', 'XF86AudioMute'], +]); + +const XDG_KEYSYM_PATTERN = /^[A-Za-z0-9_]+$/; + +function xdgKeysymForCombo(combo: {key: string; code?: string}): string | null { + if (/^[a-z]$/i.test(combo.key) && !combo.code?.startsWith('Numpad')) return combo.key.toLowerCase(); + const code = combo.code ?? usLayoutKeyToDomCode(combo.key); + if (code === null) return null; + const keysym = XDG_CODE_KEYSYMS.get(code) ?? null; + return keysym !== null && XDG_KEYSYM_PATTERN.test(keysym) ? keysym : null; +} + +export interface XdgTriggerCombo { + key: string; + code?: string; + ctrl: boolean; + alt: boolean; + shift: boolean; + meta: boolean; + mouseButton?: number; + modifierOnly?: boolean; + bothSides?: boolean; +} + +export function comboToXdgTrigger(combo: XdgTriggerCombo): string | null { + if (combo.mouseButton !== undefined || combo.modifierOnly || combo.bothSides) return null; + const keysym = xdgKeysymForCombo(combo); + if (keysym === null) return null; + const parts: Array = []; + if (combo.ctrl) parts.push('CTRL'); + if (combo.alt) parts.push('ALT'); + if (combo.shift) parts.push('SHIFT'); + if (combo.meta) parts.push('LOGO'); + parts.push(keysym); + return parts.join('+'); +} diff --git a/fluxer_desktop/src/main/GlobalShortcutMatcher.test.mjs b/fluxer_desktop/src/main/GlobalShortcutMatcher.test.mjs new file mode 100644 index 000000000..3fe7084b5 --- /dev/null +++ b/fluxer_desktop/src/main/GlobalShortcutMatcher.test.mjs @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import assert from 'node:assert/strict'; +import {describe, test} from 'node:test'; +import {loadTsModule} from './fixtures/TsModuleLoader.mjs'; + +const {GlobalShortcutMatcher, bindingFromCombo} = loadTsModule('@electron/main/GlobalShortcutMatcher'); + +function combo(overrides) { + return {key: '', ctrl: false, alt: false, shift: false, meta: false, ...overrides}; +} + +function key(type, code, {key = null, raw, ctrl = false, alt = false, shift = false, meta = false} = {}) { + return {type, code, key, rawKeycode: raw, ctrlKey: ctrl, altKey: alt, shiftKey: shift, metaKey: meta}; +} + +function mouse(type, button, mods = {}) { + return { + type, + button, + ctrlKey: mods.ctrl === true, + altKey: mods.alt === true, + shiftKey: mods.shift === true, + metaKey: mods.meta === true, + }; +} + +function matcherWith(...entries) { + const matcher = new GlobalShortcutMatcher(); + matcher.setBindings(entries.map(([sourceId, action, value]) => bindingFromCombo(sourceId, action, combo(value)))); + return matcher; +} + +function phases(transitions) { + return transitions.map((transition) => `${transition.phase}:${transition.sourceId}`); +} + +describe('GlobalShortcutMatcher key identity', () => { + test('a physical backend never confuses a keysym with a binding code', () => { + const matcher = matcherWith([ + 'default:voice_toggle_mute', + 'voice_toggle_mute', + {key: 'm', code: 'KeyM', ctrl: true, shift: true}, + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'Digit2', {raw: 3, ctrl: true, shift: true}), true)), []); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'KeyM', {raw: 50, ctrl: true, shift: true}), true)), [ + 'press:default:voice_toggle_mute', + ]); + }); + + test('a layout-aware backend matches printable keys by layout name', () => { + const matcher = matcherWith([ + 'default:voice_toggle_mute', + 'voice_toggle_mute', + {key: 'm', code: 'Semicolon', ctrl: true}, + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'KeyM', {key: 'Comma', raw: 58, ctrl: true}), true)), []); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'KeyL', {key: 'L', raw: 59, ctrl: true}), true)), []); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'Semicolon', {key: 'M', raw: 47, ctrl: true}), true)), [ + 'press:default:voice_toggle_mute', + ]); + }); + + test('a letter from another layout group falls back to the physical code', () => { + const matcher = matcherWith(['custom:a', 'voice_toggle_mute', {key: 'q', code: 'KeyQ', ctrl: true}]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'KeyQ', {key: null, raw: 24, ctrl: true}), true)), [ + 'press:custom:a', + ]); + }); + + test('a letter whose hook name is not a single character falls back to the physical code', () => { + const matcher = matcherWith(['custom:a', 'voice_toggle_mute', {key: 'q', code: 'KeyQ', ctrl: true}]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'KeyQ', {key: 'Oem7', raw: 24, ctrl: true}), true)), [ + 'press:custom:a', + ]); + }); + + test('punctuation matches by physical code whatever the hook calls the key', () => { + const matcher = matcherWith( + ['custom:less', 'voice_toggle_mute', {key: '<', code: 'IntlBackslash'}], + ['custom:semicolon', 'voice_toggle_deafen', {key: ';', code: 'Comma', shift: true}], + ); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'Comma', {key: 'Comma', raw: 0x33}), true)), []); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'IntlBackslash', {key: 'Comma', raw: 0x56}), true)), [ + 'press:custom:less', + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'Comma', {key: 'Comma', raw: 0x33, shift: true}), true)), [ + 'press:custom:semicolon', + ]); + }); + + test('a letter binding without a code ignores the physical key that types punctuation', () => { + const matcher = matcherWith([ + 'default:voice_toggle_mute', + 'voice_toggle_mute', + {key: 'm', ctrl: true, shift: true}, + ]); + assert.deepEqual( + phases(matcher.handleKey(key('keydown', 'KeyM', {key: 'Comma', raw: 50, ctrl: true, shift: true}), true)), + [], + ); + assert.deepEqual( + phases(matcher.handleKey(key('keydown', 'Semicolon', {key: 'M', raw: 39, ctrl: true, shift: true}), true)), + ['press:default:voice_toggle_mute'], + ); + }); + + test('a key-only binding falls back to the US position on physical backends', () => { + const matcher = matcherWith([ + 'default:voice_toggle_deafen', + 'voice_toggle_deafen', + {key: 'd', ctrl: true, shift: true}, + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'KeyD', {raw: 32, ctrl: true, shift: true}), true)), [ + 'press:default:voice_toggle_deafen', + ]); + }); + + test('a named key binding that uses its hook name as the layout key still matches', () => { + const matcher = new GlobalShortcutMatcher(); + matcher.setBindings([ + { + sourceId: 'key:voice_push_to_talk::F8:F8', + action: 'key:voice_push_to_talk::F8:F8', + trigger: {kind: 'key', layoutKey: 'F8', code: 'F8'}, + modifiers: {ctrl: false, alt: false, shift: false, meta: false}, + }, + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'F8', {key: 'F8', raw: 74}), true)), [ + 'press:key:voice_push_to_talk::F8:F8', + ]); + assert.deepEqual(phases(matcher.handleKey(key('keyup', 'F8', {key: 'F8', raw: 74}), true)), [ + 'release:key:voice_push_to_talk::F8:F8', + ]); + }); + + test('non-printable and numpad keys match by code even on layout-aware backends', () => { + const matcher = matcherWith( + ['custom:a', 'voice_push_to_talk', {key: 'F13', code: 'F13'}], + ['custom:b', 'voice_disconnect', {key: '1', code: 'Numpad1'}], + ); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'F13', {key: 'F13', raw: 191}), true)), [ + 'press:custom:a', + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'Digit1', {key: '1', raw: 10}), true)), []); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'Numpad1', {key: 'Numpad1', raw: 87}), true)), [ + 'press:custom:b', + ]); + }); +}); + +describe('GlobalShortcutMatcher modifiers', () => { + test('bindings with modifiers need an exact modifier match', () => { + const matcher = matcherWith(['custom:a', 'voice_toggle_mute', {key: 'F5', code: 'F5', ctrl: true}]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'F5', {raw: 63, ctrl: true, shift: true}), true)), []); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'F5', {raw: 63}), true)), []); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'F5', {raw: 63, ctrl: true}), true)), ['press:custom:a']); + }); + + test('bindings without modifiers fire with extra modifiers held, for toggles and holds alike', () => { + const matcher = matcherWith( + ['custom:toggle', 'voice_toggle_mute', {key: 'F6', code: 'F6'}], + ['custom:hold', 'voice_push_to_talk', {key: 'F7', code: 'F7'}], + ); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'F6', {raw: 64, shift: true}), true)), [ + 'press:custom:toggle', + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'F7', {raw: 65, ctrl: true, alt: true}), true)), [ + 'press:custom:hold', + ]); + }); + + test('release follows the main key regardless of modifier order', () => { + const matcher = matcherWith(['custom:a', 'voice_push_to_talk', {key: ' ', code: 'Space', ctrl: true}]); + matcher.handleKey(key('keydown', 'ControlLeft', {raw: 29}), true); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'Space', {raw: 57, ctrl: true}), true)), [ + 'press:custom:a', + ]); + assert.deepEqual(phases(matcher.handleKey(key('keyup', 'ControlLeft', {raw: 29}), true)), []); + assert.deepEqual(phases(matcher.handleKey(key('keyup', 'Space', {raw: 57}), true)), ['release:custom:a']); + }); + + test('autorepeat never emits a second press', () => { + const matcher = matcherWith(['custom:a', 'voice_push_to_talk', {key: 'F8', code: 'F8'}]); + assert.equal(matcher.handleKey(key('keydown', 'F8', {raw: 66}), true).length, 1); + assert.equal(matcher.handleKey(key('keydown', 'F8', {raw: 66}), true).length, 0); + assert.equal(matcher.handleKey(key('keyup', 'F8', {raw: 66}), true).length, 1); + }); +}); + +describe('GlobalShortcutMatcher modifier-only combos', () => { + test('presses when every required modifier is down and releases when one goes up', () => { + const matcher = matcherWith([ + 'custom:a', + 'voice_push_to_talk', + {key: 'Control', code: 'ControlLeft', ctrl: true, alt: true, modifierOnly: true}, + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'ControlLeft', {raw: 29}), true)), []); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'AltRight', {raw: 100, ctrl: true}), true)), [ + 'press:custom:a', + ]); + assert.deepEqual(phases(matcher.handleKey(key('keyup', 'AltRight', {raw: 100}), true)), ['release:custom:a']); + }); + + test('bothSides requires the left and right key of the modifier', () => { + const matcher = matcherWith([ + 'custom:a', + 'voice_push_to_talk', + {key: 'Shift', code: 'ShiftLeft', shift: true, modifierOnly: true, bothSides: true}, + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'ShiftLeft', {raw: 42}), true)), []); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'ShiftRight', {raw: 54, shift: true}), true)), [ + 'press:custom:a', + ]); + assert.deepEqual(phases(matcher.handleKey(key('keyup', 'ShiftLeft', {raw: 42, shift: true}), true)), [ + 'release:custom:a', + ]); + assert.deepEqual(phases(matcher.handleKey(key('keyup', 'ShiftRight', {raw: 54}), true)), []); + }); + + test('a modifier release reported as a keydown still releases once the flags clear', () => { + const matcher = matcherWith([ + 'custom:a', + 'voice_push_to_talk', + {key: 'Shift', code: 'ShiftLeft', shift: true, modifierOnly: true}, + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'ShiftLeft', {raw: 0x38, shift: true}), true)), [ + 'press:custom:a', + ]); + matcher.handleKey(key('keydown', 'ShiftRight', {raw: 0x3c, shift: true}), true); + matcher.handleKey(key('keydown', 'ShiftRight', {raw: 0x3c, shift: true}), true); + assert.deepEqual(phases(matcher.handleKey(key('keyup', 'ShiftLeft', {raw: 0x38}), true)), ['release:custom:a']); + }); + + test('a lost modifier keyup is recovered from the flags of the next event', () => { + const matcher = matcherWith([ + 'custom:a', + 'voice_push_to_talk', + {key: 'Control', code: 'ControlLeft', ctrl: true, modifierOnly: true}, + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'ControlLeft', {raw: 29}), true)), ['press:custom:a']); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'KeyA', {raw: 30}), true)), ['release:custom:a']); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'ControlLeft', {raw: 29}), true)), ['press:custom:a']); + assert.deepEqual(phases(matcher.handleMouse(mouse('mousedown', 1), true)), ['release:custom:a']); + }); + + test('a modifier code the backend cannot flag is kept until its own key-up', () => { + const matcher = matcherWith([ + 'custom:a', + 'voice_push_to_talk', + {key: 'AltGraph', code: 'AltRight', modifierOnly: true}, + ]); + const unflaggedModifierCodes = ['AltRight']; + assert.deepEqual( + phases(matcher.handleKey({...key('keydown', 'AltRight', {raw: 108}), unflaggedModifierCodes}, true)), + ['press:custom:a'], + ); + assert.deepEqual(phases(matcher.handleMouse({...mouse('mousedown', 0), unflaggedModifierCodes}, true)), []); + assert.deepEqual( + phases(matcher.handleKey({...key('keydown', 'KeyQ', {raw: 24}), unflaggedModifierCodes}, true)), + [], + ); + assert.deepEqual(phases(matcher.handleKey({...key('keyup', 'KeyQ', {raw: 24}), unflaggedModifierCodes}, true)), []); + assert.deepEqual( + phases(matcher.handleKey({...key('keyup', 'AltRight', {raw: 108}), unflaggedModifierCodes}, true)), + ['release:custom:a'], + ); + }); + + test('AltGr reported with Ctrl and Alt flags keeps a Right Alt hold', () => { + const matcher = matcherWith([ + 'custom:a', + 'voice_push_to_talk', + {key: 'AltGraph', code: 'AltRight', modifierOnly: true}, + ]); + matcher.handleKey(key('keydown', 'ControlLeft', {raw: 0x1d}), true); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'AltRight', {raw: 0xe038, ctrl: true}), true)), [ + 'press:custom:a', + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'KeyQ', {raw: 0x10, ctrl: true, alt: true}), true)), []); + assert.deepEqual(phases(matcher.handleKey(key('keyup', 'AltRight', {raw: 0xe038, ctrl: true, alt: true}), true)), [ + 'release:custom:a', + ]); + }); + + test('a stuck modifier no longer satisfies a combined modifier-only binding', () => { + const matcher = matcherWith([ + 'custom:a', + 'voice_push_to_talk', + {key: 'Alt', code: 'AltLeft', ctrl: true, alt: true, modifierOnly: true}, + ]); + matcher.handleKey(key('keydown', 'ControlLeft', {raw: 29, ctrl: true}), true); + matcher.handleKey(key('keydown', 'KeyL', {raw: 38, meta: true}), true); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'AltLeft', {raw: 56, alt: true}), true)), []); + }); +}); + +describe('GlobalShortcutMatcher layout modifiers', () => { + test('an X11 key the layout turns into Ctrl holds a Ctrl binding', () => { + const matcher = matcherWith([ + 'custom:a', + 'voice_push_to_talk', + {key: 'Control', code: 'ControlLeft', ctrl: true, modifierOnly: true}, + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'CapsLock', {key: 'ControlLeft', raw: 66}), true)), [ + 'press:custom:a', + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'KeyC', {key: 'C', raw: 54, ctrl: true}), true)), []); + assert.deepEqual( + phases(matcher.handleKey(key('keyup', 'CapsLock', {key: 'ControlLeft', raw: 66, ctrl: true}), true)), + ['release:custom:a'], + ); + }); + + test('a physical Ctrl key the layout turns into Caps Lock does not hold a Ctrl binding', () => { + const matcher = matcherWith([ + 'custom:a', + 'voice_push_to_talk', + {key: 'Control', code: 'ControlLeft', ctrl: true, modifierOnly: true}, + ]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'ControlLeft', {key: 'CapsLock', raw: 37}), true)), []); + assert.deepEqual(phases(matcher.handleKey(key('keyup', 'ControlLeft', {key: 'CapsLock', raw: 37}), true)), []); + }); +}); + +describe('GlobalShortcutMatcher modifier-only kinds', () => { + test('the trigger key counts as a required modifier alongside the flags', () => { + const binding = bindingFromCombo( + 'custom:a', + 'voice_push_to_talk', + combo({key: 'Shift', code: 'ShiftLeft', ctrl: true, modifierOnly: true}), + ); + assert.deepEqual(binding.trigger, {kind: 'modifier-only', modifiers: ['ctrl', 'shift'], bothSides: false}); + const matcher = new GlobalShortcutMatcher(); + matcher.setBindings([binding]); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'ControlLeft', {raw: 29, ctrl: true}), true)), []); + assert.deepEqual(phases(matcher.handleKey(key('keydown', 'ShiftLeft', {raw: 42, ctrl: true, shift: true}), true)), [ + 'press:custom:a', + ]); + }); +}); + +describe('GlobalShortcutMatcher mouse buttons', () => { + test('match by button with the same modifier rule and release on mouseup', () => { + const matcher = matcherWith(['custom:a', 'voice_push_to_talk', {key: '', mouseButton: 4}]); + assert.deepEqual(phases(matcher.handleMouse(mouse('mousedown', 3), true)), []); + assert.deepEqual(phases(matcher.handleMouse(mouse('mousedown', 4, {shift: true}), true)), ['press:custom:a']); + assert.deepEqual(phases(matcher.handleMouse(mouse('mouseup', 4), true)), ['release:custom:a']); + }); +}); + +describe('GlobalShortcutMatcher lifecycle', () => { + test('a paused press is never recorded, so no release follows', () => { + const matcher = matcherWith(['custom:a', 'voice_push_to_talk', {key: 'F9', code: 'F9'}]); + assert.deepEqual(matcher.handleKey(key('keydown', 'F9', {raw: 67}), false), []); + assert.deepEqual(matcher.handleKey(key('keyup', 'F9', {raw: 67}), true), []); + }); + + test('releases still flow while paused', () => { + const matcher = matcherWith(['custom:a', 'voice_push_to_talk', {key: 'F9', code: 'F9'}]); + matcher.handleKey(key('keydown', 'F9', {raw: 67}), true); + assert.deepEqual(phases(matcher.handleKey(key('keyup', 'F9', {raw: 67}), false)), ['release:custom:a']); + }); + + test('sync keeps unchanged pressed sources and releases removed or changed ones', () => { + const matcher = new GlobalShortcutMatcher(); + const kept = bindingFromCombo('custom:kept', 'voice_push_to_talk', combo({key: 'F1', code: 'F1'})); + const changed = bindingFromCombo('custom:changed', 'voice_push_to_mute', combo({key: 'F2', code: 'F2'})); + const removed = bindingFromCombo('custom:removed', 'voice_toggle_mute', combo({key: 'F3', code: 'F3'})); + matcher.setBindings([kept, changed, removed]); + matcher.handleKey(key('keydown', 'F1', {raw: 59}), true); + matcher.handleKey(key('keydown', 'F2', {raw: 60}), true); + matcher.handleKey(key('keydown', 'F3', {raw: 61}), true); + const released = matcher.setBindings([ + bindingFromCombo('custom:kept', 'voice_push_to_talk', combo({key: 'F1', code: 'F1'})), + bindingFromCombo('custom:changed', 'voice_push_to_mute', combo({key: 'F4', code: 'F4'})), + ]); + assert.deepEqual(phases(released).sort(), ['release:custom:changed', 'release:custom:removed']); + assert.deepEqual(phases(matcher.handleKey(key('keyup', 'F1', {raw: 59}), true)), ['release:custom:kept']); + }); + + test('releaseAll releases every pressed source', () => { + const matcher = matcherWith( + ['custom:a', 'voice_push_to_talk', {key: 'F1', code: 'F1'}], + ['custom:b', 'voice_push_to_mute', {key: '', mouseButton: 3}], + ); + matcher.handleKey(key('keydown', 'F1', {raw: 59}), true); + matcher.handleMouse(mouse('mousedown', 3), true); + assert.deepEqual(phases(matcher.releaseAll()).sort(), ['release:custom:a', 'release:custom:b']); + assert.deepEqual(matcher.releaseAll(), []); + }); + + test('combos without any key identity are dropped', () => { + assert.equal(bindingFromCombo('custom:a', 'voice_push_to_talk', combo({key: ''})), null); + assert.equal(bindingFromCombo('custom:a', 'voice_push_to_talk', combo({key: '', modifierOnly: true})), null); + }); +}); diff --git a/fluxer_desktop/src/main/GlobalShortcutMatcher.ts b/fluxer_desktop/src/main/GlobalShortcutMatcher.ts new file mode 100644 index 000000000..78993f1f0 --- /dev/null +++ b/fluxer_desktop/src/main/GlobalShortcutMatcher.ts @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { + hookKeyNameForLayoutKey, + hookKeyNameToDomCode, + MODIFIER_KEY_CODES, + type ModifierKind, + modifierKindForCode, + shouldPreferLayoutKey, + usLayoutKeyToDomCode, +} from '@electron/main/GlobalShortcutKeys'; + +export interface ShortcutModifiers { + ctrl: boolean; + alt: boolean; + shift: boolean; + meta: boolean; +} + +export type ShortcutTrigger = + | {kind: 'key'; layoutKey: string | null; code: string | null} + | {kind: 'mouse'; button: number} + | {kind: 'modifier-only'; modifiers: Array; bothSides: boolean}; + +export interface ShortcutBinding { + sourceId: string; + action: string; + trigger: ShortcutTrigger; + modifiers: ShortcutModifiers; +} + +export interface ShortcutComboInput { + key: string; + code?: string; + ctrl: boolean; + alt: boolean; + shift: boolean; + meta: boolean; + mouseButton?: number; + modifierOnly?: boolean; + bothSides?: boolean; +} + +export interface HookKeyEvent { + type: 'keydown' | 'keyup'; + code: string | null; + key: string | null; + rawKeycode: number; + ctrlKey: boolean; + altKey: boolean; + shiftKey: boolean; + metaKey: boolean; + unflaggedModifierCodes?: ReadonlyArray; +} + +export interface HookMouseEvent { + type: 'mousedown' | 'mouseup'; + button: number; + ctrlKey: boolean; + altKey: boolean; + shiftKey: boolean; + metaKey: boolean; + unflaggedModifierCodes?: ReadonlyArray; +} + +export interface ShortcutTransition { + sourceId: string; + action: string; + phase: 'press' | 'release'; +} + +type PressRecord = {kind: 'key'; rawKeycode: number} | {kind: 'mouse'; button: number} | {kind: 'modifier-only'}; + +const MODIFIER_ORDER: ReadonlyArray = ['ctrl', 'alt', 'shift', 'meta']; +const SINGLE_CHARACTER_KEY = /^[A-Z0-9]$/; + +export function bindingFromCombo(sourceId: string, action: string, combo: ShortcutComboInput): ShortcutBinding | null { + const modifiers = {ctrl: combo.ctrl, alt: combo.alt, shift: combo.shift, meta: combo.meta}; + if (combo.mouseButton !== undefined) { + return {sourceId, action, trigger: {kind: 'mouse', button: combo.mouseButton}, modifiers}; + } + const code = combo.code ?? usLayoutKeyToDomCode(combo.key); + if (combo.modifierOnly) { + const codeKind = modifierKindForCode(code); + const kinds = MODIFIER_ORDER.filter((kind) => modifiers[kind] || kind === codeKind); + if (kinds.length === 0) return null; + const bothSides = combo.bothSides === true && kinds.length === 1; + return {sourceId, action, trigger: {kind: 'modifier-only', modifiers: kinds, bothSides}, modifiers}; + } + const layoutName = shouldPreferLayoutKey(combo) ? hookKeyNameForLayoutKey(combo.key) : null; + const layoutKey = layoutName !== null && SINGLE_CHARACTER_KEY.test(layoutName) ? layoutName : null; + if (layoutKey === null && code === null) return null; + return {sourceId, action, trigger: {kind: 'key', layoutKey, code}, modifiers}; +} + +function bindingSignature(binding: ShortcutBinding): string { + return JSON.stringify([binding.action, binding.trigger, binding.modifiers]); +} + +function modifiersMatch(expected: ShortcutModifiers, event: HookKeyEvent | HookMouseEvent): boolean { + if (!expected.ctrl && !expected.alt && !expected.shift && !expected.meta) return true; + return ( + expected.ctrl === event.ctrlKey && + expected.alt === event.altKey && + expected.shift === event.shiftKey && + expected.meta === event.metaKey + ); +} + +function keyTriggerMatches(trigger: {layoutKey: string | null; code: string | null}, event: HookKeyEvent): boolean { + if (trigger.layoutKey !== null && event.key !== null) { + if (SINGLE_CHARACTER_KEY.test(event.key)) return event.key === trigger.layoutKey; + if (SINGLE_CHARACTER_KEY.test(trigger.layoutKey) && hookKeyNameToDomCode(event.key) !== null) return false; + } + return trigger.code !== null && event.code === trigger.code; +} + +function heldModifierCode(event: HookKeyEvent): string | null { + const identity = event.key ?? event.code; + return modifierKindForCode(identity) !== null ? identity : null; +} + +function modifierFlag(kind: ModifierKind, event: HookKeyEvent | HookMouseEvent): boolean { + switch (kind) { + case 'ctrl': + return event.ctrlKey; + case 'alt': + return event.altKey; + case 'shift': + return event.shiftKey; + case 'meta': + return event.metaKey; + } +} + +export class GlobalShortcutMatcher { + private readonly bindings = new Map(); + private readonly signatures = new Map(); + private readonly pressed = new Map(); + private readonly heldModifierCodes = new Set(); + + hasBindings(): boolean { + return this.bindings.size > 0; + } + + setBindings(next: ReadonlyArray): Array { + const nextBindings = new Map(); + for (const binding of next) { + if (!nextBindings.has(binding.sourceId)) nextBindings.set(binding.sourceId, binding); + } + const released: Array = []; + for (const sourceId of [...this.pressed.keys()]) { + const previous = this.bindings.get(sourceId); + const replacement = nextBindings.get(sourceId); + if (previous && replacement && bindingSignature(replacement) === this.signatures.get(sourceId)) continue; + this.pressed.delete(sourceId); + if (previous) released.push({sourceId, action: previous.action, phase: 'release'}); + } + this.bindings.clear(); + this.signatures.clear(); + for (const [sourceId, binding] of nextBindings) { + this.bindings.set(sourceId, binding); + this.signatures.set(sourceId, bindingSignature(binding)); + } + return released; + } + + handleKey(event: HookKeyEvent, allowPress: boolean): Array { + const heldCode = heldModifierCode(event); + this.reconcileModifierFlags(event, event.type === 'keydown' ? heldCode : null); + if (event.type === 'keydown') { + const transitions = this.releaseUnsatisfiedModifierOnly(); + const modifierOnlyBefore = this.satisfiedModifierOnlyBindings(); + if (heldCode !== null) this.heldModifierCodes.add(heldCode); + if (!allowPress) return transitions; + for (const binding of this.bindings.values()) { + if (this.pressed.has(binding.sourceId)) continue; + const {trigger} = binding; + if (trigger.kind === 'modifier-only') { + if (modifierOnlyBefore.has(binding.sourceId) || !this.isModifierOnlySatisfied(trigger)) continue; + this.pressed.set(binding.sourceId, {kind: 'modifier-only'}); + } else if (trigger.kind === 'key') { + if (!keyTriggerMatches(trigger, event) || !modifiersMatch(binding.modifiers, event)) continue; + this.pressed.set(binding.sourceId, {kind: 'key', rawKeycode: event.rawKeycode}); + } else { + continue; + } + transitions.push({sourceId: binding.sourceId, action: binding.action, phase: 'press'}); + } + return transitions; + } + if (heldCode !== null) this.heldModifierCodes.delete(heldCode); + const transitions: Array = []; + for (const [sourceId, record] of [...this.pressed]) { + if (record.kind !== 'key' || record.rawKeycode !== event.rawKeycode) continue; + const binding = this.bindings.get(sourceId); + this.pressed.delete(sourceId); + if (binding) transitions.push({sourceId, action: binding.action, phase: 'release'}); + } + transitions.push(...this.releaseUnsatisfiedModifierOnly()); + return transitions; + } + + handleMouse(event: HookMouseEvent, allowPress: boolean): Array { + this.reconcileModifierFlags(event, null); + const transitions = this.releaseUnsatisfiedModifierOnly(); + if (event.type === 'mouseup') { + for (const [sourceId, record] of [...this.pressed]) { + if (record.kind !== 'mouse' || record.button !== event.button) continue; + const binding = this.bindings.get(sourceId); + this.pressed.delete(sourceId); + if (binding) transitions.push({sourceId, action: binding.action, phase: 'release'}); + } + return transitions; + } + if (!allowPress) return transitions; + for (const binding of this.bindings.values()) { + const {trigger} = binding; + if (trigger.kind !== 'mouse' || trigger.button !== event.button) continue; + if (this.pressed.has(binding.sourceId) || !modifiersMatch(binding.modifiers, event)) continue; + this.pressed.set(binding.sourceId, {kind: 'mouse', button: event.button}); + transitions.push({sourceId: binding.sourceId, action: binding.action, phase: 'press'}); + } + return transitions; + } + + releaseAll(): Array { + const transitions: Array = []; + for (const sourceId of this.pressed.keys()) { + const binding = this.bindings.get(sourceId); + if (binding) transitions.push({sourceId, action: binding.action, phase: 'release'}); + } + this.pressed.clear(); + this.heldModifierCodes.clear(); + return transitions; + } + + private reconcileModifierFlags(event: HookKeyEvent | HookMouseEvent, pressingCode: string | null): void { + const unflagged = event.unflaggedModifierCodes ?? []; + for (const kind of MODIFIER_ORDER) { + if (modifierFlag(kind, event)) continue; + for (const code of MODIFIER_KEY_CODES[kind]) { + if (code !== pressingCode && !unflagged.includes(code)) this.heldModifierCodes.delete(code); + } + } + } + + private releaseUnsatisfiedModifierOnly(): Array { + const transitions: Array = []; + for (const [sourceId, record] of [...this.pressed]) { + if (record.kind !== 'modifier-only') continue; + const binding = this.bindings.get(sourceId); + if (binding?.trigger.kind === 'modifier-only' && this.isModifierOnlySatisfied(binding.trigger)) continue; + this.pressed.delete(sourceId); + if (binding) transitions.push({sourceId, action: binding.action, phase: 'release'}); + } + return transitions; + } + + private satisfiedModifierOnlyBindings(): Set { + const satisfied = new Set(); + for (const binding of this.bindings.values()) { + if (binding.trigger.kind === 'modifier-only' && this.isModifierOnlySatisfied(binding.trigger)) { + satisfied.add(binding.sourceId); + } + } + return satisfied; + } + + private isModifierOnlySatisfied(trigger: {modifiers: Array; bothSides: boolean}): boolean { + return trigger.modifiers.every((kind) => { + const [left, right] = MODIFIER_KEY_CODES[kind]; + const leftHeld = this.heldModifierCodes.has(left); + const rightHeld = this.heldModifierCodes.has(right); + return trigger.bothSides ? leftHeld && rightHeld : leftHeld || rightHeld; + }); + } +} diff --git a/fluxer_desktop/src/main/GlobalShortcutsEngine.test.mjs b/fluxer_desktop/src/main/GlobalShortcutsEngine.test.mjs new file mode 100644 index 000000000..38e266f8b --- /dev/null +++ b/fluxer_desktop/src/main/GlobalShortcutsEngine.test.mjs @@ -0,0 +1,1390 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import assert from 'node:assert/strict'; +import {describe, test} from 'node:test'; +import {loadTsModule} from './fixtures/TsModuleLoader.mjs'; + +const legacyHandlers = new Map(); +const legacyStubs = { + electron: {ipcMain: {handle: (channel, handler) => legacyHandlers.set(channel, handler)}}, + '@electron/main/PrivilegedRendererDocuments': {requirePrivilegedRendererDocumentSender: () => {}}, +}; +const engineModule = loadTsModule('@electron/main/GlobalShortcutsEngine'); +const {GlobalShortcutsEngine, sanitizeGlobalShortcutsSyncPayload, portalDefinitionsFromStored, storedActionsFromSync} = + engineModule; +const {LinuxPortalShortcutsManager} = loadTsModule('@electron/main/LinuxGlobalShortcutsPortal'); +const legacy = loadTsModule('@electron/main/LegacyGlobalKeyHookIpc', {stubs: legacyStubs}); +const {GLOBAL_SHORTCUT_ACTIONS} = loadTsModule('@electron/common/GlobalShortcutActions'); +const {bindingFromCombo, GlobalShortcutMatcher} = loadTsModule('@electron/main/GlobalShortcutMatcher'); +const native = loadTsModule('@electron/main/GlobalShortcutsNative', { + stubs: { + 'node:module': {createRequire: () => () => ({})}, + '@electron/common/Logger': {createChildLogger: () => ({info: () => {}, warn: () => {}})}, + '@electron/main/MacTcc': {getTccStatus: () => 'granted'}, + }, +}); + +function combo(overrides) { + return {key: '', ctrl: false, alt: false, shift: false, meta: false, ...overrides}; +} + +function keyEvent(type, code, raw, mods = {}, native = {keycode: raw, keyName: code}) { + return { + type, + code, + key: null, + rawKeycode: raw, + ctrlKey: mods.ctrl === true, + altKey: mods.alt === true, + shiftKey: mods.shift === true, + metaKey: mods.meta === true, + native, + }; +} + +class FakeHook { + constructor(kind, harness) { + this.kind = kind; + this.harness = harness; + this.stopped = false; + } + + async start(onEvent) { + this.harness.started.push(this.kind); + this.pendingOnEvent = onEvent; + const result = await (this.harness.startResults.shift() ?? 'ok'); + if (result === 'ok') this.onEvent = onEvent; + return result; + } + + stop() { + this.stopped = true; + this.harness.stopped.push(this.kind); + } +} + +function createSettings(initial = {}) { + const state = {directInputEnabled: false, migrated: false, lastActions: [], ...initial}; + return { + state, + getDirectInputEnabled: () => state.directInputEnabled, + setDirectInputEnabled: (enabled) => { + state.directInputEnabled = enabled; + }, + isMigrated: () => state.migrated, + setMigrated: () => { + state.migrated = true; + }, + getLastActions: () => state.lastActions, + setLastActions: (actions) => { + state.lastActions = actions; + }, + }; +} + +function createEngine({ + platform = 'linux', + session = 'x11', + inputHookMode = 'auto', + keyboardReadable = false, + settings = createSettings(), + portalConsent = 'unset', + portalOpen = {version: 2, appIdSource: 'registered', uniqueName: ':1.9', listed: []}, +} = {}) { + const harness = { + started: [], + stopped: [], + startResults: [], + hooks: [], + sent: [], + portals: [], + timers: [], + deviceWatches: [], + consent: portalConsent, + }; + const schedule = (callback, delayMs) => { + const timer = {callback, delayMs, cancelled: false, cancel: () => (timer.cancelled = true)}; + harness.timers.push(timer); + return timer; + }; + harness.keyboardReadable = keyboardReadable; + let engine = null; + const linux = platform === 'linux' ? {session, sandboxed: false, desktop: 'gnome', inputHookMode} : null; + const portal = + linux !== null && session === 'wayland' + ? new LinuxPortalShortcutsManager({ + createPortal: (onEvent) => { + const fake = { + onEvent, + closed: false, + open: async () => portalOpen, + bind: async () => ({ + outcome: 'bound', + shortcuts: GLOBAL_SHORTCUT_ACTIONS.map((id) => ({id, description: id, triggerDescription: 'F13'})), + }), + configure: async () => {}, + close: () => { + fake.closed = true; + }, + }; + harness.portals.push(fake); + return fake; + }, + desktop: 'gnome', + plasma5: false, + portalAppId: 'app.fluxer.FluxerDesktop', + getConsent: () => harness.consent, + setConsent: (consent) => { + harness.consent = consent; + }, + getDefinitions: () => [], + getParentWindow: () => '', + onShortcut: (action, phase) => engine?.handlePortalShortcut(action, phase), + onStatusChanged: () => engine?.notifyStatus(), + schedule: () => ({cancel: () => {}}), + now: () => 0, + log: () => {}, + }) + : null; + engine = new GlobalShortcutsEngine({ + platform, + linux, + settings, + portal, + createHookBackend: (kind) => { + const hook = new FakeHook(kind, harness); + harness.hooks.push(hook); + return hook; + }, + isDirectInputAvailable: () => harness.keyboardReadable, + watchInputDevices: (onChange) => { + const watch = {onChange, closed: false, close: () => (watch.closed = true)}; + harness.deviceWatches.push(watch); + return watch; + }, + schedule, + log: () => {}, + }); + harness.engine = engine; + harness.settings = settings; + harness.portal = portal; + harness.attach = (id) => engine.attachClient(id, (channel, payload) => harness.sent.push({id, channel, payload})); + harness.events = (id) => + harness.sent + .filter((entry) => entry.id === id && entry.channel === 'global-shortcut-event') + .map((entry) => `${entry.payload.phase}:${entry.payload.sourceId}`); + harness.captured = (id) => + harness.sent + .filter((entry) => entry.id === id && entry.channel === 'global-shortcuts:capture') + .map((entry) => entry.payload); + harness.activeHook = () => harness.hooks.find((hook) => hook.onEvent && !hook.stopped); + return harness; +} + +function syncPayload(bindings, actions = []) { + return sanitizeGlobalShortcutsSyncPayload({bindings, actions}); +} + +async function settle() { + for (let index = 0; index < 20; index += 1) await Promise.resolve(); +} + +describe('sanitizeGlobalShortcutsSyncPayload', () => { + test('accepts well formed payloads and filters unknown actions', () => { + const payload = sanitizeGlobalShortcutsSyncPayload({ + bindings: [ + { + sourceId: 'default:voice_toggle_mute', + action: 'voice_toggle_mute', + combo: combo({key: 'm', ctrl: true}), + }, + ], + actions: [ + { + action: 'voice_toggle_mute', + description: 'Toggle mute', + preferredCombo: combo({key: 'm', ctrl: true}), + }, + {action: 'not_global', description: 'Nope', preferredCombo: null}, + {action: 'voice_push_to_talk', description: ' ', preferredCombo: null}, + ], + }); + assert.equal(payload.bindings.length, 1); + assert.deepEqual( + payload.actions.map((entry) => entry.action), + ['voice_toggle_mute'], + ); + assert.deepEqual(storedActionsFromSync(payload.actions), [ + {action: 'voice_toggle_mute', description: 'Toggle mute', preferredTrigger: 'CTRL+m'}, + ]); + }); + + test('ignores fields it does not know so older renderers still sync', () => { + const payload = sanitizeGlobalShortcutsSyncPayload({ + bindings: [ + {sourceId: 'custom:a', action: 'voice_push_to_talk', hold: true, extra: 1, combo: combo({key: 'F13'})}, + ], + actions: [{action: 'voice_push_to_talk', description: 'Push to talk', hold: true, preferredCombo: null}], + version: 3, + }); + assert.deepEqual(payload, { + bindings: [{sourceId: 'custom:a', action: 'voice_push_to_talk', combo: combo({key: 'F13'})}], + actions: [{action: 'voice_push_to_talk', description: 'Push to talk', preferredCombo: null}], + }); + }); + + test('rejects malformed bindings', () => { + assert.equal(sanitizeGlobalShortcutsSyncPayload(null), null); + assert.equal(sanitizeGlobalShortcutsSyncPayload({bindings: {}, actions: []}), null); + assert.equal( + sanitizeGlobalShortcutsSyncPayload({bindings: [{sourceId: 'a', action: 'b', combo: {key: 1}}], actions: []}), + null, + ); + assert.equal( + sanitizeGlobalShortcutsSyncPayload({ + bindings: [{sourceId: 'a', action: 'b', combo: combo({key: '', mouseButton: 99})}], + actions: [], + }), + null, + ); + }); + + test('portal definitions always cover all ten actions with non-empty descriptions', () => { + const definitions = portalDefinitionsFromStored([ + {action: 'voice_push_to_talk', description: 'Sprechen', preferredTrigger: 'F13'}, + ]); + assert.deepEqual( + definitions.map((entry) => entry.id), + [...GLOBAL_SHORTCUT_ACTIONS], + ); + assert.ok(definitions.every((entry) => entry.description.length > 0)); + assert.deepEqual( + definitions.find((entry) => entry.id === 'voice_push_to_talk'), + {id: 'voice_push_to_talk', description: 'Sprechen', preferredTrigger: 'F13'}, + ); + assert.deepEqual( + definitions.find((entry) => entry.id === 'voice_toggle_mute'), + { + id: 'voice_toggle_mute', + description: 'Toggle mute', + }, + ); + }); +}); + +describe('portalDefinitionsFromStored safety', () => { + test('never sends blank descriptions, duplicates or malformed triggers', () => { + const definitions = portalDefinitionsFromStored([ + {action: 'voice_toggle_mute', description: ' ', preferredTrigger: 'CTRL+,'}, + {action: 'voice_toggle_mute', description: 'Again', preferredTrigger: 'F1'}, + {action: 'voice_disconnect', description: 'Leave', preferredTrigger: 'NUM+KP_1'}, + {action: 'voice_push_to_talk', description: 'Talk', preferredTrigger: 'SHIFT+F13'}, + ]); + const ids = definitions.map((entry) => entry.id); + assert.equal(new Set(ids).size, GLOBAL_SHORTCUT_ACTIONS.length); + assert.ok(definitions.every((entry) => entry.description.trim().length > 0)); + const byId = Object.fromEntries(definitions.map((entry) => [entry.id, entry])); + assert.equal(byId.voice_disconnect.preferredTrigger, undefined); + assert.equal(byId.voice_push_to_talk.preferredTrigger, 'SHIFT+F13'); + assert.equal(byId.voice_toggle_mute.description.trim().length > 0, true); + }); +}); + +describe('GlobalShortcutsEngine hook backends', () => { + test('starts the hook only once a client has bindings and emits press/release to that client', async () => { + const harness = createEngine({session: 'x11', settings: createSettings({migrated: true})}); + harness.attach(1); + harness.attach(2); + await harness.engine.refreshHooks(); + assert.deepEqual(harness.started, []); + harness.engine.sync( + 1, + syncPayload([{sourceId: 'custom:a', action: 'voice_push_to_talk', combo: combo({key: 'F13', code: 'F13'})}]), + ); + await settle(); + assert.deepEqual(harness.started, ['x11']); + assert.equal(harness.engine.getStatus().hooksActive, true); + const hook = harness.activeHook(); + hook.onEvent(keyEvent('keydown', 'F13', 191)); + hook.onEvent(keyEvent('keyup', 'F13', 191)); + assert.deepEqual(harness.events(1), ['press:custom:a', 'release:custom:a']); + assert.deepEqual(harness.events(2), []); + }); + + test('pause suppresses presses only', async () => { + const harness = createEngine({session: 'x11', settings: createSettings({migrated: true})}); + harness.attach(1); + harness.engine.sync( + 1, + syncPayload([{sourceId: 'custom:a', action: 'voice_push_to_talk', combo: combo({key: 'F13', code: 'F13'})}]), + ); + await settle(); + const hook = harness.activeHook(); + hook.onEvent(keyEvent('keydown', 'F13', 191)); + harness.engine.setPaused(1, true); + hook.onEvent(keyEvent('keyup', 'F13', 191)); + hook.onEvent(keyEvent('keydown', 'F13', 191)); + assert.deepEqual(harness.events(1), ['press:custom:a', 'release:custom:a']); + }); + + test('removing a binding mid-hold releases it and stops the idle hook', async () => { + const harness = createEngine({session: 'x11', settings: createSettings({migrated: true})}); + harness.attach(1); + harness.engine.sync( + 1, + syncPayload([{sourceId: 'custom:a', action: 'voice_push_to_talk', combo: combo({key: 'F13', code: 'F13'})}]), + ); + await settle(); + harness.activeHook().onEvent(keyEvent('keydown', 'F13', 191)); + harness.engine.sync(1, syncPayload([])); + await settle(); + assert.deepEqual(harness.events(1), ['press:custom:a', 'release:custom:a']); + assert.deepEqual(harness.stopped, ['x11']); + assert.equal(harness.engine.getStatus().hooksActive, false); + }); + + test('a failed start is reported and retried on the next sync', async () => { + const harness = createEngine({platform: 'macos'}); + harness.startResults.push('permission'); + harness.attach(1); + const payload = syncPayload([ + {sourceId: 'custom:a', action: 'voice_toggle_mute', combo: combo({key: 'm', meta: true})}, + ]); + harness.engine.sync(1, payload); + await settle(); + assert.equal(harness.engine.getStatus().hookError, 'permission'); + assert.equal(harness.engine.getStatus().backend, 'macos'); + harness.engine.sync(1, payload); + await settle(); + assert.deepEqual(harness.started, ['macos', 'macos']); + assert.equal(harness.engine.getStatus().hookError, null); + assert.equal(harness.engine.getStatus().hooksActive, true); + }); + + test('a backend dropped after its start resolves releases the presses it already sent', async () => { + const harness = createEngine({session: 'x11', keyboardReadable: true, settings: createSettings({migrated: true})}); + let resolveStart; + harness.startResults.push(new Promise((resolve) => (resolveStart = resolve))); + harness.attach(1); + harness.engine.sync( + 1, + syncPayload([{sourceId: 'custom:a', action: 'voice_push_to_talk', combo: combo({key: 'F9', code: 'F9'})}]), + ); + await settle(); + assert.deepEqual(harness.started, ['x11']); + harness.hooks[0].pendingOnEvent(keyEvent('keydown', 'F9', 75)); + assert.deepEqual(harness.events(1), ['press:custom:a']); + harness.engine.setDirectInputEnabled(true); + resolveStart('ok'); + await settle(); + assert.deepEqual(harness.stopped, ['x11']); + assert.deepEqual(harness.started, ['x11', 'evdev']); + assert.deepEqual(harness.events(1), ['press:custom:a', 'release:custom:a']); + }); + + test('detaching a client stops the hook without touching other clients', async () => { + const harness = createEngine({platform: 'windows'}); + harness.attach(1); + harness.attach(2); + const binding = [{sourceId: 'custom:a', action: 'voice_push_to_talk', combo: combo({key: 'F13', code: 'F13'})}]; + harness.engine.sync(1, syncPayload(binding)); + harness.engine.sync(2, syncPayload(binding)); + await settle(); + harness.engine.detachClient(1); + await settle(); + assert.deepEqual(harness.stopped, []); + harness.activeHook().onEvent(keyEvent('keydown', 'F13', 0x64)); + assert.deepEqual(harness.events(2), ['press:custom:a']); + assert.deepEqual(harness.events(1), []); + }); +}); + +describe('GlobalShortcutsEngine Linux backend selection', () => { + test('Wayland uses the portal and never starts a hook', async () => { + const harness = createEngine({session: 'wayland', settings: createSettings({migrated: true})}); + harness.engine.start(); + harness.attach(1); + harness.engine.sync( + 1, + syncPayload([{sourceId: 'custom:a', action: 'voice_push_to_talk', combo: combo({key: 'F13', code: 'F13'})}]), + ); + await settle(); + const status = harness.engine.getStatus(); + assert.equal(status.backend, 'portal'); + assert.equal(status.linux.portal.state, 'not-set-up'); + assert.equal(status.supportsMouseButtons, false); + assert.deepEqual(harness.started, []); + }); + + test('portal shortcuts reach clients as portal sources', async () => { + const harness = createEngine({ + session: 'wayland', + portalConsent: 'granted', + settings: createSettings({migrated: true}), + }); + harness.engine.start(); + harness.attach(1); + await settle(); + harness.portals[0].onEvent({type: 'activated', id: 'voice_push_to_talk'}); + harness.engine.setPaused(1, true); + harness.portals[0].onEvent({type: 'deactivated', id: 'voice_push_to_talk'}); + harness.portals[0].onEvent({type: 'activated', id: 'voice_toggle_mute'}); + assert.deepEqual(harness.events(1), ['press:portal:voice_push_to_talk', 'release:portal:voice_push_to_talk']); + }); + + test('the first sync migrates readable keyboards to direct input and closes the portal', async () => { + const harness = createEngine({session: 'wayland', keyboardReadable: true, portalConsent: 'granted'}); + harness.engine.start(); + harness.attach(1); + await settle(); + assert.equal(harness.portals.length, 1); + harness.engine.sync( + 1, + syncPayload([{sourceId: 'custom:a', action: 'voice_push_to_talk', combo: combo({key: '', mouseButton: 3})}]), + ); + await settle(); + assert.equal(harness.settings.state.migrated, true); + assert.equal(harness.settings.state.directInputEnabled, true); + assert.equal(harness.portals[0].closed, true); + assert.equal(harness.engine.getStatus().backend, 'evdev'); + assert.deepEqual(harness.started, ['evdev']); + }); + + test('migration only marks itself done when no keyboard is readable', async () => { + const harness = createEngine({session: 'wayland', keyboardReadable: false}); + harness.engine.start(); + harness.attach(1); + harness.engine.sync( + 1, + syncPayload([{sourceId: 'custom:a', action: 'voice_push_to_talk', combo: combo({key: 'F13', code: 'F13'})}]), + ); + await settle(); + assert.equal(harness.settings.state.migrated, true); + assert.equal(harness.settings.state.directInputEnabled, false); + assert.equal(harness.engine.getStatus().backend, 'portal'); + }); + + test('direct input is ignored when no keyboard is readable', () => { + const harness = createEngine({ + session: 'x11', + settings: createSettings({directInputEnabled: true, migrated: true}), + }); + assert.equal(harness.engine.getStatus().backend, 'x11'); + assert.deepEqual(harness.engine.getStatus().linux.directInput, {available: false, enabled: true, locked: false}); + }); + + test('direct input is probed again when a keyboard shows up after launch', async () => { + const harness = createEngine({ + session: 'wayland', + settings: createSettings({directInputEnabled: true, migrated: true}), + }); + harness.attach(1); + await settle(); + assert.equal(harness.engine.getStatus().backend, 'portal'); + harness.keyboardReadable = true; + harness.engine.sync( + 1, + syncPayload([{sourceId: 'custom:a', action: 'voice_push_to_talk', combo: combo({key: 'F13', code: 'F13'})}]), + ); + await settle(); + assert.equal(harness.engine.getStatus().backend, 'evdev'); + assert.equal(harness.engine.getStatus().linux.directInput.available, true); + assert.deepEqual(harness.started, ['evdev']); + assert.equal(harness.portals[0]?.closed ?? true, true); + }); + + test('a keyboard plugged in after launch is picked up from the input device watch', async () => { + const harness = createEngine({ + session: 'wayland', + settings: createSettings({directInputEnabled: true, migrated: true}), + }); + harness.engine.start(); + harness.attach(1); + harness.engine.sync( + 1, + syncPayload([{sourceId: 'custom:a', action: 'voice_push_to_talk', combo: combo({key: 'F13', code: 'F13'})}]), + ); + await settle(); + assert.equal(harness.engine.getStatus().backend, 'portal'); + assert.equal(harness.deviceWatches.length, 1); + const watch = harness.deviceWatches[0]; + watch.onChange(); + watch.onChange(); + assert.deepEqual( + harness.timers.map((timer) => [timer.delayMs, timer.cancelled]), + [ + [500, true], + [500, false], + ], + ); + harness.timers[1].callback(); + await settle(); + assert.equal(watch.closed, false); + assert.deepEqual(harness.started, []); + harness.keyboardReadable = true; + watch.onChange(); + harness.timers[2].callback(); + await settle(); + assert.equal(watch.closed, true); + assert.equal(harness.engine.getStatus().backend, 'evdev'); + assert.deepEqual(harness.started, ['evdev']); + assert.equal(harness.deviceWatches.length, 1); + }); + + test('the input device watch only runs while direct input is on and unavailable', () => { + const harness = createEngine({session: 'wayland', settings: createSettings({migrated: true})}); + harness.engine.start(); + assert.equal(harness.deviceWatches.length, 0); + harness.engine.setDirectInputEnabled(true); + assert.equal(harness.deviceWatches.length, 1); + harness.deviceWatches[0].onChange(); + harness.engine.setDirectInputEnabled(false); + assert.equal(harness.deviceWatches[0].closed, true); + assert.equal(harness.timers[0].cancelled, true); + harness.engine.setDirectInputEnabled(true); + assert.equal(harness.deviceWatches.length, 2); + harness.engine.dispose(); + assert.equal(harness.deviceWatches[1].closed, true); + }); + + test('a hook start attempt notices a keyboard that went away', async () => { + const harness = createEngine({ + session: 'x11', + keyboardReadable: true, + settings: createSettings({directInputEnabled: true, migrated: true}), + }); + assert.equal(harness.engine.getStatus().backend, 'evdev'); + harness.keyboardReadable = false; + harness.attach(1); + harness.engine.sync( + 1, + syncPayload([{sourceId: 'custom:a', action: 'voice_push_to_talk', combo: combo({key: 'F13', code: 'F13'})}]), + ); + await settle(); + assert.equal(harness.engine.getStatus().backend, 'x11'); + assert.deepEqual(harness.started, ['x11']); + }); + + test('a launch flag locks the direct input setting', () => { + const harness = createEngine({session: 'wayland', inputHookMode: 'evdev', keyboardReadable: true}); + assert.equal(harness.engine.getStatus().linux.directInput.locked, true); + }); + + test('disabling direct input hands Wayland back to the portal', async () => { + const harness = createEngine({ + session: 'wayland', + keyboardReadable: true, + settings: createSettings({directInputEnabled: true, migrated: true}), + }); + harness.engine.start(); + harness.attach(1); + assert.equal(harness.engine.getStatus().backend, 'evdev'); + harness.engine.setDirectInputEnabled(false); + await settle(); + assert.equal(harness.engine.getStatus().backend, 'portal'); + assert.equal(harness.engine.getStatus().linux.portal.state, 'not-set-up'); + }); + + test('launch modes off and native refuse backends', () => { + assert.equal(createEngine({session: 'x11', inputHookMode: 'off'}).engine.getStatus().backend, 'none'); + assert.equal(createEngine({session: 'wayland', inputHookMode: 'native'}).engine.getStatus().backend, 'none'); + assert.equal(createEngine({session: 'x11', inputHookMode: 'native'}).engine.getStatus().backend, 'x11'); + assert.equal( + createEngine({session: 'wayland', inputHookMode: 'evdev', keyboardReadable: true}).engine.getStatus().backend, + 'evdev', + ); + }); + + test('an unsupported portal reports backend none', async () => { + const harness = createEngine({session: 'wayland', settings: createSettings({migrated: true})}); + harness.portal.deps.createPortal = () => ({ + open: async () => { + throw new Error('unsupported:2'); + }, + bind: async () => ({outcome: 'denied'}), + configure: async () => {}, + close: () => {}, + }); + harness.engine.start(); + harness.attach(1); + await settle(); + assert.equal(harness.engine.getStatus().backend, 'none'); + assert.equal(harness.engine.getStatus().linux.portal.state, 'unsupported'); + }); +}); + +describe('GlobalShortcutsEngine release on suspend', () => { + test('releaseAllPressed releases hook holds without stopping the hook', async () => { + const harness = createEngine({session: 'x11', settings: createSettings({migrated: true})}); + harness.attach(1); + harness.engine.sync( + 1, + syncPayload([{sourceId: 'custom:a', action: 'voice_push_to_talk', combo: combo({key: 'F13', code: 'F13'})}]), + ); + await settle(); + const hook = harness.activeHook(); + hook.onEvent(keyEvent('keydown', 'F13', 191)); + harness.engine.releaseAllPressed(); + hook.onEvent(keyEvent('keyup', 'F13', 191)); + assert.deepEqual(harness.events(1), ['press:custom:a', 'release:custom:a']); + assert.deepEqual(harness.stopped, []); + hook.onEvent(keyEvent('keydown', 'F13', 191)); + assert.deepEqual(harness.events(1), ['press:custom:a', 'release:custom:a', 'press:custom:a']); + }); + + test('releaseAllPressed releases portal holds', async () => { + const harness = createEngine({ + session: 'wayland', + portalConsent: 'granted', + settings: createSettings({migrated: true}), + }); + harness.engine.start(); + harness.attach(1); + await settle(); + harness.portals[0].onEvent({type: 'activated', id: 'voice_push_to_talk'}); + harness.engine.releaseAllPressed(); + harness.portals[0].onEvent({type: 'deactivated', id: 'voice_push_to_talk'}); + assert.deepEqual(harness.events(1), ['press:portal:voice_push_to_talk', 'release:portal:voice_push_to_talk']); + }); + + test('a release-all on lock lets the next portal press through', async () => { + const harness = createEngine({ + session: 'wayland', + portalConsent: 'granted', + settings: createSettings({migrated: true}), + }); + harness.attach(1); + await settle(); + const portal = harness.portals[0]; + portal.onEvent({type: 'activated', id: 'voice_push_to_talk'}); + harness.engine.releaseAllPressed(); + portal.onEvent({type: 'activated', id: 'voice_push_to_talk'}); + assert.deepEqual(harness.events(1), [ + 'press:portal:voice_push_to_talk', + 'release:portal:voice_push_to_talk', + 'press:portal:voice_push_to_talk', + ]); + }); + + test('a portal release is only sent for a press the client received', async () => { + const harness = createEngine({ + session: 'wayland', + portalConsent: 'granted', + settings: createSettings({migrated: true}), + }); + harness.attach(1); + await settle(); + harness.engine.setPaused(1, true); + harness.portals[0].onEvent({type: 'activated', id: 'voice_push_to_talk'}); + harness.engine.setPaused(1, false); + harness.portals[0].onEvent({type: 'deactivated', id: 'voice_push_to_talk'}); + harness.engine.releaseAllPressed(); + assert.deepEqual(harness.events(1), []); + }); +}); + +describe('GlobalShortcutsEngine portal lifecycle', () => { + test('the portal stays closed until a client of the new API calls in', async () => { + const harness = createEngine({ + session: 'wayland', + portalConsent: 'granted', + settings: createSettings({migrated: true}), + }); + harness.engine.start(); + await settle(); + assert.equal(harness.portals.length, 0); + assert.equal(harness.portal.getState(), 'unknown'); + harness.attach(1); + await settle(); + assert.equal(harness.portals.length, 1); + assert.equal(harness.portal.getState(), 'bound'); + }); + + test('check again re-probes an unsupported portal', async () => { + const harness = createEngine({session: 'wayland', settings: createSettings({migrated: true})}); + const create = harness.portal.deps.createPortal; + harness.portal.deps.createPortal = () => { + harness.portal.deps.createPortal = create; + return { + open: async () => { + throw new Error('unsupported:interface'); + }, + bind: async () => ({outcome: 'denied'}), + configure: async () => {}, + close: () => {}, + }; + }; + harness.attach(1); + await settle(); + assert.equal(harness.engine.getStatus().backend, 'none'); + await harness.engine.recheck(); + assert.equal(harness.engine.getStatus().backend, 'portal'); + assert.equal(harness.engine.getStatus().linux.portal.state, 'not-set-up'); + }); + + test('set up on an unsupported portal checks again instead of binding', async () => { + const harness = createEngine({session: 'wayland', settings: createSettings({migrated: true})}); + const create = harness.portal.deps.createPortal; + harness.portal.deps.createPortal = () => { + harness.portal.deps.createPortal = create; + return { + open: async () => { + throw new Error('unsupported:interface'); + }, + bind: async () => ({outcome: 'denied'}), + configure: async () => {}, + close: () => {}, + }; + }; + harness.attach(1); + await settle(); + await harness.engine.setUp(); + assert.equal(harness.engine.getStatus().linux.portal.state, 'not-set-up'); + assert.equal(harness.consent, 'unset'); + }); +}); + +describe('GlobalShortcutsEngine capture', () => { + test('captures raw input on a hook backend for the requesting client only', async () => { + const harness = createEngine({session: 'x11', settings: createSettings({migrated: true})}); + harness.attach(1); + harness.attach(2); + const captureId = await harness.engine.startCapture(1); + assert.equal(typeof captureId, 'number'); + assert.deepEqual(harness.started, ['x11']); + const hook = harness.activeHook(); + hook.onEvent({...keyEvent('keydown', 'PrintScreen', 107, {ctrl: true}), key: 'Print'}); + hook.onEvent({ + type: 'mousedown', + button: 4, + ctrlKey: false, + altKey: false, + shiftKey: false, + metaKey: false, + native: null, + }); + assert.deepEqual(harness.captured(1), [ + { + type: 'keydown', + code: 'PrintScreen', + key: 'Print', + button: null, + ctrl: true, + alt: false, + shift: false, + meta: false, + }, + {type: 'mousedown', code: null, key: null, button: 4, ctrl: false, alt: false, shift: false, meta: false}, + ]); + assert.deepEqual(harness.captured(2), []); + harness.engine.stopCapture(2, captureId); + hook.onEvent(keyEvent('keyup', 'PrintScreen', 107)); + assert.equal(harness.captured(1).length, 3); + harness.engine.stopCapture(1, captureId); + await settle(); + assert.deepEqual(harness.stopped, ['x11']); + assert.equal(harness.timers[0].cancelled, true); + }); + + test('capture stops on its own after a minute and when the client goes away', async () => { + const harness = createEngine({platform: 'windows'}); + harness.attach(1); + assert.notEqual(await harness.engine.startCapture(1), null); + assert.equal(harness.timers[0].delayMs, engineModule.GLOBAL_SHORTCUT_CAPTURE_TIMEOUT_MS); + harness.timers[0].callback(); + await settle(); + assert.deepEqual(harness.stopped, ['windows']); + assert.notEqual(await harness.engine.startCapture(1), null); + harness.engine.detachClient(1); + await settle(); + assert.deepEqual(harness.stopped, ['windows', 'windows']); + }); + + test('capture is refused on the portal and when the hook cannot start', async () => { + const wayland = createEngine({session: 'wayland', settings: createSettings({migrated: true})}); + wayland.attach(1); + assert.equal(await wayland.engine.startCapture(1), null); + assert.deepEqual(wayland.started, []); + const mac = createEngine({platform: 'macos'}); + mac.startResults.push('permission'); + mac.attach(1); + assert.equal(await mac.engine.startCapture(1), null); + assert.deepEqual(mac.started, ['macos']); + assert.equal(await mac.engine.startCapture(2), null); + }); + + test('a late stop from a replaced capture leaves the newer capture running', async () => { + const harness = createEngine({platform: 'windows'}); + harness.attach(1); + const first = await harness.engine.startCapture(1); + const secondStart = harness.engine.startCapture(1); + harness.engine.stopCapture(1, first); + const second = await secondStart; + assert.notEqual(second, null); + assert.notEqual(second, first); + await settle(); + assert.deepEqual(harness.stopped, []); + harness.activeHook().onEvent(keyEvent('keydown', 'F13', 100)); + assert.equal(harness.captured(1).length, 1); + harness.timers[0].callback(); + await settle(); + assert.deepEqual(harness.stopped, []); + harness.engine.stopCapture(1, second); + await settle(); + assert.deepEqual(harness.stopped, ['windows']); + }); +}); + +describe('GlobalShortcutsNative translation', () => { + function nativeKey(type, overrides) { + return {type, ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, ...overrides}; + } + + test('an injected Windows key without a scan code matches by virtual key', () => { + const matcher = new GlobalShortcutMatcher(); + matcher.setBindings([bindingFromCombo('custom:a', 'voice_push_to_talk', combo({key: 'F13', code: 'F13'}))]); + const identity = {keycode: 0x7c, keyName: 'F13', scanCode: 0, extended: false}; + const down = native.translateNativeHookEvent('windows', nativeKey('keydown', identity)); + const up = native.translateNativeHookEvent('windows', nativeKey('keyup', identity)); + assert.equal(down.code, 'F13'); + assert.equal(matcher.handleKey(down, true).length, 1); + assert.equal(matcher.handleKey(up, true).length, 1); + }); + + test('a Windows modifier keydown sets its own flag so a Ctrl then Shift capture keeps both', () => { + const ctrl = {keycode: 0xa2, keyName: 'ControlLeft', scanCode: 0x1d, extended: false}; + const shift = {keycode: 0xa0, keyName: 'ShiftLeft', scanCode: 0x2a, extended: false}; + const ctrlDown = native.translateNativeHookEvent('windows', nativeKey('keydown', ctrl)); + assert.equal(ctrlDown.ctrlKey, true); + const shiftDown = native.translateNativeHookEvent('windows', nativeKey('keydown', {...shift, ctrlKey: true})); + assert.deepEqual( + [shiftDown.ctrlKey, shiftDown.shiftKey, shiftDown.altKey, shiftDown.metaKey], + [true, true, false, false], + ); + const shiftUp = native.translateNativeHookEvent('windows', nativeKey('keyup', {...shift, ctrlKey: true})); + assert.equal(shiftUp.shiftKey, false); + const x11 = native.translateNativeHookEvent( + 'x11', + nativeKey('keydown', {keycode: 0xffe1, keyName: 'ShiftLeft', x11Keycode: 50}), + ); + assert.equal(x11.shiftKey, false); + }); + + test('a Windows Ctrl then Shift capture saves a binding that ignores Ctrl alone', async () => { + const harness = createEngine({platform: 'windows'}); + harness.attach(1); + assert.notEqual(await harness.engine.startCapture(1), null); + const hook = harness.activeHook(); + const ctrl = {keycode: 0xa2, keyName: 'ControlLeft', scanCode: 0x1d, extended: false}; + const shift = {keycode: 0xa0, keyName: 'ShiftLeft', scanCode: 0x2a, extended: false}; + hook.onEvent(native.translateNativeHookEvent('windows', nativeKey('keydown', ctrl))); + hook.onEvent(native.translateNativeHookEvent('windows', nativeKey('keydown', {...shift, ctrlKey: true}))); + const last = harness.captured(1).at(-1); + assert.deepEqual([last.code, last.ctrl, last.shift], ['ShiftLeft', true, true]); + const binding = bindingFromCombo( + 'custom:a', + 'voice_push_to_talk', + combo({key: 'Shift', code: last.code, ctrl: last.ctrl, shift: last.shift, modifierOnly: true}), + ); + const matcher = new GlobalShortcutMatcher(); + matcher.setBindings([binding]); + const ctrlOnly = native.translateNativeHookEvent('windows', nativeKey('keydown', ctrl)); + assert.deepEqual(matcher.handleKey(ctrlOnly, true), []); + }); + + test('evdev keys without a DOM code are dropped and every mapped key passes through', () => { + const unmapped = native.translateNativeHookEvent('evdev', nativeKey('keydown', {keycode: 0x2ff, keyName: ''})); + assert.equal(unmapped, null); + const pause = native.translateNativeHookEvent('evdev', nativeKey('keydown', {keycode: 119, keyName: ''})); + assert.equal(pause.code, 'Pause'); + const calculator = native.translateNativeHookEvent('evdev', nativeKey('keydown', {keycode: 140, keyName: ''})); + assert.equal(calculator.code, 'LaunchApp2'); + }); + + test('an old renderer media select registration matches the media select key', () => { + const binding = legacy.legacyRegistrationToBinding({id: 'key:media', keyName: 'LaunchMediaPlayer'}); + assert.deepEqual(binding.trigger, {kind: 'key', layoutKey: 'LaunchMediaPlayer', code: 'MediaSelect'}); + const matcher = new GlobalShortcutMatcher(); + matcher.setBindings([binding]); + const identity = {keycode: 0xb5, keyName: 'LaunchMediaPlayer', scanCode: 0x6d, extended: true}; + assert.equal( + matcher.handleKey(native.translateNativeHookEvent('windows', nativeKey('keydown', identity)), true).length, + 1, + ); + }); + + test('an X11 keysym without a name falls back to the physical code', () => { + const matcher = new GlobalShortcutMatcher(); + matcher.setBindings([ + bindingFromCombo('custom:a', 'voice_toggle_mute', combo({key: 'q', code: 'KeyQ', ctrl: true})), + ]); + const event = native.translateNativeHookEvent( + 'x11', + nativeKey('keydown', {keycode: 0x6ca, keyName: '', x11Keycode: 24, ctrlKey: true}), + ); + assert.equal(event.key, null); + assert.equal(event.code, 'KeyQ'); + assert.equal(matcher.handleKey(event, true).length, 1); + }); + + test('X11 Right Alt survives events whose Mod1 flag is clear on AltGr layouts', () => { + const matcher = new GlobalShortcutMatcher(); + matcher.setBindings([ + bindingFromCombo( + 'custom:a', + 'voice_push_to_talk', + combo({key: 'AltGraph', code: 'AltRight', modifierOnly: true}), + ), + ]); + const down = native.translateNativeHookEvent( + 'x11', + nativeKey('keydown', {keycode: 0xfe03, keyName: '', x11Keycode: 108}), + ); + const click = native.translateNativeHookEvent('x11', { + type: 'mousedown', + button: 0, + ctrlKey: false, + altKey: false, + shiftKey: false, + metaKey: false, + }); + const other = native.translateNativeHookEvent( + 'x11', + nativeKey('keydown', {keycode: 0x71, keyName: 'Q', x11Keycode: 24}), + ); + const up = native.translateNativeHookEvent( + 'x11', + nativeKey('keyup', {keycode: 0xfe03, keyName: '', x11Keycode: 108}), + ); + assert.deepEqual(matcher.handleKey(down, true), [ + {sourceId: 'custom:a', action: 'voice_push_to_talk', phase: 'press'}, + ]); + assert.deepEqual(matcher.handleMouse(click, true), []); + assert.deepEqual(matcher.handleKey(other, true), []); + assert.deepEqual(matcher.handleKey(up, true), [ + {sourceId: 'custom:a', action: 'voice_push_to_talk', phase: 'release'}, + ]); + }); + + test('a left Alt hold still recovers from a lost key-up on X11', () => { + const matcher = new GlobalShortcutMatcher(); + matcher.setBindings([ + bindingFromCombo('custom:a', 'voice_push_to_talk', combo({key: 'Alt', code: 'AltLeft', modifierOnly: true})), + ]); + const down = native.translateNativeHookEvent( + 'x11', + nativeKey('keydown', {keycode: 0xffe9, keyName: 'AltLeft', x11Keycode: 64}), + ); + const other = native.translateNativeHookEvent( + 'x11', + nativeKey('keydown', {keycode: 0x71, keyName: 'Q', x11Keycode: 24}), + ); + assert.equal(matcher.handleKey(down, true).length, 1); + assert.deepEqual(matcher.handleKey(other, true), [ + {sourceId: 'custom:a', action: 'voice_push_to_talk', phase: 'release'}, + ]); + }); + + test('Windows unicode packets report the typed character, and a space packet is the Space key', () => { + const matcher = new GlobalShortcutMatcher(); + matcher.setBindings([ + bindingFromCombo('custom:f13', 'voice_push_to_talk', combo({key: 'F13', code: 'F13'})), + bindingFromCombo('custom:space', 'voice_toggle_mute', combo({key: ' ', code: 'Space'})), + bindingFromCombo( + 'custom:shift', + 'voice_push_to_mute', + combo({key: 'Shift', code: 'ShiftRight', modifierOnly: true}), + ), + ]); + for (const character of ['d', '9', '6', '8']) { + const identity = {keycode: 0xe7, keyName: 'Key231', scanCode: character.charCodeAt(0), extended: false}; + const down = native.translateNativeHookEvent('windows', nativeKey('keydown', identity)); + assert.equal(down.code, null); + assert.equal(down.key, character.toUpperCase()); + assert.deepEqual(matcher.handleKey(down, true), []); + assert.deepEqual( + matcher.handleKey(native.translateNativeHookEvent('windows', nativeKey('keyup', identity)), true), + [], + ); + } + const letter = new GlobalShortcutMatcher(); + letter.setBindings([bindingFromCombo('custom:d', 'voice_toggle_mute', combo({key: 'd', code: 'KeyD'}))]); + const packetD = {keycode: 0xe7, keyName: 'Key231', scanCode: 0x64, extended: false}; + const packetE = {keycode: 0xe7, keyName: 'Key231', scanCode: 0x65, extended: false}; + assert.equal( + letter.handleKey(native.translateNativeHookEvent('windows', nativeKey('keydown', packetD)), true).length, + 1, + ); + assert.deepEqual( + letter.handleKey(native.translateNativeHookEvent('windows', nativeKey('keyup', packetE)), true), + [], + ); + assert.equal( + letter.handleKey(native.translateNativeHookEvent('windows', nativeKey('keyup', packetD)), true).length, + 1, + ); + const packetSpace = {keycode: 0xe7, keyName: 'Space', scanCode: 0x20, extended: false}; + const spaceDown = native.translateNativeHookEvent('windows', nativeKey('keydown', packetSpace)); + assert.equal(spaceDown.code, 'Space'); + assert.deepEqual( + matcher.handleKey(spaceDown, true).map((transition) => `${transition.phase}:${transition.sourceId}`), + ['press:custom:space'], + ); + assert.deepEqual( + matcher + .handleKey(native.translateNativeHookEvent('windows', nativeKey('keyup', packetSpace)), true) + .map((transition) => `${transition.phase}:${transition.sourceId}`), + ['release:custom:space'], + ); + }); + + test('injected Windows keys without a scan code resolve every virtual key the old names covered', () => { + const expected = [ + [0x0c, 'Numpad5'], + [0x15, 'KanaMode'], + [0x1c, 'Convert'], + [0x1d, 'NonConvert'], + [0x5f, 'Sleep'], + [0x6c, 'NumpadComma'], + [0x92, 'NumpadEqual'], + [0xa6, 'BrowserBack'], + [0xa7, 'BrowserForward'], + [0xa8, 'BrowserRefresh'], + [0xa9, 'BrowserStop'], + [0xaa, 'BrowserSearch'], + [0xab, 'BrowserFavorites'], + [0xac, 'BrowserHome'], + [0xb4, 'LaunchMail'], + [0xb5, 'MediaSelect'], + [0xb6, 'LaunchApp1'], + [0xb7, 'LaunchApp2'], + [0x87, 'F24'], + [0xde, 'Quote'], + [0xe2, 'IntlBackslash'], + ]; + for (const [vk, code] of expected) { + const identity = {keycode: vk, keyName: 'x', scanCode: 0, extended: false}; + assert.equal(native.translateNativeHookEvent('windows', nativeKey('keydown', identity)).code, code, code); + } + }); +}); + +function loadNativeWith(modules) { + return loadTsModule('@electron/main/GlobalShortcutsNative', { + stubs: { + 'node:module': {createRequire: () => (name) => modules[name] ?? {loadError: null}}, + '@electron/common/Logger': {createChildLogger: () => ({info: () => {}, warn: () => {}})}, + '@electron/main/MacTcc': {getTccStatus: () => 'granted'}, + }, + }); +} + +describe('GlobalShortcutsNative backends', () => { + function x11Module(start) { + const instances = []; + class InputHook { + constructor(callback) { + this.callback = callback; + this.stopped = false; + instances.push(this); + } + start() { + return start(this); + } + stop() { + this.stopped = true; + } + } + return {instances, module: {InputHook, loadError: null}}; + } + + test('an asynchronous native start is awaited before the hook counts as running', async () => { + let resolveStart; + const x11 = x11Module(() => new Promise((resolve) => (resolveStart = resolve))); + const loaded = loadNativeWith({'@fluxer/linux-input-hook': x11.module}); + const backend = loaded.createNativeHookBackend('x11'); + let settled = null; + const pending = backend.start(() => {}).then((result) => (settled = result)); + await settle(); + assert.equal(settled, null); + resolveStart(); + await pending; + assert.equal(settled, 'ok'); + backend.stop(); + assert.equal(x11.instances[0].stopped, true); + }); + + test('a rejected native start stops the instance and surfaces the error', async () => { + const x11 = x11Module(() => Promise.reject(new Error('XRecord extension not available'))); + const backend = loadNativeWith({'@fluxer/linux-input-hook': x11.module}).createNativeHookBackend('x11'); + await assert.rejects( + backend.start(() => {}), + /XRecord extension not available/, + ); + assert.equal(x11.instances[0].stopped, true); + }); + + test('a synchronous native start still works', async () => { + const x11 = x11Module(() => undefined); + const backend = loadNativeWith({'@fluxer/linux-input-hook': x11.module}).createNativeHookBackend('x11'); + assert.equal(await backend.start(() => {}), 'ok'); + const failing = x11Module(() => false); + const refused = loadNativeWith({'@fluxer/linux-input-hook': failing.module}).createNativeHookBackend('x11'); + assert.equal(await refused.start(() => {}), 'start-failed'); + assert.equal(failing.instances[0].stopped, true); + }); + + test('the session monitor never loads the portals module when portals are turned off', () => { + const original = Object.getOwnPropertyDescriptor(process, 'platform'); + const previousEnv = process.env.FLUXER_DISABLE_LINUX_PORTALS; + let constructed = 0; + const portals = { + GlobalShortcutsPortal: null, + SessionStateMonitor: class { + constructor() { + constructed += 1; + } + close() {} + }, + loadError: null, + }; + Object.defineProperty(process, 'platform', {...original, value: 'linux'}); + try { + process.env.FLUXER_DISABLE_LINUX_PORTALS = '1'; + const off = loadNativeWith({'@fluxer/linux-portals': portals}); + assert.equal( + off.createLinuxSessionStateMonitor(() => {}), + null, + ); + assert.equal(constructed, 0); + delete process.env.FLUXER_DISABLE_LINUX_PORTALS; + const on = loadNativeWith({'@fluxer/linux-portals': portals}); + assert.notEqual( + on.createLinuxSessionStateMonitor(() => {}), + null, + ); + assert.equal(constructed, 1); + } finally { + Object.defineProperty(process, 'platform', original); + if (previousEnv === undefined) delete process.env.FLUXER_DISABLE_LINUX_PORTALS; + else process.env.FLUXER_DISABLE_LINUX_PORTALS = previousEnv; + } + }); +}); + +describe('LegacyGlobalKeyHookAdapter', () => { + function legacyHarness({session = 'x11', portalConsent = 'unset'} = {}) { + const harness = createEngine({session, portalConsent, settings: createSettings({migrated: true})}); + const adapter = new legacy.LegacyGlobalKeyHookAdapter(); + harness.engine.addConsumer(adapter); + harness.legacySent = []; + adapter.attach(7, (channel, payload) => harness.legacySent.push({channel, payload})); + harness.adapter = adapter; + harness.triggered = () => + harness.legacySent + .filter((entry) => entry.channel === 'global-keybind-triggered') + .map((entry) => `${entry.payload.type}:${entry.payload.id}`); + return harness; + } + + test('legacy registrations translate to bindings', () => { + assert.deepEqual( + legacy.legacyRegistrationToBinding({ + id: 'key:voice_toggle_mute:m', + keycode: 50, + keyName: 'M', + physicalKeyName: 'M', + ctrl: true, + }), + { + sourceId: 'key:voice_toggle_mute:m', + action: 'key:voice_toggle_mute:m', + trigger: {kind: 'key', layoutKey: 'M', code: 'KeyM'}, + modifiers: {ctrl: true, alt: false, shift: false, meta: false}, + }, + ); + assert.deepEqual(legacy.legacyRegistrationToBinding({id: 'mouse:voice_push_to_talk:3', mouseButton: 3}).trigger, { + kind: 'mouse', + button: 3, + }); + assert.equal(legacy.legacyRegistrationToBinding({id: 'key:x', keycode: 50}), null); + }); + + test('started legacy senders get raw events and triggered ids without the keysym collision', async () => { + const harness = legacyHarness(); + harness.adapter.register( + 7, + legacy.legacyRegistrationToBinding({id: 'key:voice_toggle_mute:m', keyName: 'M', ctrl: true}), + ); + harness.adapter.acquire(7); + await harness.engine.refreshHooks(); + const hook = harness.activeHook(); + hook.onEvent({...keyEvent('keydown', 'Digit2', 11, {ctrl: true}, {keycode: 0x32, keyName: '2'}), key: '2'}); + hook.onEvent({...keyEvent('keydown', 'KeyM', 58, {ctrl: true}, {keycode: 0x6d, keyName: 'M'}), key: 'M'}); + hook.onEvent({...keyEvent('keyup', 'KeyM', 58, {}, {keycode: 0x6d, keyName: 'M'}), key: 'M'}); + assert.deepEqual(harness.triggered(), ['keydown:key:voice_toggle_mute:m', 'keyup:key:voice_toggle_mute:m']); + const raw = harness.legacySent.filter((entry) => entry.channel === 'global-key-event'); + assert.equal(raw.length, 3); + assert.deepEqual(raw[1].payload, { + type: 'keydown', + keycode: 0x6d, + keyName: 'M', + ctrlKey: true, + altKey: false, + shiftKey: false, + metaKey: false, + backend: 'native', + }); + harness.adapter.release(7); + await harness.engine.refreshHooks(); + assert.deepEqual(harness.stopped, ['x11']); + }); + + function registerLegacy(harness) { + legacyHandlers.clear(); + const sender = { + id: 9, + isDestroyed: () => false, + send: (channel, payload) => harness.legacySent.push({channel, payload}), + }; + legacy.registerLegacyGlobalKeyHookHandlers(harness.engine, harness.adapter, { + platform: 'linux', + sandboxed: false, + username: 'u', + hasInputMonitoringAccess: () => true, + watchSender: () => {}, + }); + return (channel, ...args) => legacyHandlers.get(channel)({sender}, ...args); + } + + test('an old renderer alone never opens the portal', async () => { + const harness = legacyHarness({session: 'wayland', portalConsent: 'granted'}); + harness.engine.start(); + await settle(); + const invoke = registerLegacy(harness); + assert.equal(await invoke('global-key-hook-start'), false); + assert.equal(await invoke('global-key-hook-is-running'), false); + await invoke('global-key-hook-register', {id: 'key:voice_toggle_mute::F9:F9', keyName: 'F9'}); + assert.equal(harness.portals.length, 0); + assert.deepEqual(harness.started, []); + }); + + test('the portal closes when the last new renderer goes away and reopens for the next one', async () => { + const harness = legacyHarness({session: 'wayland', portalConsent: 'granted'}); + harness.attach(1); + await settle(); + assert.equal(harness.engine.getStatus().linux.portal.state, 'bound'); + harness.portals[0].onEvent({type: 'activated', id: 'voice_push_to_talk'}); + harness.engine.detachClient(1); + await settle(); + assert.equal(harness.portals[0].closed, true); + assert.equal(harness.engine.getStatus().linux.portal.state, 'unknown'); + const invoke = registerLegacy(harness); + assert.equal(await invoke('global-key-hook-start'), false); + assert.equal(harness.portals.length, 1); + harness.attach(2); + await settle(); + assert.equal(harness.portals.length, 2); + assert.equal(harness.engine.getStatus().linux.portal.state, 'bound'); + }); + + test('an old renderer alone on Wayland is told it has access so it never offers a grant', async () => { + const harness = legacyHarness({session: 'wayland', portalConsent: 'granted'}); + const invoke = registerLegacy(harness); + assert.equal(await invoke('global-key-hook-start'), false); + assert.equal((await invoke('linux-evdev-status')).hasAccess, true); + assert.equal(harness.engine.getStatus().linux.portal.state, 'unknown'); + }); + + test('a portal bound for a new renderer keeps an old renderer quiet without routing to it', async () => { + const harness = legacyHarness({session: 'wayland', portalConsent: 'granted'}); + harness.attach(1); + await settle(); + assert.equal(harness.engine.getStatus().linux.portal.state, 'bound'); + const invoke = registerLegacy(harness); + assert.equal(await invoke('global-key-hook-start'), false); + assert.equal((await invoke('linux-evdev-status')).hasAccess, true); + harness.portals[0].onEvent({type: 'activated', id: 'voice_toggle_mute'}); + assert.deepEqual(harness.triggered(), []); + }); + + function unmigratedLegacyHarness(options) { + const harness = createEngine(options); + harness.legacySent = []; + harness.adapter = new legacy.LegacyGlobalKeyHookAdapter(); + harness.engine.addConsumer(harness.adapter); + return harness; + } + + test('a legacy start alone migrates readable keyboards to direct input on Wayland', async () => { + const harness = unmigratedLegacyHarness({session: 'wayland', keyboardReadable: true, portalConsent: 'granted'}); + harness.engine.start(); + await settle(); + const invoke = registerLegacy(harness); + assert.equal(await invoke('global-key-hook-start'), true); + assert.deepEqual(harness.started, ['evdev']); + assert.equal(harness.settings.state.migrated, true); + assert.equal(harness.settings.state.directInputEnabled, true); + assert.equal(harness.engine.getStatus().backend, 'evdev'); + await invoke('global-key-hook-register', {id: 'key:voice_toggle_mute::F9:F9', keyName: 'F9'}); + assert.deepEqual(harness.started, ['evdev']); + assert.equal((await invoke('linux-evdev-status')).hasAccess, true); + }); + + test('legacy calls leave the migration pending while no keyboard is readable', async () => { + const harness = unmigratedLegacyHarness({session: 'wayland', keyboardReadable: false}); + harness.engine.start(); + await settle(); + const invoke = registerLegacy(harness); + assert.equal(await invoke('global-key-hook-start'), false); + await invoke('global-key-hook-register', {id: 'key:voice_toggle_mute::F9:F9', keyName: 'F9'}); + assert.equal(harness.settings.state.migrated, false); + assert.equal(harness.settings.state.directInputEnabled, false); + harness.keyboardReadable = true; + assert.equal(await invoke('global-key-hook-start'), true); + assert.equal(harness.settings.state.migrated, true); + assert.deepEqual(harness.started, ['evdev']); + }); + + test('a migrated user who turned direct input off stays on the portal for legacy starts', async () => { + const harness = unmigratedLegacyHarness({ + session: 'wayland', + keyboardReadable: true, + settings: createSettings({migrated: true}), + }); + const invoke = registerLegacy(harness); + assert.equal(await invoke('global-key-hook-start'), false); + assert.equal(harness.settings.state.directInputEnabled, false); + assert.deepEqual(harness.started, []); + }); + + test('a legacy registration also runs the migration', async () => { + const harness = unmigratedLegacyHarness({session: 'x11', keyboardReadable: true}); + const invoke = registerLegacy(harness); + await invoke('global-key-hook-register', {id: 'mouse:voice_push_to_talk::4', mouseButton: 4}); + assert.equal(harness.settings.state.migrated, true); + assert.equal(harness.settings.state.directInputEnabled, true); + assert.equal(harness.engine.getStatus().backend, 'evdev'); + }); + + test('legacy evdev status never offers a prompt and grant is unsupported', () => { + assert.deepEqual(legacy.buildLegacyLinuxEvdevStatus({platform: 'linux', sandboxed: true, username: 'u'}), { + supported: true, + hasAccess: true, + canPrompt: false, + sandboxed: true, + username: 'u', + totalEventDevices: 0, + readableEventDevices: 0, + inInputGroup: false, + }); + assert.equal( + legacy.buildLegacyLinuxEvdevStatus({platform: 'darwin', sandboxed: false, username: null}).hasAccess, + true, + ); + assert.deepEqual( + {...legacy.LEGACY_LINUX_EVDEV_GRANT_RESULT}, + {success: false, needsRelogin: false, error: 'unsupported'}, + ); + }); +}); diff --git a/fluxer_desktop/src/main/GlobalShortcutsEngine.ts b/fluxer_desktop/src/main/GlobalShortcutsEngine.ts new file mode 100644 index 000000000..f5089244a --- /dev/null +++ b/fluxer_desktop/src/main/GlobalShortcutsEngine.ts @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { + GLOBAL_SHORTCUT_ACTIONS, + GLOBAL_SHORTCUT_DEFAULT_DESCRIPTIONS, + GLOBAL_SHORTCUT_DESCRIPTION_MAX_LENGTH, + type GlobalShortcutAction, + isGlobalShortcutAction, +} from '@electron/common/GlobalShortcutActions'; +import type { + GlobalShortcutCaptureEvent, + GlobalShortcutCombo, + GlobalShortcutEvent, + GlobalShortcutsBackend, + GlobalShortcutsStatus, + LinuxDesktopKind, + LinuxSessionType, +} from '@electron/common/Types'; +import {comboToXdgTrigger} from '@electron/main/GlobalShortcutKeys'; +import { + bindingFromCombo, + GlobalShortcutMatcher, + type HookKeyEvent, + type HookMouseEvent, + type ShortcutBinding, + type ShortcutTransition, +} from '@electron/main/GlobalShortcutMatcher'; +import type {LinuxPortalShortcutsManager, PortalShortcutDefinition} from '@electron/main/LinuxGlobalShortcutsPortal'; + +export type HookBackendKind = 'x11' | 'evdev' | 'windows' | 'macos'; +export type HookStartResult = 'ok' | 'permission' | 'start-failed'; +export type InputHookMode = 'auto' | 'off' | 'evdev' | 'native'; + +export interface NativeKeyIdentity { + keycode: number; + keyName: string; +} + +export type HookEvent = (HookKeyEvent & {native: NativeKeyIdentity}) | (HookMouseEvent & {native: null}); + +export interface HookBackend { + readonly kind: HookBackendKind; + start(onEvent: (event: HookEvent) => void): Promise; + stop(): void; +} + +export interface StoredGlobalShortcutAction { + action: GlobalShortcutAction; + description: string; + preferredTrigger: string | null; +} + +export interface GlobalShortcutsSettingsStore { + getDirectInputEnabled(): boolean; + setDirectInputEnabled(enabled: boolean): void; + isMigrated(): boolean; + setMigrated(): void; + getLastActions(): Array; + setLastActions(actions: Array): void; +} + +export interface GlobalShortcutsLinuxEnvironment { + session: LinuxSessionType; + sandboxed: boolean; + desktop: LinuxDesktopKind; + inputHookMode: InputHookMode; +} + +export interface GlobalShortcutsConsumer { + wantsHooks(): boolean; + handleHookEvent(event: HookEvent, backend: HookBackendKind): void; + releaseHookPresses(): void; +} + +export interface GlobalShortcutsTimer { + cancel(): void; +} + +export interface InputDeviceWatch { + close(): void; +} + +export interface GlobalShortcutsEngineDeps { + platform: 'linux' | 'windows' | 'macos'; + linux: GlobalShortcutsLinuxEnvironment | null; + settings: GlobalShortcutsSettingsStore; + portal: LinuxPortalShortcutsManager | null; + createHookBackend: (kind: HookBackendKind) => HookBackend; + isDirectInputAvailable: () => boolean; + watchInputDevices: (onChange: () => void) => InputDeviceWatch | null; + schedule: (callback: () => void, delayMs: number) => GlobalShortcutsTimer; + log: (message: string, details?: Record) => void; +} + +export interface SanitizedSyncBinding { + sourceId: string; + action: string; + combo: GlobalShortcutCombo; +} + +export interface SanitizedSyncAction { + action: GlobalShortcutAction; + description: string; + preferredCombo: GlobalShortcutCombo | null; +} + +export interface SanitizedSyncPayload { + bindings: Array; + actions: Array; +} + +const XDG_TRIGGER_PATTERN = /^(?:(?:CTRL|ALT|SHIFT|LOGO)\+)*[A-Za-z0-9_]+$/; +const MAX_SYNC_BINDINGS = 256; +const MAX_SOURCE_ID_LENGTH = 200; +const MAX_ACTION_LENGTH = 100; +const MAX_KEY_LENGTH = 64; +const MAX_MOUSE_BUTTON = 31; +const INPUT_DEVICE_REPROBE_DELAY_MS = 500; +export const GLOBAL_SHORTCUT_CAPTURE_TIMEOUT_MS = 60000; + +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +function sanitizeBoundedString(value: unknown, maxLength: number): string | null { + if (typeof value !== 'string') return null; + if (value.length === 0 || value.length > maxLength) return null; + return value; +} + +function sanitizeGlobalShortcutCombo(value: unknown): GlobalShortcutCombo | null { + if (!isRecord(value)) return null; + if (typeof value.key !== 'string' || value.key.length > MAX_KEY_LENGTH) return null; + const combo: GlobalShortcutCombo = { + key: value.key, + ctrl: value.ctrl === true, + alt: value.alt === true, + shift: value.shift === true, + meta: value.meta === true, + }; + if (value.code !== undefined) { + const code = sanitizeBoundedString(value.code, MAX_KEY_LENGTH); + if (code === null) return null; + combo.code = code; + } + if (value.mouseButton !== undefined) { + const button = value.mouseButton; + if (typeof button !== 'number' || !Number.isInteger(button) || button < 0 || button > MAX_MOUSE_BUTTON) { + return null; + } + combo.mouseButton = button; + } + if (value.modifierOnly === true) combo.modifierOnly = true; + if (value.bothSides === true) combo.bothSides = true; + return combo; +} + +export function sanitizeGlobalShortcutsSyncPayload(value: unknown): SanitizedSyncPayload | null { + if (!isRecord(value) || !Array.isArray(value.bindings) || !Array.isArray(value.actions)) return null; + if (value.bindings.length > MAX_SYNC_BINDINGS) return null; + const bindings: Array = []; + for (const entry of value.bindings) { + if (!isRecord(entry)) return null; + const sourceId = sanitizeBoundedString(entry.sourceId, MAX_SOURCE_ID_LENGTH); + const action = sanitizeBoundedString(entry.action, MAX_ACTION_LENGTH); + const combo = sanitizeGlobalShortcutCombo(entry.combo); + if (sourceId === null || action === null || combo === null) return null; + bindings.push({sourceId, action, combo}); + } + const actions: Array = []; + const seenActions = new Set(); + for (const entry of value.actions) { + if (!isRecord(entry) || !isGlobalShortcutAction(entry.action) || seenActions.has(entry.action)) continue; + const description = typeof entry.description === 'string' ? entry.description.trim() : ''; + if (description.length === 0 || description.length > GLOBAL_SHORTCUT_DESCRIPTION_MAX_LENGTH) continue; + const preferredCombo = entry.preferredCombo === null ? null : sanitizeGlobalShortcutCombo(entry.preferredCombo); + seenActions.add(entry.action); + actions.push({action: entry.action, description, preferredCombo}); + } + return {bindings, actions}; +} + +export function storedActionsFromSync(actions: ReadonlyArray): Array { + return actions.map((entry) => ({ + action: entry.action, + description: entry.description, + preferredTrigger: entry.preferredCombo === null ? null : comboToXdgTrigger(entry.preferredCombo), + })); +} + +export function portalDefinitionsFromStored( + stored: ReadonlyArray, +): Array { + const byAction = new Map(stored.map((entry) => [entry.action, entry])); + return GLOBAL_SHORTCUT_ACTIONS.map((action) => { + const entry = byAction.get(action); + const description = entry?.description.trim() || GLOBAL_SHORTCUT_DEFAULT_DESCRIPTIONS[action]; + const preferredTrigger = entry?.preferredTrigger ?? null; + return preferredTrigger !== null && XDG_TRIGGER_PATTERN.test(preferredTrigger) + ? {id: action, description, preferredTrigger} + : {id: action, description}; + }); +} + +function portalSourceId(action: GlobalShortcutAction): string { + return `portal:${action}`; +} + +function isHookBackend(backend: GlobalShortcutsBackend): backend is HookBackendKind { + return backend !== 'portal' && backend !== 'none'; +} + +function captureEventFromHook(event: HookEvent): GlobalShortcutCaptureEvent { + const modifiers = {ctrl: event.ctrlKey, alt: event.altKey, shift: event.shiftKey, meta: event.metaKey}; + if (event.native === null) return {type: event.type, code: null, key: null, button: event.button, ...modifiers}; + return {type: event.type, code: event.code, key: event.key, button: null, ...modifiers}; +} + +interface SyncClient { + send: (channel: string, payload: unknown) => void; + matcher: GlobalShortcutMatcher; + paused: boolean; + portalPressed: Set; +} + +interface CaptureSession { + clientId: number; + captureId: number; + timer: GlobalShortcutsTimer; +} + +export class GlobalShortcutsEngine { + private readonly clients = new Map(); + private readonly consumers = new Set(); + private hook: HookBackend | null = null; + private hookGeneration = 0; + private hookError: 'permission' | 'start-failed' | null = null; + private hookQueue: Promise = Promise.resolve(); + private directInputAvailable = false; + private inputDeviceWatch: InputDeviceWatch | null = null; + private inputDeviceReprobe: GlobalShortcutsTimer | null = null; + private portalRequested = false; + private capture: CaptureSession | null = null; + private captureSeq = 0; + private lastStatusJson: string | null = null; + private disposed = false; + + constructor(private readonly deps: GlobalShortcutsEngineDeps) { + this.directInputAvailable = deps.linux !== null && deps.isDirectInputAvailable(); + } + + start(): void { + this.syncPortalActivity(); + this.syncInputDeviceWatch(); + } + + dispose(): void { + if (this.disposed) return; + this.disposed = true; + this.capture?.timer.cancel(); + this.capture = null; + this.deps.portal?.deactivate(); + this.syncInputDeviceWatch(); + this.stopHook(); + this.clients.clear(); + this.consumers.clear(); + } + + addConsumer(consumer: GlobalShortcutsConsumer): void { + this.consumers.add(consumer); + } + + attachClient(id: number, send: (channel: string, payload: unknown) => void): void { + if (this.clients.has(id)) return; + this.clients.set(id, {send, matcher: new GlobalShortcutMatcher(), paused: false, portalPressed: new Set()}); + this.portalRequested = true; + this.syncPortalActivity(); + } + + hasClient(id: number): boolean { + return this.clients.has(id); + } + + detachClient(id: number): void { + if (!this.clients.delete(id)) return; + if (this.capture?.clientId === id) { + this.capture.timer.cancel(); + this.capture = null; + } + if (this.clients.size === 0) { + this.portalRequested = false; + this.syncPortalActivity(); + } + void this.refreshHooks(); + } + + sync(id: number, payload: SanitizedSyncPayload): void { + const client = this.clients.get(id); + if (!client) return; + const bindings: Array = []; + for (const entry of payload.bindings) { + const binding = bindingFromCombo(entry.sourceId, entry.action, entry.combo); + if (binding !== null) bindings.push(binding); + } + this.emitTransitions(client, client.matcher.setBindings(bindings)); + if (payload.actions.length > 0) this.storeActions(storedActionsFromSync(payload.actions)); + if (bindings.length > 0) this.migrateDirectInput(); + if (!this.directInputAvailable) this.reprobeDirectInput(); + void this.refreshHooks(); + } + + setPaused(id: number, paused: boolean): void { + const client = this.clients.get(id); + if (client) client.paused = paused; + } + + refreshDirectInputAvailability(): void { + if (this.deps.linux === null) return; + this.directInputAvailable = this.deps.isDirectInputAvailable(); + this.syncPortalActivity(); + this.syncInputDeviceWatch(); + void this.refreshHooks(); + this.notifyStatus(); + } + + async setUp(): Promise { + const portal = this.deps.portal; + if (portal === null || !this.portalApplicable()) return; + if (portal.getState() === 'unsupported') { + await portal.recheck(); + return; + } + if (this.getBackendSelection() !== 'portal') return; + await portal.setUp(); + } + + async recheck(): Promise { + const portal = this.deps.portal; + if (portal === null || !this.portalApplicable()) return; + await portal.recheck(); + } + + async startCapture(id: number): Promise { + if (!this.clients.has(id) || !isHookBackend(this.getBackendSelection())) return null; + this.capture?.timer.cancel(); + const captureId = ++this.captureSeq; + const timer = this.deps.schedule(() => this.stopCapture(id, captureId), GLOBAL_SHORTCUT_CAPTURE_TIMEOUT_MS); + const session: CaptureSession = {clientId: id, captureId, timer}; + this.capture = session; + await this.refreshHooks(); + if (this.capture !== session) return null; + if (this.hook !== null) return captureId; + this.stopCapture(id, captureId); + return null; + } + + stopCapture(id: number, captureId: number): void { + if (this.capture?.clientId !== id || this.capture.captureId !== captureId) return; + this.capture.timer.cancel(); + this.capture = null; + void this.refreshHooks(); + } + + async configure(): Promise { + const portal = this.deps.portal; + if (portal === null || this.getBackendSelection() !== 'portal') return; + await portal.configure(); + } + + setDirectInputEnabled(enabled: boolean): void { + if (this.deps.linux === null) return; + this.deps.settings.setDirectInputEnabled(enabled); + this.refreshDirectInputAvailability(); + } + + isDirectInputActive(): boolean { + const linux = this.deps.linux; + if (linux === null || linux.inputHookMode === 'off' || linux.inputHookMode === 'native') return false; + if (!this.directInputAvailable) return false; + return linux.inputHookMode === 'evdev' || this.deps.settings.getDirectInputEnabled(); + } + + hooksActive(): boolean { + return this.hook !== null; + } + + getBackendSelection(): GlobalShortcutsBackend { + const linux = this.deps.linux; + if (linux === null) return this.deps.platform === 'windows' ? 'windows' : 'macos'; + if (linux.inputHookMode === 'off') return 'none'; + if (this.isDirectInputActive()) return 'evdev'; + if (linux.session === 'wayland') { + const portal = this.deps.portal; + if (linux.inputHookMode === 'native' || portal === null) return 'none'; + return portal.getState() === 'unsupported' ? 'none' : 'portal'; + } + return 'x11'; + } + + getStatus(): GlobalShortcutsStatus { + const backend = this.getBackendSelection(); + const linux = this.deps.linux; + const hookBackend = isHookBackend(backend); + return { + backend, + platform: this.deps.platform, + linux: + linux === null + ? null + : { + session: linux.session, + sandbox: linux.sandboxed ? 'flatpak' : 'none', + desktop: linux.desktop, + portal: linux.session === 'wayland' && this.deps.portal !== null ? this.deps.portal.getStatus() : null, + directInput: { + available: this.directInputAvailable, + enabled: this.deps.settings.getDirectInputEnabled(), + locked: linux.inputHookMode !== 'auto', + }, + }, + hooksActive: this.hook !== null, + hookError: this.hookError, + supportsMouseButtons: hookBackend, + supportsModifierOnly: hookBackend, + }; + } + + notifyStatus(): void { + if (this.disposed) return; + const status = this.getStatus(); + const json = JSON.stringify(status); + if (json === this.lastStatusJson) return; + this.lastStatusJson = json; + for (const client of this.clients.values()) client.send('global-shortcuts:status', status); + } + + handlePortalShortcut(action: GlobalShortcutAction, phase: 'press' | 'release'): void { + const sourceId = portalSourceId(action); + const event: GlobalShortcutEvent = {action, sourceId, phase}; + for (const client of this.clients.values()) { + if (phase === 'press') { + if (client.paused) continue; + client.portalPressed.add(sourceId); + } else if (!client.portalPressed.delete(sourceId)) { + continue; + } + client.send('global-shortcut-event', event); + } + } + + releaseAllPressed(): void { + this.releaseHookSources(); + this.deps.portal?.releaseAll(); + } + + refreshHooks(): Promise { + const run = this.hookQueue.then(() => this.reconcileHooks()); + this.hookQueue = run.catch((error: unknown) => { + this.deps.log('Global shortcut hook reconcile failed', { + message: error instanceof Error ? error.message : String(error), + }); + }); + return this.hookQueue; + } + + private desiredHookKind(): HookBackendKind | null { + if (this.disposed) return null; + const backend = this.getBackendSelection(); + return isHookBackend(backend) && this.hooksWanted() ? backend : null; + } + + private hooksWanted(): boolean { + return ( + this.capture !== null || + [...this.clients.values()].some((client) => client.matcher.hasBindings()) || + [...this.consumers].some((consumer) => consumer.wantsHooks()) + ); + } + + private reprobeDirectInput(): void { + const linux = this.deps.linux; + if (linux === null || linux.inputHookMode === 'off' || linux.inputHookMode === 'native') return; + if (linux.inputHookMode !== 'evdev' && !this.deps.settings.getDirectInputEnabled()) return; + const available = this.deps.isDirectInputAvailable(); + if (available === this.directInputAvailable) return; + this.deps.log('Direct input availability changed', {available}); + this.directInputAvailable = available; + this.syncPortalActivity(); + this.syncInputDeviceWatch(); + this.notifyStatus(); + } + + private async reconcileHooks(): Promise { + if (this.hook === null && !this.disposed && this.hooksWanted()) this.reprobeDirectInput(); + const desired = this.desiredHookKind(); + if (this.hook !== null && this.hook.kind !== desired) this.stopHook(); + if (desired === null) { + this.hookError = null; + this.notifyStatus(); + return; + } + if (this.hook !== null) return; + const backend = this.deps.createHookBackend(desired); + const generation = ++this.hookGeneration; + let result: HookStartResult; + try { + result = await backend.start((event) => { + if (generation === this.hookGeneration) this.dispatchHookEvent(event, backend.kind); + }); + } catch (error) { + this.deps.log('Global shortcut hook start threw', { + backend: desired, + message: error instanceof Error ? error.message : String(error), + }); + result = 'start-failed'; + } + if (result !== 'ok' || generation !== this.hookGeneration || this.desiredHookKind() !== desired) { + if (result === 'ok') { + this.stopBackendQuietly(backend); + } else { + this.deps.log('Global shortcut hook did not start', {backend: desired, result}); + } + if (generation === this.hookGeneration) { + this.hookGeneration += 1; + this.releaseHookSources(); + } + this.hookError = result === 'ok' ? null : result; + this.notifyStatus(); + return; + } + this.hook = backend; + this.hookError = null; + this.notifyStatus(); + } + + private stopHook(): void { + const hook = this.hook; + this.hook = null; + this.hookGeneration += 1; + if (hook !== null) this.stopBackendQuietly(hook); + this.releaseHookSources(); + } + + private releaseHookSources(): void { + for (const client of this.clients.values()) this.emitTransitions(client, client.matcher.releaseAll()); + for (const consumer of this.consumers) consumer.releaseHookPresses(); + } + + private stopBackendQuietly(backend: HookBackend): void { + try { + backend.stop(); + } catch (error) { + this.deps.log('Global shortcut hook stop threw', { + backend: backend.kind, + message: error instanceof Error ? error.message : String(error), + }); + } + } + + private dispatchHookEvent(event: HookEvent, backend: HookBackendKind): void { + for (const client of this.clients.values()) { + const transitions = + event.native === null + ? client.matcher.handleMouse(event, !client.paused) + : client.matcher.handleKey(event, !client.paused); + this.emitTransitions(client, transitions); + } + for (const consumer of this.consumers) consumer.handleHookEvent(event, backend); + const capture = this.capture; + if (capture !== null) + this.clients.get(capture.clientId)?.send('global-shortcuts:capture', captureEventFromHook(event)); + } + + private emitTransitions(client: SyncClient, transitions: ReadonlyArray): void { + for (const transition of transitions) { + const event: GlobalShortcutEvent = { + action: transition.action, + sourceId: transition.sourceId, + phase: transition.phase, + }; + client.send('global-shortcut-event', event); + } + } + + private storeActions(actions: Array): void { + const previous = this.deps.settings.getLastActions(); + if (JSON.stringify(previous) === JSON.stringify(actions)) return; + this.deps.settings.setLastActions(actions); + } + + migrateLegacyDirectInput(): void { + if (this.deps.linux === null || this.deps.settings.isMigrated() || !this.deps.isDirectInputAvailable()) return; + this.migrateDirectInput(); + } + + private migrateDirectInput(): void { + if (this.deps.linux === null || this.deps.settings.isMigrated()) return; + this.directInputAvailable = this.deps.isDirectInputAvailable(); + if (this.directInputAvailable) { + this.deps.log('Enabling direct input access for an existing global shortcut setup'); + this.deps.settings.setDirectInputEnabled(true); + } + this.deps.settings.setMigrated(); + this.syncPortalActivity(); + this.syncInputDeviceWatch(); + void this.refreshHooks(); + this.notifyStatus(); + } + + private wantsInputDeviceWatch(): boolean { + const linux = this.deps.linux; + if (linux === null || this.disposed || this.directInputAvailable) return false; + if (linux.inputHookMode === 'off' || linux.inputHookMode === 'native') return false; + return linux.inputHookMode === 'evdev' || this.deps.settings.getDirectInputEnabled(); + } + + private syncInputDeviceWatch(): void { + if (!this.wantsInputDeviceWatch()) { + this.inputDeviceReprobe?.cancel(); + this.inputDeviceReprobe = null; + this.inputDeviceWatch?.close(); + this.inputDeviceWatch = null; + return; + } + if (this.inputDeviceWatch !== null) return; + this.inputDeviceWatch = this.deps.watchInputDevices(() => this.scheduleInputDeviceReprobe()); + } + + private scheduleInputDeviceReprobe(): void { + if (this.inputDeviceWatch === null) return; + this.inputDeviceReprobe?.cancel(); + this.inputDeviceReprobe = this.deps.schedule(() => { + this.inputDeviceReprobe = null; + this.refreshDirectInputAvailability(); + }, INPUT_DEVICE_REPROBE_DELAY_MS); + } + + private portalApplicable(): boolean { + const linux = this.deps.linux; + if (linux === null || this.deps.portal === null || this.disposed) return false; + return ( + linux.session === 'wayland' && + linux.inputHookMode !== 'off' && + linux.inputHookMode !== 'native' && + !this.isDirectInputActive() + ); + } + + syncPortalActivity(): void { + const portal = this.deps.portal; + if (portal === null) return; + if (this.portalRequested && this.portalApplicable()) { + void portal.activate(); + void portal.probe(); + } else { + portal.deactivate(); + } + } +} diff --git a/fluxer_desktop/src/main/GlobalShortcutsIpc.ts b/fluxer_desktop/src/main/GlobalShortcutsIpc.ts new file mode 100644 index 000000000..f1bfede61 --- /dev/null +++ b/fluxer_desktop/src/main/GlobalShortcutsIpc.ts @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {watch} from 'node:fs'; +import {userInfo} from 'node:os'; +import {getGlobalShortcutsSettings, setGlobalShortcutsSettings} from '@electron/common/DesktopConfig'; +import {LINUX_DESKTOP_ENTRY_ID, LINUX_PORTAL_SESSION_TOKEN} from '@electron/common/DesktopIdentity'; +import {createChildLogger} from '@electron/common/Logger'; +import type {GlobalShortcutsStatus} from '@electron/common/Types'; +import { + GlobalShortcutsEngine, + type GlobalShortcutsEngineDeps, + type GlobalShortcutsLinuxEnvironment, + type GlobalShortcutsSettingsStore, + type InputDeviceWatch, + portalDefinitionsFromStored, + sanitizeGlobalShortcutsSyncPayload, +} from '@electron/main/GlobalShortcutsEngine'; +import { + createLinuxSessionStateMonitor, + createNativeGlobalShortcutsPortal, + createNativeHookBackend, + hasMacInputMonitoringAccess, + isLinuxKeyboardReadable, +} from '@electron/main/GlobalShortcutsNative'; +import {getLinuxInputHookMode, getLinuxPortalsMode} from '@electron/main/LaunchOptions'; +import {LegacyGlobalKeyHookAdapter, registerLegacyGlobalKeyHookHandlers} from '@electron/main/LegacyGlobalKeyHookIpc'; +import {LinuxPortalShortcutsManager} from '@electron/main/LinuxGlobalShortcutsPortal'; +import {getFlatpakAppId, isFlatpakRuntime} from '@electron/main/LinuxSandbox'; +import {getLinuxDesktop, getLinuxSessionType, isKdePlasma5Session} from '@electron/main/LinuxSession'; +import {requirePrivilegedRendererDocumentSender} from '@electron/main/PrivilegedRendererDocuments'; +import {getMainWindow} from '@electron/main/Window'; +import {app, ipcMain, powerMonitor} from 'electron'; + +const logger = createChildLogger('GlobalShortcuts'); + +let engine: GlobalShortcutsEngine | null = null; +let legacyAdapter: LegacyGlobalKeyHookAdapter | null = null; +const senderWatchers = new Map void>>(); +let detachPowerListeners: (() => void) | null = null; + +function log(message: string, details?: Record): void { + logger.info(message, details ?? {}); +} + +function getShortcutsPlatform(): GlobalShortcutsEngineDeps['platform'] { + if (process.platform === 'darwin') return 'macos'; + if (process.platform === 'win32') return 'windows'; + return 'linux'; +} + +function watchSender(sender: Electron.WebContents, onGone: () => void): void { + const senderId = sender.id; + const existing = senderWatchers.get(senderId); + if (existing) { + existing.add(onGone); + return; + } + const callbacks = new Set([onGone]); + senderWatchers.set(senderId, callbacks); + const runCallbacks = (): void => { + const pending = [...callbacks]; + callbacks.clear(); + for (const callback of pending) callback(); + }; + sender.on('did-navigate', runCallbacks); + sender.on('render-process-gone', runCallbacks); + sender.once('destroyed', () => { + runCallbacks(); + senderWatchers.delete(senderId); + }); +} + +function createSettingsStore(): GlobalShortcutsSettingsStore { + return { + getDirectInputEnabled: () => getGlobalShortcutsSettings().directInputEnabled, + setDirectInputEnabled: (enabled) => { + setGlobalShortcutsSettings({directInputEnabled: enabled}); + }, + isMigrated: () => getGlobalShortcutsSettings().migrated, + setMigrated: () => { + setGlobalShortcutsSettings({migrated: true}); + }, + getLastActions: () => getGlobalShortcutsSettings().lastActions, + setLastActions: (actions) => { + setGlobalShortcutsSettings({lastActions: actions}); + }, + }; +} + +function getPortalParentWindow(linux: GlobalShortcutsLinuxEnvironment): string { + const window = getMainWindow(); + if (window === null || window.isDestroyed()) return ''; + if (linux.sandboxed && linux.session === 'wayland') return ''; + const x11Window = app.commandLine.getSwitchValue('ozone-platform') === 'x11' || linux.session !== 'wayland'; + if (!x11Window) return ''; + const handle = window.getNativeWindowHandle(); + if (handle.length === 8) return `x11:${handle.readBigUInt64LE(0).toString(16)}`; + if (handle.length === 4) return `x11:${handle.readUInt32LE(0).toString(16)}`; + return ''; +} + +function schedule(callback: () => void, delayMs: number): {cancel(): void} { + const timer = setTimeout(callback, delayMs); + timer.unref?.(); + return {cancel: () => clearTimeout(timer)}; +} + +function watchLinuxInputDevices(onChange: () => void): InputDeviceWatch | null { + try { + const watcher = watch('/dev/input', {persistent: false}, () => onChange()); + watcher.on('error', (error) => { + log('Input device watch failed', {message: error.message}); + watcher.close(); + }); + return {close: () => watcher.close()}; + } catch (error) { + log('Input device watch unavailable', {message: error instanceof Error ? error.message : String(error)}); + return null; + } +} + +function createPortalManager( + linux: GlobalShortcutsLinuxEnvironment, + getEngine: () => GlobalShortcutsEngine | null, +): LinuxPortalShortcutsManager | null { + if (linux.session !== 'wayland' || getLinuxPortalsMode() === 'off') return null; + const hostPortalAppId = linux.sandboxed ? null : LINUX_DESKTOP_ENTRY_ID; + return new LinuxPortalShortcutsManager({ + createPortal: (onEvent) => + createNativeGlobalShortcutsPortal(onEvent, { + portalAppId: hostPortalAppId, + sandboxed: linux.sandboxed, + sessionToken: LINUX_PORTAL_SESSION_TOKEN, + desktop: linux.desktop, + }), + desktop: linux.desktop, + plasma5: isKdePlasma5Session(process.env), + portalAppId: linux.sandboxed ? getFlatpakAppId() : LINUX_DESKTOP_ENTRY_ID, + getConsent: () => getGlobalShortcutsSettings().portalConsent, + setConsent: (consent) => { + setGlobalShortcutsSettings({portalConsent: consent}); + }, + getDefinitions: () => portalDefinitionsFromStored(getGlobalShortcutsSettings().lastActions), + getParentWindow: () => getPortalParentWindow(linux), + onShortcut: (action, phase) => getEngine()?.handlePortalShortcut(action, phase), + onStatusChanged: () => getEngine()?.notifyStatus(), + schedule, + now: () => Date.now(), + log, + }); +} + +function attachPowerListeners(target: GlobalShortcutsEngine): () => void { + const releaseAll = (): void => { + target.releaseAllPressed(); + }; + powerMonitor.on('suspend', releaseAll); + powerMonitor.on('lock-screen', releaseAll); + const sessionMonitor = createLinuxSessionStateMonitor((event) => { + if (event.type === 'screen-locked') releaseAll(); + }); + return () => { + powerMonitor.off('suspend', releaseAll); + powerMonitor.off('lock-screen', releaseAll); + sessionMonitor?.close(); + }; +} + +function logDesktopIdMismatch(linux: GlobalShortcutsLinuxEnvironment): void { + if (linux.sandboxed) return; + const expected = `${LINUX_DESKTOP_ENTRY_ID}.desktop`; + const actual = process.env.CHROME_DESKTOP ?? null; + if (actual !== expected) { + logger.warn('CHROME_DESKTOP does not match the portal app id', {expected, actual}); + } +} + +function readUsername(): string | null { + try { + return userInfo().username; + } catch { + return null; + } +} + +function requireEngine(): GlobalShortcutsEngine { + if (engine === null) throw new Error('Global shortcuts are not initialised'); + return engine; +} + +function ensureClient(event: Electron.IpcMainInvokeEvent, channel: string): number { + requirePrivilegedRendererDocumentSender(event, channel); + const current = requireEngine(); + const sender = event.sender; + const senderId = sender.id; + if (!current.hasClient(senderId)) { + current.attachClient(senderId, (eventChannel, payload) => { + if (!sender.isDestroyed()) sender.send(eventChannel, payload); + }); + watchSender(sender, () => engine?.detachClient(senderId)); + } + return senderId; +} + +function registerGlobalShortcutsHandlers(): void { + ipcMain.handle('global-shortcuts:sync', (event, payload: unknown): void => { + const senderId = ensureClient(event, 'global-shortcuts:sync'); + const sanitized = sanitizeGlobalShortcutsSyncPayload(payload); + if (sanitized === null) throw new Error('Invalid global shortcuts sync payload'); + requireEngine().sync(senderId, sanitized); + }); + ipcMain.handle('global-shortcuts:set-paused', (event, paused: unknown): void => { + const senderId = ensureClient(event, 'global-shortcuts:set-paused'); + if (typeof paused !== 'boolean') throw new Error('Invalid paused flag'); + requireEngine().setPaused(senderId, paused); + }); + ipcMain.handle('global-shortcuts:get-status', (event): GlobalShortcutsStatus => { + ensureClient(event, 'global-shortcuts:get-status'); + return requireEngine().getStatus(); + }); + ipcMain.handle('global-shortcuts:set-up', async (event): Promise => { + ensureClient(event, 'global-shortcuts:set-up'); + await requireEngine().setUp(); + return requireEngine().getStatus(); + }); + ipcMain.handle('global-shortcuts:recheck', async (event): Promise => { + ensureClient(event, 'global-shortcuts:recheck'); + await requireEngine().recheck(); + return requireEngine().getStatus(); + }); + ipcMain.handle('global-shortcuts:start-capture', (event): Promise => { + const senderId = ensureClient(event, 'global-shortcuts:start-capture'); + return requireEngine().startCapture(senderId); + }); + ipcMain.handle('global-shortcuts:stop-capture', (event, captureId: unknown): void => { + const senderId = ensureClient(event, 'global-shortcuts:stop-capture'); + if (typeof captureId !== 'number' || !Number.isSafeInteger(captureId)) throw new Error('Invalid capture id'); + requireEngine().stopCapture(senderId, captureId); + }); + ipcMain.handle('global-shortcuts:configure', async (event): Promise => { + ensureClient(event, 'global-shortcuts:configure'); + await requireEngine().configure(); + }); + ipcMain.handle( + 'global-shortcuts:set-direct-input-enabled', + async (event, enabled: unknown): Promise => { + ensureClient(event, 'global-shortcuts:set-direct-input-enabled'); + if (typeof enabled !== 'boolean') throw new Error('Invalid direct input flag'); + const current = requireEngine(); + current.setDirectInputEnabled(enabled); + await current.refreshHooks(); + return current.getStatus(); + }, + ); +} + +export function initializeGlobalShortcuts(): void { + if (engine !== null) return; + const platform = getShortcutsPlatform(); + const linux: GlobalShortcutsLinuxEnvironment | null = + platform === 'linux' + ? { + session: getLinuxSessionType(), + sandboxed: isFlatpakRuntime(), + desktop: getLinuxDesktop(), + inputHookMode: getLinuxInputHookMode(), + } + : null; + if (linux !== null) logDesktopIdMismatch(linux); + const portal = linux === null ? null : createPortalManager(linux, () => engine); + const created = new GlobalShortcutsEngine({ + platform, + linux, + settings: createSettingsStore(), + portal, + createHookBackend: createNativeHookBackend, + isDirectInputAvailable: isLinuxKeyboardReadable, + watchInputDevices: watchLinuxInputDevices, + schedule, + log, + }); + engine = created; + const adapter = new LegacyGlobalKeyHookAdapter(); + legacyAdapter = adapter; + created.addConsumer(adapter); + registerGlobalShortcutsHandlers(); + registerLegacyGlobalKeyHookHandlers(created, adapter, { + platform: process.platform, + sandboxed: linux?.sandboxed === true, + username: readUsername(), + hasInputMonitoringAccess: hasMacInputMonitoringAccess, + watchSender, + }); + detachPowerListeners = attachPowerListeners(created); + log('Global shortcuts initialised', {platform, linux, backend: created.getBackendSelection()}); + created.start(); +} + +export function cleanupGlobalShortcuts(): void { + detachPowerListeners?.(); + detachPowerListeners = null; + legacyAdapter?.clear(); + legacyAdapter = null; + engine?.dispose(); + engine = null; +} diff --git a/fluxer_desktop/src/main/GlobalShortcutsNative.ts b/fluxer_desktop/src/main/GlobalShortcutsNative.ts new file mode 100644 index 000000000..ab968af02 --- /dev/null +++ b/fluxer_desktop/src/main/GlobalShortcutsNative.ts @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {createRequire} from 'node:module'; +import {createChildLogger} from '@electron/common/Logger'; +import { + evdevKeyToDomCode, + macosKeycodeToDomCode, + modifierKindForCode, + windowsKeyToDomCode, + windowsScanCodeKey, + x11KeycodeToDomCode, +} from '@electron/main/GlobalShortcutKeys'; +import type {HookBackend, HookBackendKind, HookEvent, HookStartResult} from '@electron/main/GlobalShortcutsEngine'; +import {getLinuxPortalsMode} from '@electron/main/LaunchOptions'; +import type {PortalClient, PortalClientOptions, PortalEvent} from '@electron/main/LinuxGlobalShortcutsPortal'; +import {getTccStatus} from '@electron/main/MacTcc'; + +const logger = createChildLogger('GlobalShortcutsNative'); +const requireModule = createRequire(import.meta.url); + +const WINDOWS_VK_RAW_PREFIX = 0x10000; +const WINDOWS_PACKET_RAW_PREFIX = 0x20000; +const WINDOWS_VK_PACKET = 0xe7; +const WINDOWS_PACKET_SPACE = 0x20; +const UNMAPPED_KEY_NAME = /^Key\d+$/; +const X11_UNFLAGGED_MODIFIER_CODES: ReadonlyArray = ['AltRight']; + +interface NativeModifierFlags { + ctrlKey: boolean; + altKey: boolean; + shiftKey: boolean; + metaKey: boolean; +} + +interface NativeKeyEvent extends NativeModifierFlags { + type: 'keydown' | 'keyup'; + keycode: number; + keyName: string; + x11Keycode?: number; + scanCode?: number; + extended?: boolean; +} + +interface NativeMouseEvent extends NativeModifierFlags { + type: 'mousedown' | 'mouseup'; + button: number; +} + +type NativeHookEvent = NativeKeyEvent | NativeMouseEvent; + +interface NativeInputHookInstance { + start(): boolean | undefined | Promise; + stop(): void; +} + +interface NativeInputHookModule { + InputHook: (new (callback: (event: NativeHookEvent) => void) => NativeInputHookInstance) | null; + hasAccessibilityPermission?: () => boolean; + loadError: Error | null; +} + +interface NativeEvdevModule { + EvdevHook: (new (callback: (event: NativeHookEvent) => void) => NativeInputHookInstance) | null; + isKeyboardReadable: (() => boolean) | null; + loadError: Error | null; +} + +interface NativeGlobalShortcutsPortalCtor { + new (onEvent: (event: PortalEvent) => void, options: PortalClientOptions): PortalClient; +} + +export type SessionStateEvent = {type: 'screen-locked'} | {type: 'screen-unlocked'}; + +export interface SessionStateMonitorHandle { + close(): void; +} + +interface NativeSessionStateMonitorCtor { + new (onEvent: (event: SessionStateEvent) => void): SessionStateMonitorHandle; +} + +interface NativePortalsModule { + GlobalShortcutsPortal: NativeGlobalShortcutsPortalCtor | null; + SessionStateMonitor?: NativeSessionStateMonitorCtor | null; + loadError: Error | null; +} + +const NATIVE_HOOK_MODULES: Readonly, string>> = { + x11: '@fluxer/linux-input-hook', + windows: '@fluxer/windows-input-hook', + macos: '@fluxer/macos-input-hook', +}; + +const moduleCache = new Map(); + +function loadModule(name: string): T { + const cached = moduleCache.get(name); + if (cached !== undefined) return cached as T; + let loaded: T; + try { + loaded = requireModule(name) as T; + } catch (error) { + throw new Error( + `${name} failed to load; this is a packaging bug, not a runtime fallback case. ` + + `Original error: ${error instanceof Error ? error.message : String(error)}`, + ); + } + moduleCache.set(name, loaded); + return loaded; +} + +function keyCodeIdentity( + kind: HookBackendKind, + event: NativeKeyEvent, +): {code: string | null; key: string | null; raw: number} { + switch (kind) { + case 'x11': { + const x11Keycode = event.x11Keycode ?? 0; + return {code: x11KeycodeToDomCode(x11Keycode), key: event.keyName || null, raw: x11Keycode}; + } + case 'evdev': + return {code: evdevKeyToDomCode(event.keycode), key: null, raw: event.keycode}; + case 'windows': { + const scanCode = event.scanCode ?? 0; + const extended = event.extended === true; + if (event.keycode === WINDOWS_VK_PACKET) { + return { + code: scanCode === WINDOWS_PACKET_SPACE ? 'Space' : null, + key: windowsPacketKey(scanCode), + raw: WINDOWS_PACKET_RAW_PREFIX | scanCode, + }; + } + return { + code: windowsKeyToDomCode(scanCode, extended, event.keycode), + key: event.keyName && !UNMAPPED_KEY_NAME.test(event.keyName) ? event.keyName : null, + raw: scanCode === 0 ? WINDOWS_VK_RAW_PREFIX | event.keycode : windowsScanCodeKey(scanCode, extended), + }; + } + case 'macos': + return {code: macosKeycodeToDomCode(event.keycode), key: null, raw: event.keycode}; + } +} + +function windowsPacketKey(character: number): string | null { + const upper = String.fromCharCode(character).toUpperCase(); + return /^[A-Z0-9]$/.test(upper) ? upper : null; +} + +function isNativeKeyEvent(event: NativeHookEvent): event is NativeKeyEvent { + return event.type === 'keydown' || event.type === 'keyup'; +} + +function isNativeMouseEvent(event: NativeHookEvent): event is NativeMouseEvent { + return event.type === 'mousedown' || event.type === 'mouseup'; +} + +export function translateNativeHookEvent(kind: HookBackendKind, event: NativeHookEvent): HookEvent | null { + const unflaggedModifierCodes = kind === 'x11' ? X11_UNFLAGGED_MODIFIER_CODES : undefined; + if (isNativeMouseEvent(event)) { + return { + type: event.type, + button: event.button, + ctrlKey: event.ctrlKey, + altKey: event.altKey, + shiftKey: event.shiftKey, + metaKey: event.metaKey, + unflaggedModifierCodes, + native: null, + }; + } + if (!isNativeKeyEvent(event)) return null; + const identity = keyCodeIdentity(kind, event); + if (kind === 'evdev' && identity.code === null) return null; + const own = kind === 'windows' && event.type === 'keydown' ? modifierKindForCode(identity.code) : null; + return { + type: event.type, + code: identity.code, + key: identity.key, + rawKeycode: identity.raw, + ctrlKey: event.ctrlKey || own === 'ctrl', + altKey: event.altKey || own === 'alt', + shiftKey: event.shiftKey || own === 'shift', + metaKey: event.metaKey || own === 'meta', + unflaggedModifierCodes, + native: {keycode: event.keycode, keyName: event.keyName}, + }; +} + +export function hasMacInputMonitoringAccess(): boolean { + if (process.platform !== 'darwin') return true; + try { + const module = loadModule(NATIVE_HOOK_MODULES.macos); + if (module.hasAccessibilityPermission) return module.hasAccessibilityPermission(); + } catch (error) { + logger.warn('Input Monitoring preflight failed', {error}); + return false; + } + try { + return getTccStatus('input-monitoring') === 'granted'; + } catch (error) { + logger.warn('Input Monitoring status could not be read', {error}); + return false; + } +} + +class NativeHookBackend implements HookBackend { + private instance: NativeInputHookInstance | null = null; + + constructor(public readonly kind: HookBackendKind) {} + + async start(onEvent: (event: HookEvent) => void): Promise { + if (this.kind === 'macos' && !hasMacInputMonitoringAccess()) return 'permission'; + const Ctor = + this.kind === 'evdev' + ? loadModule('@fluxer/linux-evdev').EvdevHook + : loadModule(NATIVE_HOOK_MODULES[this.kind]).InputHook; + if (Ctor === null) return 'start-failed'; + const kind = this.kind; + const instance = new Ctor((event) => { + const translated = translateNativeHookEvent(kind, event); + if (translated !== null) onEvent(translated); + }); + let started: boolean | undefined; + try { + started = (await instance.start()) ?? undefined; + } catch (error) { + instance.stop(); + throw error; + } + if (started === false) { + instance.stop(); + return 'start-failed'; + } + this.instance = instance; + logger.info('Global shortcut hook running', {backend: kind}); + return 'ok'; + } + + stop(): void { + const instance = this.instance; + this.instance = null; + instance?.stop(); + } +} + +export function createNativeHookBackend(kind: HookBackendKind): HookBackend { + return new NativeHookBackend(kind); +} + +export function isLinuxKeyboardReadable(): boolean { + if (process.platform !== 'linux') return false; + try { + const module = loadModule('@fluxer/linux-evdev'); + return module.isKeyboardReadable?.() === true; + } catch (error) { + logger.warn('Keyboard readability probe failed', {error}); + return false; + } +} + +export function createNativeGlobalShortcutsPortal( + onEvent: (event: PortalEvent) => void, + options: PortalClientOptions, +): PortalClient { + const module = loadModule('@fluxer/linux-portals'); + if (module.GlobalShortcutsPortal === null) { + throw new Error( + `@fluxer/linux-portals exports no GlobalShortcutsPortal: ${module.loadError?.message ?? 'unknown'}`, + ); + } + return new module.GlobalShortcutsPortal(onEvent, options); +} + +export function createLinuxSessionStateMonitor( + onEvent: (event: SessionStateEvent) => void, +): SessionStateMonitorHandle | null { + if (process.platform !== 'linux' || getLinuxPortalsMode(process.argv) === 'off') return null; + try { + const Monitor = loadModule('@fluxer/linux-portals').SessionStateMonitor; + return Monitor ? new Monitor(onEvent) : null; + } catch (error) { + logger.info('Session state monitor unavailable', {error}); + return null; + } +} diff --git a/fluxer_desktop/src/main/LegacyGlobalKeyHookIpc.ts b/fluxer_desktop/src/main/LegacyGlobalKeyHookIpc.ts new file mode 100644 index 000000000..6fbf69dba --- /dev/null +++ b/fluxer_desktop/src/main/LegacyGlobalKeyHookIpc.ts @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import type {GlobalKeyHookRegisterOptions} from '@electron/common/Types'; +import {hookKeyNameToDomCode} from '@electron/main/GlobalShortcutKeys'; +import {GlobalShortcutMatcher, type ShortcutBinding} from '@electron/main/GlobalShortcutMatcher'; +import type { + GlobalShortcutsConsumer, + GlobalShortcutsEngine, + HookBackendKind, + HookEvent, +} from '@electron/main/GlobalShortcutsEngine'; +import {requirePrivilegedRendererDocumentSender} from '@electron/main/PrivilegedRendererDocuments'; +import {ipcMain} from 'electron'; + +const MAX_LEGACY_ID_LENGTH = 512; +const MAX_LEGACY_KEY_NAME_LENGTH = 64; +const MAX_LEGACY_MOUSE_BUTTON = 31; + +export interface LegacyLinuxEvdevStatus { + supported: boolean; + hasAccess: boolean; + canPrompt: boolean; + sandboxed: boolean; + username: string | null; + totalEventDevices: number; + readableEventDevices: number; + inInputGroup: boolean; +} + +export interface LegacyLinuxEvdevGrantResult { + success: boolean; + needsRelogin: boolean; + error?: string; +} + +export interface LegacyHostEnvironment { + platform: NodeJS.Platform; + sandboxed: boolean; + username: string | null; + hasInputMonitoringAccess: () => boolean; + watchSender: (sender: Electron.WebContents, onGone: () => void) => void; +} + +function readLegacyName(value: unknown): string | null { + if (typeof value !== 'string' || value.length === 0 || value.length > MAX_LEGACY_KEY_NAME_LENGTH) return null; + return value; +} + +export function legacyRegistrationToBinding(options: unknown): ShortcutBinding | null { + if (options === null || typeof options !== 'object') return null; + const record = options as Record; + const id = record.id; + if (typeof id !== 'string' || id.length === 0 || id.length > MAX_LEGACY_ID_LENGTH) return null; + const modifiers = { + ctrl: record.ctrl === true, + alt: record.alt === true, + shift: record.shift === true, + meta: record.meta === true, + }; + const mouseButton = record.mouseButton; + if (typeof mouseButton === 'number' && Number.isInteger(mouseButton)) { + if (mouseButton < 0 || mouseButton > MAX_LEGACY_MOUSE_BUTTON) return null; + return {sourceId: id, action: id, trigger: {kind: 'mouse', button: mouseButton}, modifiers}; + } + const keyName = readLegacyName(record.keyName); + const physicalKeyName = readLegacyName(record.physicalKeyName); + const physicalSource = physicalKeyName ?? keyName; + const code = physicalSource === null ? null : hookKeyNameToDomCode(physicalSource); + if (keyName === null && code === null) return null; + return {sourceId: id, action: id, trigger: {kind: 'key', layoutKey: keyName, code}, modifiers}; +} + +interface LegacyClient { + send: (channel: string, payload: unknown) => void; + starts: number; + matcher: GlobalShortcutMatcher; + registrations: Map; +} + +export class LegacyGlobalKeyHookAdapter implements GlobalShortcutsConsumer { + private readonly clients = new Map(); + + attach(id: number, send: (channel: string, payload: unknown) => void): boolean { + if (this.clients.has(id)) return false; + this.clients.set(id, { + send, + starts: 0, + matcher: new GlobalShortcutMatcher(), + registrations: new Map(), + }); + return true; + } + + detach(id: number): void { + this.clients.delete(id); + } + + clear(): void { + this.clients.clear(); + } + + acquire(id: number): void { + const client = this.clients.get(id); + if (client) client.starts += 1; + } + + release(id: number): void { + const client = this.clients.get(id); + if (client && client.starts > 0) client.starts -= 1; + } + + register(id: number, binding: ShortcutBinding): void { + const client = this.clients.get(id); + if (!client) return; + client.registrations.set(binding.sourceId, binding); + this.applyRegistrations(client); + } + + unregister(id: number, registrationId: string): void { + const client = this.clients.get(id); + if (!client?.registrations.delete(registrationId)) return; + this.applyRegistrations(client); + } + + unregisterAll(id: number): void { + const client = this.clients.get(id); + if (!client) return; + client.registrations.clear(); + this.applyRegistrations(client); + } + + wantsHooks(): boolean { + for (const client of this.clients.values()) { + if (client.starts > 0) return true; + } + return false; + } + + handleHookEvent(event: HookEvent, backend: HookBackendKind): void { + for (const client of this.clients.values()) { + const transitions = + event.native === null ? client.matcher.handleMouse(event, true) : client.matcher.handleKey(event, true); + for (const transition of transitions) { + client.send('global-keybind-triggered', { + id: transition.sourceId, + type: transition.phase === 'press' ? 'keydown' : 'keyup', + }); + } + if (client.starts === 0) continue; + const modifiers = { + ctrlKey: event.ctrlKey, + altKey: event.altKey, + shiftKey: event.shiftKey, + metaKey: event.metaKey, + }; + if (event.native === null) { + client.send('global-mouse-event', {type: event.type, button: event.button, ...modifiers}); + } else { + client.send('global-key-event', { + type: event.type, + keycode: event.native.keycode, + keyName: event.native.keyName, + ...modifiers, + backend: backend === 'evdev' ? 'evdev' : 'native', + }); + } + } + } + + releaseHookPresses(): void { + for (const client of this.clients.values()) { + for (const transition of client.matcher.releaseAll()) { + client.send('global-keybind-triggered', {id: transition.sourceId, type: 'keyup'}); + } + } + } + + private applyRegistrations(client: LegacyClient): void { + for (const transition of client.matcher.setBindings([...client.registrations.values()])) { + client.send('global-keybind-triggered', {id: transition.sourceId, type: 'keyup'}); + } + } +} + +export function buildLegacyLinuxEvdevStatus( + host: Pick, +): LegacyLinuxEvdevStatus { + const linux = host.platform === 'linux'; + return { + supported: linux, + hasAccess: true, + canPrompt: false, + sandboxed: linux && host.sandboxed, + username: host.username, + totalEventDevices: 0, + readableEventDevices: 0, + inInputGroup: false, + }; +} + +export const LEGACY_LINUX_EVDEV_GRANT_RESULT: LegacyLinuxEvdevGrantResult = Object.freeze({ + success: false, + needsRelogin: false, + error: 'unsupported', +}); + +export function registerLegacyGlobalKeyHookHandlers( + engine: GlobalShortcutsEngine, + adapter: LegacyGlobalKeyHookAdapter, + host: LegacyHostEnvironment, +): void { + const ensureClient = (sender: Electron.WebContents): void => { + const senderId = sender.id; + const send = (channel: string, payload: unknown): void => { + if (!sender.isDestroyed()) sender.send(channel, payload); + }; + if (!adapter.attach(senderId, send)) return; + host.watchSender(sender, () => { + adapter.detach(senderId); + void engine.refreshHooks(); + }); + }; + ipcMain.handle('global-key-hook-start', async (event): Promise => { + requirePrivilegedRendererDocumentSender(event, 'global-key-hook-start'); + if (host.platform === 'darwin' && !host.hasInputMonitoringAccess()) return false; + ensureClient(event.sender); + adapter.acquire(event.sender.id); + engine.migrateLegacyDirectInput(); + await engine.refreshHooks(); + if (engine.hooksActive()) return true; + adapter.release(event.sender.id); + await engine.refreshHooks(); + return false; + }); + ipcMain.handle('global-key-hook-stop', async (event): Promise => { + requirePrivilegedRendererDocumentSender(event, 'global-key-hook-stop'); + adapter.release(event.sender.id); + await engine.refreshHooks(); + }); + ipcMain.handle('global-key-hook-is-running', (event): boolean => { + requirePrivilegedRendererDocumentSender(event, 'global-key-hook-is-running'); + return engine.hooksActive(); + }); + ipcMain.handle('check-input-monitoring-access', (event): boolean => { + requirePrivilegedRendererDocumentSender(event, 'check-input-monitoring-access'); + return host.hasInputMonitoringAccess(); + }); + ipcMain.handle('global-key-hook-register', (event, options: GlobalKeyHookRegisterOptions): void => { + requirePrivilegedRendererDocumentSender(event, 'global-key-hook-register'); + const binding = legacyRegistrationToBinding(options); + if (binding === null) return; + ensureClient(event.sender); + adapter.register(event.sender.id, binding); + engine.migrateLegacyDirectInput(); + }); + ipcMain.handle('global-key-hook-unregister', (event, id: unknown): void => { + requirePrivilegedRendererDocumentSender(event, 'global-key-hook-unregister'); + if (typeof id !== 'string') return; + adapter.unregister(event.sender.id, id); + }); + ipcMain.handle('global-key-hook-unregister-all', (event): void => { + requirePrivilegedRendererDocumentSender(event, 'global-key-hook-unregister-all'); + adapter.unregisterAll(event.sender.id); + }); + ipcMain.handle('linux-evdev-status', (event): LegacyLinuxEvdevStatus => { + requirePrivilegedRendererDocumentSender(event, 'linux-evdev-status'); + return buildLegacyLinuxEvdevStatus(host); + }); + ipcMain.handle('linux-evdev-grant-access', (event): LegacyLinuxEvdevGrantResult => { + requirePrivilegedRendererDocumentSender(event, 'linux-evdev-grant-access'); + return {...LEGACY_LINUX_EVDEV_GRANT_RESULT}; + }); +} diff --git a/fluxer_desktop/src/main/LinuxAudioCapture.ts b/fluxer_desktop/src/main/LinuxAudioCapture.ts index 71c3a9090..bda82b26d 100644 --- a/fluxer_desktop/src/main/LinuxAudioCapture.ts +++ b/fluxer_desktop/src/main/LinuxAudioCapture.ts @@ -14,11 +14,10 @@ import {buildFluxerAudioExcludePatterns, isFluxerAudioNode} from '@electron/main import {getLinuxPortalsMode, getNativeAudioMode} from '@electron/main/LaunchOptions'; import { isDBusObjectPathSegment, - isWaylandSessionEnv, - isX11SessionEnv, isX11WindowToken, parseWindowSourceToken, } from '@electron/main/LinuxAudioCaptureHelpers'; +import {isWaylandSession, isX11Session} from '@electron/main/LinuxSession'; import { isValidVirtmicLinkOptions, isValidVirtmicNodeList, @@ -333,14 +332,6 @@ function startVirtmicSystem(exclude: unknown, options: unknown = {}): boolean { } } -function isX11Session(): boolean { - return isX11SessionEnv(process.env); -} - -function isWaylandSession(): boolean { - return isWaylandSessionEnv(process.env); -} - async function resolveWindowPidViaX11(xid: string): Promise { if (getLinuxPortalsMode(process.argv) === 'off') return null; const portals = requireModule('@fluxer/linux-portals') as { diff --git a/fluxer_desktop/src/main/LinuxAudioCaptureHelpers.ts b/fluxer_desktop/src/main/LinuxAudioCaptureHelpers.ts index 2b374bf7f..1ffb53e61 100644 --- a/fluxer_desktop/src/main/LinuxAudioCaptureHelpers.ts +++ b/fluxer_desktop/src/main/LinuxAudioCaptureHelpers.ts @@ -3,20 +3,6 @@ const MAX_WINDOW_SOURCE_ID_LENGTH = 256; const MAX_WINDOW_TOKEN_LENGTH = 128; -export function isX11SessionEnv(env: NodeJS.ProcessEnv): boolean { - const sessionType = (env.XDG_SESSION_TYPE ?? '').toLowerCase(); - if (sessionType === 'x11') return true; - if (sessionType === 'wayland') return false; - return Boolean(env.DISPLAY); -} - -export function isWaylandSessionEnv(env: NodeJS.ProcessEnv): boolean { - const sessionType = (env.XDG_SESSION_TYPE ?? '').toLowerCase(); - if (sessionType === 'wayland') return true; - if (sessionType === 'x11') return false; - return Boolean(env.WAYLAND_DISPLAY); -} - export function parseWindowSourceToken(sourceId: unknown): string | null { if (typeof sourceId !== 'string' || sourceId.length > MAX_WINDOW_SOURCE_ID_LENGTH) return null; const match = /^window:([^:]+):(?:0|1)$/.exec(sourceId); diff --git a/fluxer_desktop/src/main/LinuxDesktopEntry.test.mjs b/fluxer_desktop/src/main/LinuxDesktopEntry.test.mjs new file mode 100644 index 000000000..cbc48b552 --- /dev/null +++ b/fluxer_desktop/src/main/LinuxDesktopEntry.test.mjs @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import {afterEach, beforeEach, describe, test} from 'node:test'; +import {loadTsModule} from './fixtures/TsModuleLoader.mjs'; + +const EXEC_PATH = '/opt/Fluxer/fluxer'; +const protocolRegistrations = []; +const noopLogger = {debug: () => {}, info: () => {}, warn: () => {}, error: () => {}}; +const englishStrings = { + 'desktop.jumpList.openSettings': 'Open Settings', + 'desktop.jumpList.newDirectMessage': 'New Direct Message', + 'desktop.linuxEntry.genericName': 'Instant Messenger', + 'desktop.linuxEntry.comment': 'Instant messaging and VoIP', +}; + +const desktopEntry = loadTsModule('@electron/main/LinuxDesktopEntry', { + stubs: { + 'node:child_process': {execFile: () => {}}, + 'node:fs': fs, + 'node:os': os, + 'node:path': path, + electron: {app: {setAsDefaultProtocolClient: (protocol) => protocolRegistrations.push(protocol)}}, + '@electron/common/BuildChannel': {BUILD_CHANNEL: 'stable'}, + '@electron/common/Logger': {createChildLogger: () => noopLogger}, + '@electron/main/JumpList': {TASK_ARG_PREFIX: '--fluxer-task='}, + '@electron/main/LinuxLaunchPath': {getStableLinuxLaunchPath: () => EXEC_PATH}, + '@electron/main/LinuxSandbox': {isFlatpakRuntime: () => false}, + '@electron/main/MainI18n': {getNativeLocale: () => 'en-US', t: (key) => englishStrings[key] ?? key}, + }, +}); + +function parseMainGroup(contents) { + const entries = new Map(); + for (const line of contents.split('\n')) { + if (line.startsWith('[Desktop Action ')) break; + const index = line.indexOf('='); + if (index > 0 && !line.startsWith('#')) entries.set(line.slice(0, index), line.slice(index + 1)); + } + return entries; +} + +describe('LinuxDesktopEntry contents', () => { + test('uses the reverse-DNS desktop id for window matching and keeps the packaged icon name', () => { + const entry = parseMainGroup(desktopEntry.buildDesktopFileContents(EXEC_PATH, false)); + assert.equal(entry.get('StartupWMClass'), 'app.fluxer.FluxerDesktop'); + assert.equal(entry.get('Icon'), 'fluxer'); + assert.equal(entry.get('Exec'), `"${EXEC_PATH}" %U`); + assert.equal(entry.get('TryExec'), EXEC_PATH); + assert.equal(entry.get('MimeType'), 'x-scheme-handler/fluxer;'); + assert.equal(entry.has('Hidden'), false); + assert.equal(entry.has('NoDisplay'), false); + }); + + test('a third-party launcher keeps ours out of menus without hiding it from lookups', () => { + const contents = desktopEntry.buildDesktopFileContents(EXEC_PATH, true); + const entry = parseMainGroup(contents); + assert.equal(entry.get('NoDisplay'), 'true'); + assert.equal(entry.has('Hidden'), false); + assert.ok(contents.includes(desktopEntry.GENERATED_MARKER)); + }); +}); + +describe('ensureLinuxDesktopEntry', () => { + let root; + let previous; + + beforeEach(() => { + root = fs.mkdtempSync(path.join(os.tmpdir(), 'fluxer-desktop-entry-')); + previous = { + platform: Object.getOwnPropertyDescriptor(process, 'platform'), + resourcesPath: process.resourcesPath, + dataHome: process.env.XDG_DATA_HOME, + dataDirs: process.env.XDG_DATA_DIRS, + disable: process.env.FLUXER_DISABLE_DESKTOP_FILE, + }; + Object.defineProperty(process, 'platform', {value: 'linux'}); + process.resourcesPath = path.join(root, 'resources'); + process.env.XDG_DATA_HOME = path.join(root, 'home'); + process.env.XDG_DATA_DIRS = path.join(root, 'system'); + delete process.env.FLUXER_DISABLE_DESKTOP_FILE; + protocolRegistrations.length = 0; + }); + + afterEach(() => { + Object.defineProperty(process, 'platform', previous.platform); + process.resourcesPath = previous.resourcesPath; + for (const [key, value] of [ + ['XDG_DATA_HOME', previous.dataHome], + ['XDG_DATA_DIRS', previous.dataDirs], + ['FLUXER_DISABLE_DESKTOP_FILE', previous.disable], + ]) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } + fs.rmSync(root, {recursive: true, force: true}); + }); + + const userDir = () => path.join(root, 'home', 'applications'); + const systemDir = () => path.join(root, 'system', 'applications'); + + test('writes the new entry and removes only the generated legacy entry', () => { + fs.mkdirSync(userDir(), {recursive: true}); + fs.writeFileSync(path.join(userDir(), 'fluxer.desktop'), `[Desktop Entry]\n${desktopEntry.GENERATED_MARKER}\n`); + fs.writeFileSync( + path.join(userDir(), 'fluxer-canary.desktop'), + `[Desktop Entry]\n${desktopEntry.GENERATED_MARKER}\n`, + ); + assert.equal(desktopEntry.ensureLinuxDesktopEntry(), true); + assert.equal(fs.existsSync(path.join(userDir(), 'fluxer.desktop')), false); + assert.equal(fs.existsSync(path.join(userDir(), 'fluxer-canary.desktop')), true); + const written = fs.readFileSync(path.join(userDir(), 'app.fluxer.FluxerDesktop.desktop'), 'utf8'); + assert.equal(written, desktopEntry.buildDesktopFileContents(EXEC_PATH, false)); + assert.deepEqual( + fs.readdirSync(userDir()).filter((name) => name.endsWith('.tmp')), + [], + ); + assert.deepEqual(protocolRegistrations, ['fluxer']); + }); + + test('leaves a hand-written legacy entry alone', () => { + fs.mkdirSync(userDir(), {recursive: true}); + fs.writeFileSync(path.join(userDir(), 'fluxer.desktop'), '[Desktop Entry]\nExec=/somewhere/else\n'); + desktopEntry.ensureLinuxDesktopEntry(); + assert.equal(fs.existsSync(path.join(userDir(), 'fluxer.desktop')), true); + }); + + test('a system entry wins and removes a generated user copy', () => { + fs.mkdirSync(systemDir(), {recursive: true}); + fs.writeFileSync(path.join(systemDir(), 'app.fluxer.FluxerDesktop.desktop'), '[Desktop Entry]\n'); + fs.mkdirSync(userDir(), {recursive: true}); + fs.writeFileSync( + path.join(userDir(), 'app.fluxer.FluxerDesktop.desktop'), + desktopEntry.buildDesktopFileContents(EXEC_PATH, false), + ); + assert.equal(desktopEntry.ensureLinuxDesktopEntry(), true); + assert.equal(fs.existsSync(path.join(userDir(), 'app.fluxer.FluxerDesktop.desktop')), false); + }); + + test('a system entry under the previous desktop id keeps the new user entry out of menus', () => { + fs.mkdirSync(systemDir(), {recursive: true}); + fs.writeFileSync(path.join(systemDir(), 'fluxer.desktop'), '[Desktop Entry]\nExec=/usr/bin/fluxer\n'); + assert.equal(desktopEntry.ensureLinuxDesktopEntry(), true); + const written = fs.readFileSync(path.join(userDir(), 'app.fluxer.FluxerDesktop.desktop'), 'utf8'); + assert.equal(written, desktopEntry.buildDesktopFileContents(EXEC_PATH, true)); + assert.equal(fs.existsSync(path.join(systemDir(), 'fluxer.desktop')), true); + }); + + test('FLUXER_DISABLE_DESKTOP_FILE leaves launchers and the scheme handler alone', () => { + process.env.FLUXER_DISABLE_DESKTOP_FILE = '1'; + fs.mkdirSync(userDir(), {recursive: true}); + fs.writeFileSync(path.join(userDir(), 'fluxer.desktop'), `[Desktop Entry]\n${desktopEntry.GENERATED_MARKER}\n`); + assert.equal(desktopEntry.ensureLinuxDesktopEntry(), false); + assert.equal(fs.existsSync(path.join(userDir(), 'fluxer.desktop')), true); + assert.equal(fs.existsSync(path.join(userDir(), 'app.fluxer.FluxerDesktop.desktop')), false); + assert.deepEqual(protocolRegistrations, []); + }); + + test('FLUXER_DISABLE_DESKTOP_FILE still reports an entry the user installed', () => { + process.env.FLUXER_DISABLE_DESKTOP_FILE = '1'; + fs.mkdirSync(userDir(), {recursive: true}); + fs.writeFileSync(path.join(userDir(), 'app.fluxer.FluxerDesktop.desktop'), '[Desktop Entry]\n'); + assert.equal(desktopEntry.ensureLinuxDesktopEntry(), true); + assert.deepEqual(protocolRegistrations, []); + }); + + test('FLUXER_DISABLE_DESKTOP_FILE still points the scheme handler at a packaged entry', () => { + process.env.FLUXER_DISABLE_DESKTOP_FILE = '1'; + fs.mkdirSync(systemDir(), {recursive: true}); + fs.writeFileSync(path.join(systemDir(), 'app.fluxer.FluxerDesktop.desktop'), '[Desktop Entry]\n'); + fs.mkdirSync(userDir(), {recursive: true}); + fs.writeFileSync(path.join(userDir(), 'fluxer.desktop'), `[Desktop Entry]\n${desktopEntry.GENERATED_MARKER}\n`); + assert.equal(desktopEntry.ensureLinuxDesktopEntry(), true); + assert.equal(fs.existsSync(path.join(userDir(), 'fluxer.desktop')), true); + assert.deepEqual(protocolRegistrations, ['fluxer']); + }); +}); diff --git a/fluxer_desktop/src/main/LinuxDesktopEntry.ts b/fluxer_desktop/src/main/LinuxDesktopEntry.ts index 3bf38b3d7..c0d4c051a 100644 --- a/fluxer_desktop/src/main/LinuxDesktopEntry.ts +++ b/fluxer_desktop/src/main/LinuxDesktopEntry.ts @@ -5,7 +5,12 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import {APP_PROTOCOL} from '@electron/common/Constants'; -import {DESKTOP_APP_NAME, LINUX_DESKTOP_ENTRY_ID} from '@electron/common/DesktopIdentity'; +import { + DESKTOP_APP_NAME, + LEGACY_LINUX_DESKTOP_ENTRY_ID, + LINUX_DESKTOP_ENTRY_ID, + LINUX_ICON_NAME, +} from '@electron/common/DesktopIdentity'; import {createChildLogger} from '@electron/common/Logger'; import {TASK_ARG_PREFIX} from '@electron/main/JumpList'; import {getStableLinuxLaunchPath} from '@electron/main/LinuxLaunchPath'; @@ -16,11 +21,12 @@ import {app} from 'electron'; const logger = createChildLogger('LinuxDesktopEntry'); const APP_NAME = DESKTOP_APP_NAME; const APP_ID = LINUX_DESKTOP_ENTRY_ID; -export const WM_CLASS = APP_ID; +const WM_CLASS = APP_ID; const DESKTOP_FILE_BASENAME = `${APP_ID}.desktop`; +const LEGACY_DESKTOP_FILE_BASENAME = `${LEGACY_LINUX_DESKTOP_ENTRY_ID}.desktop`; const GENERIC_NAME_DEFAULT = 'Instant Messenger'; const COMMENT_DEFAULT = 'Instant messaging and VoIP'; -const GENERATED_MARKER = '# X-Generated-By=fluxer-desktop'; +export const GENERATED_MARKER = '# X-Generated-By=fluxer-desktop'; const DESKTOP_ACTIONS = [ { id: 'open-settings', @@ -53,9 +59,9 @@ function getDesktopFilePath(): string { return path.join(getUserApplicationsDir(), DESKTOP_FILE_BASENAME); } -function findSystemDesktopEntry(): string | null { +function findSystemDesktopEntry(basename = DESKTOP_FILE_BASENAME): string | null { for (const dataDir of getXdgDataDirs()) { - const candidate = path.join(dataDir, 'applications', DESKTOP_FILE_BASENAME); + const candidate = path.join(dataDir, 'applications', basename); try { if (fs.existsSync(candidate)) return candidate; } catch {} @@ -93,7 +99,7 @@ function installHicolorIcons(): void { const iconRoot = path.join(process.resourcesPath, 'icons'); for (const size of HICOLOR_ICON_SIZES) { const source = path.join(iconRoot, `${size}x${size}.png`); - const target = path.join(getXdgDataHome(), 'icons', 'hicolor', `${size}x${size}`, 'apps', `${APP_ID}.png`); + const target = path.join(getXdgDataHome(), 'icons', 'hicolor', `${size}x${size}`, 'apps', `${LINUX_ICON_NAME}.png`); try { if (!fs.existsSync(source)) continue; fs.mkdirSync(path.dirname(target), {recursive: true}); @@ -104,10 +110,6 @@ function installHicolorIcons(): void { } } -function resolveIconHint(): string { - return APP_ID; -} - function buildDesktopActionExecLine(execPath: string, taskId: (typeof DESKTOP_ACTIONS)[number]['id']): string { return `${quoteExecArg(execPath)} ${TASK_ARG_PREFIX}${taskId} %U`; } @@ -134,7 +136,7 @@ function buildLocalizedEntryLines(key: string, defaultValue: string, localizedVa return lines; } -function buildDesktopFileContents(execPath: string, hidden: boolean): string { +export function buildDesktopFileContents(execPath: string, hidden: boolean): string { const execLine = `${quoteExecArg(execPath)} %U`; return [ '[Desktop Entry]', @@ -146,7 +148,7 @@ function buildDesktopFileContents(execPath: string, hidden: boolean): string { ...buildLocalizedEntryLines('Comment', COMMENT_DEFAULT, t('desktop.linuxEntry.comment')), `Exec=${escapeDesktopValue(execLine)}`, `TryExec=${escapeDesktopValue(execPath)}`, - `Icon=${escapeDesktopValue(resolveIconHint())}`, + `Icon=${escapeDesktopValue(LINUX_ICON_NAME)}`, 'Terminal=false', 'Categories=Network;InstantMessaging;Chat;', `MimeType=x-scheme-handler/${APP_PROTOCOL};`, @@ -202,96 +204,104 @@ function runUpdateDesktopDatabase(applicationsDir: string): void { }); } -export function ensureLinuxProtocolDesktopEntry(): void { - if (process.platform !== 'linux') return; - if (isFlatpakRuntime()) { - logger.debug('Skipping .desktop entry creation in Flatpak; package export owns launcher/protocol integration'); - try { - app.setAsDefaultProtocolClient(APP_PROTOCOL); - } catch (error) { - logger.warn('Failed to register protocol client', {error}); - } - return; - } - if (process.env.FLUXER_DISABLE_DESKTOP_FILE === '1') { - logger.debug('Skipping .desktop entry creation; FLUXER_DISABLE_DESKTOP_FILE=1'); - try { - app.setAsDefaultProtocolClient(APP_PROTOCOL); - } catch (error) { - logger.warn('Failed to register protocol client', {error}); - } - return; - } - const execPath = getStableLinuxLaunchPath(); - const applicationsDir = getUserApplicationsDir(); - const filePath = getDesktopFilePath(); - installHicolorIcons(); - const existing = readExistingDesktopFile(filePath); - const thirdPartyEntry = findThirdPartyDesktopEntry(execPath); - if (thirdPartyEntry) { - logger.debug('Third-party .desktop entry manages the app menu entry; keeping ours hidden', { - thirdPartyEntry, - }); - } - const desired = buildDesktopFileContents(execPath, thirdPartyEntry !== null); - let needsWrite = true; - if (existing === null) { - const systemEntry = findSystemDesktopEntry(); - if (systemEntry) { - logger.debug('System-wide .desktop entry detected; skipping user-local copy', {systemEntry}); - try { - app.setAsDefaultProtocolClient(APP_PROTOCOL); - } catch (error) { - logger.warn('Failed to register protocol client', {error}); - } - return; - } - } - if (existing !== null) { - if (!existing.includes(GENERATED_MARKER)) { - if (!isStaleDesktopFile(existing)) { - logger.debug('Linux .desktop entry was hand-edited; leaving untouched', {filePath}); - return; - } - const systemEntry = findSystemDesktopEntry(); - if (systemEntry) { - try { - fs.unlinkSync(filePath); - logger.info('Removed stale .desktop entry shadowing the system entry', {filePath, systemEntry}); - } catch (error) { - logger.warn('Failed to remove stale .desktop entry', {filePath, error}); - } - try { - app.setAsDefaultProtocolClient(APP_PROTOCOL); - } catch (error) { - logger.warn('Failed to register protocol client', {error}); - } - return; - } - logger.info('Rewriting stale .desktop entry whose TryExec no longer resolves', {filePath}); - } else { - needsWrite = existing !== desired; - } - } - if (!needsWrite) { - logger.debug('Linux .desktop entry already up to date', {filePath}); - } else { - try { - fs.mkdirSync(applicationsDir, {recursive: true}); - fs.writeFileSync(filePath, desired, {encoding: 'utf8', mode: 0o644}); - logger.info('Wrote Linux .desktop entry for protocol registration', {filePath, execPath}); - } catch (error) { - logger.warn('Failed to write Linux .desktop entry; deep links may fall back to the browser', { - filePath, - error, - }); - return; - } - runUpdateDesktopDatabase(applicationsDir); - } +function registerProtocolClient(): void { try { app.setAsDefaultProtocolClient(APP_PROTOCOL); } catch (error) { - logger.warn('Failed to re-register protocol client', {error}); + logger.warn('Failed to register protocol client', {error}); } } + +function removeFile(filePath: string, reason: string): boolean { + try { + fs.unlinkSync(filePath); + logger.info(reason, {filePath}); + return true; + } catch (error) { + logger.warn('Failed to remove .desktop entry', {filePath, error}); + return false; + } +} + +function removeLegacyGeneratedDesktopEntry(): boolean { + const legacyPath = path.join(getUserApplicationsDir(), LEGACY_DESKTOP_FILE_BASENAME); + const contents = readExistingDesktopFile(legacyPath); + if (contents === null || !contents.includes(GENERATED_MARKER)) return false; + return removeFile(legacyPath, 'Removed the generated .desktop entry for the previous desktop id'); +} + +function writeDesktopFileAtomically(filePath: string, contents: string): void { + const tempPath = `${filePath}.${process.pid}.tmp`; + try { + fs.mkdirSync(path.dirname(filePath), {recursive: true}); + fs.writeFileSync(tempPath, contents, {encoding: 'utf8', mode: 0o644}); + fs.renameSync(tempPath, filePath); + } catch (error) { + try { + fs.rmSync(tempPath, {force: true}); + } catch {} + throw error; + } +} + +function syncUserDesktopEntry(): boolean { + const execPath = getStableLinuxLaunchPath(); + const filePath = getDesktopFilePath(); + const existing = readExistingDesktopFile(filePath); + const systemEntry = findSystemDesktopEntry(); + if (systemEntry) { + if (existing !== null && (existing.includes(GENERATED_MARKER) || isStaleDesktopFile(existing))) { + return removeFile(filePath, 'Removed a user .desktop entry shadowing the system entry'); + } + logger.debug('System-wide .desktop entry detected; skipping user-local copy', {systemEntry}); + return false; + } + if (existing !== null && !existing.includes(GENERATED_MARKER) && !isStaleDesktopFile(existing)) { + logger.debug('Linux .desktop entry was hand-edited; leaving untouched', {filePath}); + return false; + } + installHicolorIcons(); + const thirdPartyEntry = findThirdPartyDesktopEntry(execPath) ?? findSystemDesktopEntry(LEGACY_DESKTOP_FILE_BASENAME); + if (thirdPartyEntry) { + logger.debug('Another .desktop entry manages the app menu entry; keeping ours hidden', {thirdPartyEntry}); + } + const desired = buildDesktopFileContents(execPath, thirdPartyEntry !== null); + if (existing === desired) { + logger.debug('Linux .desktop entry already up to date', {filePath}); + return false; + } + writeDesktopFileAtomically(filePath, desired); + logger.info('Wrote Linux .desktop entry', {filePath, execPath}); + return true; +} + +function desktopEntryResolves(): boolean { + return findSystemDesktopEntry() !== null || readExistingDesktopFile(getDesktopFilePath()) !== null; +} + +export function ensureLinuxDesktopEntry(): boolean { + if (process.platform !== 'linux') return false; + if (isFlatpakRuntime()) { + logger.debug('Skipping .desktop entry creation in Flatpak; package export owns launcher/protocol integration'); + registerProtocolClient(); + return false; + } + if (process.env.FLUXER_DISABLE_DESKTOP_FILE === '1') { + logger.debug('Skipping .desktop entry management; FLUXER_DISABLE_DESKTOP_FILE=1'); + if (findSystemDesktopEntry() !== null) registerProtocolClient(); + return desktopEntryResolves(); + } + let changed = removeLegacyGeneratedDesktopEntry(); + try { + changed = syncUserDesktopEntry() || changed; + } catch (error) { + logger.warn('Failed to write Linux .desktop entry; deep links and portals may not resolve the app', { + filePath: getDesktopFilePath(), + error, + }); + } + if (changed) runUpdateDesktopDatabase(getUserApplicationsDir()); + const resolves = desktopEntryResolves(); + if (resolves) registerProtocolClient(); + return resolves; +} diff --git a/fluxer_desktop/src/main/LinuxGlobalShortcutsPortal.test.mjs b/fluxer_desktop/src/main/LinuxGlobalShortcutsPortal.test.mjs new file mode 100644 index 000000000..0e64eb2c8 --- /dev/null +++ b/fluxer_desktop/src/main/LinuxGlobalShortcutsPortal.test.mjs @@ -0,0 +1,896 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import assert from 'node:assert/strict'; +import {describe, test} from 'node:test'; +import {loadTsModule} from './fixtures/TsModuleLoader.mjs'; + +const { + LinuxPortalShortcutsManager, + PORTAL_RECOVERY_BACKOFF_MS, + PORTAL_NO_PORTAL_RETRY_WINDOW_MS, + PORTAL_TOGGLE_REARM_MS, +} = loadTsModule('@electron/main/LinuxGlobalShortcutsPortal'); +const {GLOBAL_SHORTCUT_ACTIONS} = loadTsModule('@electron/common/GlobalShortcutActions'); + +function listedAll(trigger = 'F13') { + return GLOBAL_SHORTCUT_ACTIONS.map((id) => ({id, description: id, triggerDescription: trigger})); +} + +class FakePortal { + constructor(harness, onEvent) { + this.harness = harness; + this.onEvent = onEvent; + this.closed = false; + this.bindCalls = []; + this.configureCalls = []; + } + + async open() { + this.harness.openCalls += 1; + const next = this.harness.openResults.shift() ?? this.harness.defaultOpen; + if (next instanceof Error) throw next; + if (typeof next === 'function') return next(this); + return next; + } + + async bind(shortcuts, parentWindow) { + this.bindCalls.push({shortcuts, parentWindow}); + this.harness.bindCalls.push({shortcuts, parentWindow}); + const next = this.harness.bindResults.shift() ?? {outcome: 'bound', shortcuts: listedAll()}; + if (next instanceof Error) throw next; + return next; + } + + async configure(parentWindow) { + this.configureCalls.push(parentWindow); + this.harness.configureCalls.push(parentWindow); + const next = this.harness.configureResults.shift(); + if (next instanceof Error) throw next; + } + + close() { + this.closed = true; + } + + emit(event) { + this.onEvent(event); + } +} + +function createHarness({ + consent = 'unset', + desktop = 'gnome', + plasma5 = false, + version = 2, + listed = [], + appIdSource = 'registered', +} = {}) { + const harness = { + consent, + openCalls: 0, + openResults: [], + bindResults: [], + bindCalls: [], + configureCalls: [], + configureResults: [], + defaultOpen: {version, appIdSource, uniqueName: ':1.42', listed}, + portals: [], + shortcuts: [], + statusChanges: 0, + timers: [], + clock: 0, + }; + harness.manager = new LinuxPortalShortcutsManager({ + createPortal: (onEvent) => { + const portal = new FakePortal(harness, onEvent); + harness.portals.push(portal); + return portal; + }, + desktop, + plasma5, + portalAppId: 'app.fluxer.FluxerDesktop', + getConsent: () => harness.consent, + setConsent: (next) => { + harness.consent = next; + }, + getDefinitions: () => GLOBAL_SHORTCUT_ACTIONS.map((id) => ({id, description: `Label ${id}`})), + getParentWindow: () => 'x11:2a', + onShortcut: (action, phase) => harness.shortcuts.push(`${phase}:${action}`), + onStatusChanged: () => { + harness.statusChanges += 1; + }, + schedule: (callback, delayMs) => { + const timer = {callback, delayMs, cancelled: false, cancel: () => (timer.cancelled = true)}; + harness.timers.push(timer); + return timer; + }, + now: () => harness.clock, + log: () => {}, + }); + harness.latestPortal = () => harness.portals[harness.portals.length - 1]; + harness.runTimer = async () => { + const timer = harness.timers.find((entry) => !entry.cancelled && !entry.ran); + assert.ok(timer, 'expected a pending timer'); + timer.ran = true; + timer.callback(); + await harness.manager.probe(); + await flush(); + return timer.delayMs; + }; + return harness; +} + +async function flush() { + for (let index = 0; index < 10; index += 1) await Promise.resolve(); +} + +function recordStates(harness) { + const states = []; + const original = harness.manager.deps.onStatusChanged; + harness.manager.deps.onStatusChanged = () => { + const state = harness.manager.getState(); + if (states[states.length - 1] !== state) states.push(state); + original(); + }; + return states; +} + +async function runAllTimers(harness) { + const delays = []; + for (;;) { + const pending = harness.timers.find((timer) => !timer.cancelled && !timer.ran); + if (!pending) return delays; + harness.clock += pending.delayMs; + delays.push(await harness.runTimer()); + } +} + +describe('LinuxPortalShortcutsManager consent', () => { + test('does not open the portal at activation without consent, and a probe reports not-set-up', async () => { + const harness = createHarness({consent: 'unset'}); + await harness.manager.activate(); + assert.equal(harness.openCalls, 0); + assert.equal(harness.manager.getState(), 'unknown'); + await harness.manager.probe(); + assert.equal(harness.openCalls, 1); + assert.equal(harness.manager.getState(), 'not-set-up'); + assert.equal(harness.bindCalls.length, 0); + }); + + test('a stored decline is reported without prompting', async () => { + const harness = createHarness({consent: 'declined'}); + await harness.manager.activate(); + await harness.manager.probe(); + assert.equal(harness.manager.getState(), 'declined'); + assert.equal(harness.bindCalls.length, 0); + }); + + test('set up binds the full action set and stores consent', async () => { + const harness = createHarness({consent: 'unset'}); + await harness.manager.activate(); + await harness.manager.setUp(); + assert.equal(harness.bindCalls.length, 1); + assert.deepEqual( + harness.bindCalls[0].shortcuts.map((entry) => entry.id), + [...GLOBAL_SHORTCUT_ACTIONS], + ); + assert.equal(harness.bindCalls[0].parentWindow, 'x11:2a'); + assert.equal(harness.consent, 'granted'); + const status = harness.manager.getStatus(); + assert.equal(status.state, 'bound'); + assert.equal(status.canConfigure, true); + assert.equal(status.shortcuts.length, GLOBAL_SHORTCUT_ACTIONS.length); + assert.equal(status.shortcuts[0].triggerDescription, 'F13'); + }); + + for (const outcome of [{outcome: 'denied'}, {outcome: 'cancelled'}]) { + test(`a ${outcome.outcome} bind stores the decline`, async () => { + const harness = createHarness({consent: 'unset'}); + harness.bindResults.push(outcome); + await harness.manager.activate(); + await harness.manager.setUp(); + assert.equal(harness.manager.getState(), 'declined'); + assert.equal(harness.consent, 'declined'); + }); + } + + test('a failed bind is an error that keeps consent untouched', async () => { + const harness = createHarness({consent: 'unset'}); + harness.bindResults.push({outcome: 'failed', code: 3}); + await harness.manager.activate(); + await harness.manager.setUp(); + assert.equal(harness.manager.getState(), 'error'); + assert.equal(harness.manager.getStatus().error, 'bind-failed'); + assert.equal(harness.consent, 'unset'); + }); + + test('a GNOME 48 bind with shortcuts but a garbage response code is bound', async () => { + const harness = createHarness({consent: 'unset'}); + harness.bindResults.push({outcome: 'bound', shortcuts: listedAll('Ctrl+F13')}); + await harness.manager.activate(); + await harness.manager.setUp(); + assert.equal(harness.manager.getState(), 'bound'); + assert.equal(harness.consent, 'granted'); + }); + + test('try again after a decline binds again', async () => { + const harness = createHarness({consent: 'declined'}); + await harness.manager.activate(); + await harness.manager.probe(); + await harness.manager.setUp(); + assert.equal(harness.bindCalls.length, 1); + assert.equal(harness.manager.getState(), 'bound'); + }); +}); + +describe('LinuxPortalShortcutsManager startup bind policy', () => { + test('version 2 binds at startup when consent was granted', async () => { + const harness = createHarness({consent: 'granted', version: 2, listed: listedAll()}); + await harness.manager.activate(); + assert.equal(harness.bindCalls.length, 1); + assert.equal(harness.manager.getState(), 'bound'); + }); + + test('version 1 skips the bind when the list already covers every id', async () => { + const harness = createHarness({consent: 'granted', version: 1, desktop: 'kde', listed: listedAll()}); + await harness.manager.activate(); + assert.equal(harness.bindCalls.length, 0); + assert.equal(harness.manager.getState(), 'bound'); + assert.equal(harness.manager.getStatus().canConfigure, false); + }); + + test('version 1 on KDE with an empty list waits for the user', async () => { + const harness = createHarness({consent: 'granted', version: 1, desktop: 'kde', listed: []}); + await harness.manager.activate(); + assert.equal(harness.bindCalls.length, 0); + assert.equal(harness.manager.getState(), 'not-set-up'); + assert.equal(harness.consent, 'granted'); + }); + + test('version 1 on KDE with a partial list binds', async () => { + const harness = createHarness({consent: 'granted', version: 1, desktop: 'kde', listed: listedAll().slice(0, 3)}); + await harness.manager.activate(); + assert.equal(harness.bindCalls.length, 1); + }); + + test('version 1 on GNOME binds with an empty list', async () => { + const harness = createHarness({consent: 'granted', version: 1, desktop: 'gnome', listed: []}); + await harness.manager.activate(); + assert.equal(harness.bindCalls.length, 1); + assert.equal(harness.manager.getState(), 'bound'); + }); + + test('Plasma 5 is unsupported and never opens the portal', async () => { + const harness = createHarness({consent: 'granted', plasma5: true}); + await harness.manager.activate(); + await harness.manager.probe(); + await harness.manager.setUp(); + assert.equal(harness.openCalls, 0); + assert.equal(harness.manager.getState(), 'unsupported'); + assert.equal(harness.manager.getStatus().canRecheck, false); + }); + + test('open failures map to unsupported, identity and error states', async () => { + const cases = [ + ['unsupported:no-portal', 'unsupported', null], + ['unsupported:2', 'unsupported', null], + ['identity:not-found', 'error', 'identity'], + ['error:timeout', 'error', 'timeout'], + ['error:bus', 'error', 'open-failed'], + ]; + for (const [message, state, error] of cases) { + const harness = createHarness({consent: 'granted'}); + harness.openResults.push(new Error(message)); + await harness.manager.activate(); + assert.equal(harness.manager.getState(), state, message); + assert.equal(harness.manager.getStatus().error, error, message); + assert.equal(harness.manager.getStatus().canRecheck, true, message); + } + }); + + test('an unregistered app id hides the portal app id', async () => { + const harness = createHarness({consent: 'unset', appIdSource: 'unregistered'}); + await harness.manager.activate(); + await harness.manager.probe(); + assert.equal(harness.manager.getStatus().portalAppId, null); + }); +}); + +describe('LinuxPortalShortcutsManager events', () => { + test('activations are deduplicated and unknown ids are ignored', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + const portal = harness.latestPortal(); + portal.emit({type: 'activated', id: 'voice_push_to_talk'}); + portal.emit({type: 'activated', id: 'voice_push_to_talk'}); + portal.emit({type: 'activated', id: 'not_an_action'}); + portal.emit({type: 'deactivated', id: 'voice_toggle_mute'}); + portal.emit({type: 'deactivated', id: 'voice_push_to_talk'}); + assert.deepEqual(harness.shortcuts, ['press:voice_push_to_talk', 'release:voice_push_to_talk']); + }); + + test('shortcuts-changed with empty triggers keeps the bound state', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + harness.latestPortal().emit({type: 'shortcuts-changed', shortcuts: listedAll('')}); + const status = harness.manager.getStatus(); + assert.equal(status.state, 'bound'); + assert.ok(status.shortcuts.every((entry) => entry.triggerDescription === null)); + }); + + test('session loss releases held shortcuts and reopens with backoff while still reporting bound', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + const first = harness.latestPortal(); + first.emit({type: 'activated', id: 'voice_push_to_talk'}); + const changesBefore = harness.statusChanges; + first.emit({type: 'session-lost', reason: 'portal-restarted'}); + assert.deepEqual(harness.shortcuts, ['press:voice_push_to_talk', 'release:voice_push_to_talk']); + assert.equal(first.closed, true); + assert.ok(harness.statusChanges > changesBefore); + let status = harness.manager.getStatus(); + assert.equal(status.state, 'bound'); + assert.equal(status.recovering, true); + assert.equal(status.canConfigure, false); + await harness.manager.configure(); + assert.deepEqual(harness.configureCalls, []); + first.emit({type: 'activated', id: 'voice_toggle_mute'}); + assert.equal(harness.shortcuts.length, 2); + harness.openResults.push(new Error('error:bus'), new Error('error:timeout')); + assert.equal(await harness.runTimer(), PORTAL_RECOVERY_BACKOFF_MS[0]); + assert.equal(harness.manager.getState(), 'bound'); + assert.equal(harness.manager.getStatus().recovering, true); + assert.equal(await harness.runTimer(), PORTAL_RECOVERY_BACKOFF_MS[1]); + assert.equal(await harness.runTimer(), PORTAL_RECOVERY_BACKOFF_MS[2]); + status = harness.manager.getStatus(); + assert.equal(status.state, 'bound'); + assert.equal(status.recovering, false); + assert.equal(status.canConfigure, true); + assert.equal(harness.openCalls, 4); + }); + + test('a reopen that lists new triggers pushes a status even when the state stays bound', async () => { + const harness = createHarness({consent: 'granted', version: 1, desktop: 'kde', listed: listedAll()}); + await harness.manager.activate(); + harness.latestPortal().emit({type: 'session-lost', reason: 'portal-restarted'}); + harness.defaultOpen = {...harness.defaultOpen, listed: listedAll('F14')}; + const changesBefore = harness.statusChanges; + await harness.runTimer(); + assert.equal(harness.bindCalls.length, 0); + assert.ok(harness.statusChanges > changesBefore); + const status = harness.manager.getStatus(); + assert.equal(status.recovering, false); + assert.ok(status.shortcuts.every((entry) => entry.triggerDescription === 'F14')); + }); + + test('a recovery that runs out of backoff steps settles into an error and keeps retrying', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + harness.latestPortal().emit({type: 'session-lost', reason: 'portal-restarted'}); + for (const delay of PORTAL_RECOVERY_BACKOFF_MS) { + assert.equal(harness.manager.getStatus().recovering, true); + harness.openResults.push(new Error('error:bus')); + assert.equal(await harness.runTimer(), delay); + } + const status = harness.manager.getStatus(); + assert.equal(status.state, 'error'); + assert.equal(status.error, 'portal-unavailable'); + assert.equal(status.recovering, false); + assert.equal(await harness.runTimer(), 30000); + assert.equal(harness.manager.getState(), 'bound'); + }); + + test('the backoff caps at thirty seconds', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + harness.latestPortal().emit({type: 'session-lost', reason: 'bus-error'}); + const delays = []; + for (let index = 0; index < 7; index += 1) { + harness.openResults.push(new Error('down')); + delays.push(await harness.runTimer()); + } + assert.deepEqual(delays, [1000, 2000, 5000, 10000, 30000, 30000, 30000]); + }); + + test('a closed session reopens once, then a second close within a minute is an error', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + harness.latestPortal().emit({type: 'session-lost', reason: 'closed'}); + await harness.runTimer(); + assert.equal(harness.manager.getState(), 'bound'); + harness.clock += 1000; + harness.latestPortal().emit({type: 'session-lost', reason: 'closed'}); + assert.equal(harness.manager.getState(), 'error'); + assert.equal(harness.manager.getStatus().error, 'session-closed'); + assert.equal(harness.timers.filter((timer) => !timer.cancelled && !timer.ran).length, 0); + }); + + test('deactivate releases held shortcuts, cancels recovery and ignores late events', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + const portal = harness.latestPortal(); + portal.emit({type: 'activated', id: 'voice_push_to_mute'}); + harness.manager.deactivate(); + assert.deepEqual(harness.shortcuts, ['press:voice_push_to_mute', 'release:voice_push_to_mute']); + assert.equal(portal.closed, true); + assert.equal(harness.manager.getState(), 'unknown'); + portal.emit({type: 'activated', id: 'voice_push_to_mute'}); + assert.equal(harness.shortcuts.length, 2); + }); + + test('a deactivate that lands before the queued startup never opens a session', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + const activation = harness.manager.activate(); + harness.manager.deactivate(); + await activation; + await flush(); + assert.equal(harness.openCalls, 0); + assert.equal(harness.bindCalls.length, 0); + assert.equal(harness.manager.getState(), 'unknown'); + }); + + test('check again is only offered where it can do something', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + assert.equal(harness.manager.getStatus().canRecheck, false); + harness.manager.deactivate(); + assert.equal(harness.manager.getStatus().canRecheck, false); + }); + + test('configure only runs on version 2 portals', async () => { + const v2 = createHarness({consent: 'granted', version: 2, listed: listedAll()}); + await v2.manager.activate(); + await v2.manager.configure(); + assert.deepEqual(v2.configureCalls, ['x11:2a']); + const v1 = createHarness({consent: 'granted', version: 1, desktop: 'kde', listed: listedAll()}); + await v1.manager.activate(); + await v1.manager.configure(); + assert.deepEqual(v1.configureCalls, []); + }); +}); + +function deferred() { + let resolve; + let reject; + const promise = new Promise((onResolve, onReject) => { + resolve = onResolve; + reject = onReject; + }); + return {promise, resolve, reject}; +} + +describe('LinuxPortalShortcutsManager portal availability', () => { + test('a missing portal at startup is retried for about thirty seconds without flickering', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + const noPortal = () => new Error('unsupported:no-portal'); + harness.openResults.push(noPortal(), noPortal(), noPortal(), noPortal(), noPortal(), noPortal()); + const states = recordStates(harness); + await harness.manager.activate(); + assert.equal(harness.manager.getState(), 'unsupported'); + assert.equal(harness.manager.getStatus().recovering, true); + const delays = await runAllTimers(harness); + assert.deepEqual(delays, [1000, 2000, 5000, 10000, 12000]); + assert.equal( + delays.reduce((sum, delay) => sum + delay, 0), + PORTAL_NO_PORTAL_RETRY_WINDOW_MS, + ); + assert.equal(harness.manager.getState(), 'unsupported'); + assert.equal(harness.manager.getStatus().recovering, false); + assert.deepEqual(states, ['probing', 'unsupported']); + }); + + test('a portal restart during the startup open is retried until it binds', async () => { + for (const message of ['error:session-lost', 'error:timeout', 'error:bus', 'unsupported:2']) { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + harness.openResults.push(new Error(message), new Error(message)); + const settledStates = []; + const original = harness.manager.deps.onStatusChanged; + harness.manager.deps.onStatusChanged = () => { + const status = harness.manager.getStatus(); + if (!status.recovering) settledStates.push(status.state); + original(); + }; + await harness.manager.activate(); + assert.equal(harness.manager.getStatus().recovering, true, message); + await runAllTimers(harness); + assert.deepEqual(settledStates, ['probing', 'bound'], message); + assert.equal(harness.manager.getState(), 'bound', message); + assert.equal(harness.manager.getStatus().recovering, false, message); + assert.equal(harness.bindCalls.length, 1, message); + } + }); + + test('startup errors without consent are shown without retrying', async () => { + const harness = createHarness({consent: 'unset'}); + harness.openResults.push(new Error('error:bus')); + await harness.manager.activate(); + await harness.manager.probe(); + assert.equal(harness.manager.getState(), 'error'); + assert.equal(harness.manager.getStatus().error, 'open-failed'); + assert.equal(harness.manager.getStatus().recovering, false); + assert.equal(harness.timers.length, 0); + }); + + test('startup errors that outlast the retry window settle', async () => { + const harness = createHarness({consent: 'granted'}); + for (let index = 0; index < 8; index += 1) harness.openResults.push(new Error('error:timeout')); + await harness.manager.activate(); + await runAllTimers(harness); + const status = harness.manager.getStatus(); + assert.equal(status.state, 'error'); + assert.equal(status.error, 'timeout'); + assert.equal(status.recovering, false); + }); + + test('check again reopens an unsupported portal', async () => { + const harness = createHarness({consent: 'unset'}); + harness.openResults.push(new Error('unsupported:interface')); + await harness.manager.activate(); + await harness.manager.probe(); + assert.equal(harness.manager.getState(), 'unsupported'); + const states = recordStates(harness); + await harness.manager.recheck(); + assert.deepEqual(states, ['probing', 'not-set-up']); + assert.equal(harness.bindCalls.length, 0); + }); + + test('check again leaves a working session alone', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + await harness.manager.recheck(); + assert.equal(harness.openCalls, 1); + assert.equal(harness.latestPortal().closed, false); + }); + + test('a missing portal that comes up during the retry window binds', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + harness.openResults.push(new Error('unsupported:no-portal')); + await harness.manager.activate(); + await harness.runTimer(); + assert.equal(harness.manager.getState(), 'bound'); + assert.equal(harness.bindCalls.length, 1); + }); + + test('the portal appearing on the bus re-probes after the retries gave up', async () => { + const harness = createHarness({consent: 'unset'}); + harness.openResults.push(new Error('unsupported:no-portal')); + await harness.manager.activate(); + await harness.manager.probe(); + const failed = harness.latestPortal(); + assert.equal(failed.closed, false); + assert.equal(harness.manager.getState(), 'unsupported'); + failed.emit({type: 'portal-available'}); + await harness.manager.probe(); + await flush(); + assert.equal(harness.manager.getState(), 'not-set-up'); + assert.equal(failed.closed, true); + assert.equal(harness.timers.filter((timer) => !timer.cancelled && !timer.ran).length, 0); + }); + + test('an interface that is genuinely unsupported is final and ignores the bus', async () => { + const harness = createHarness({consent: 'granted'}); + harness.openResults.push(new Error('unsupported:interface')); + await harness.manager.activate(); + assert.equal(harness.manager.getState(), 'unsupported'); + assert.equal(harness.timers.length, 0); + assert.equal(harness.latestPortal().closed, true); + }); + + test('a missing portal during recovery keeps backing off without leaving bound', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + harness.latestPortal().emit({type: 'session-lost', reason: 'portal-restarted'}); + harness.openResults.push(new Error('unsupported:no-portal')); + await harness.runTimer(); + assert.equal(harness.manager.getState(), 'bound'); + assert.equal(harness.manager.getStatus().recovering, true); + assert.equal(await harness.runTimer(), PORTAL_RECOVERY_BACKOFF_MS[1]); + assert.equal(harness.manager.getState(), 'bound'); + assert.equal(harness.manager.getStatus().recovering, false); + }); + + test('a retry from an error state never passes through probing', async () => { + const harness = createHarness({consent: 'unset'}); + harness.openResults.push(new Error('error:timeout')); + await harness.manager.activate(); + await harness.manager.probe(); + assert.equal(harness.manager.getState(), 'error'); + const states = recordStates(harness); + await harness.manager.setUp(); + assert.deepEqual(states, ['error', 'binding', 'bound']); + }); +}); + +describe('LinuxPortalShortcutsManager bind single flight', () => { + test('a second set up joins the pending bind', async () => { + const harness = createHarness({consent: 'unset'}); + const pending = deferred(); + harness.bindResults.push(pending.promise); + await harness.manager.activate(); + await harness.manager.probe(); + const first = harness.manager.setUp(); + await flush(); + const second = harness.manager.setUp(); + await flush(); + pending.resolve({outcome: 'bound', shortcuts: listedAll()}); + await Promise.all([first, second]); + assert.equal(harness.bindCalls.length, 1); + assert.equal(harness.manager.getState(), 'bound'); + }); + + test('set up during the startup bind joins it', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + const pending = deferred(); + harness.bindResults.push(pending.promise); + const startup = harness.manager.activate(); + await flush(); + assert.equal(harness.manager.getState(), 'binding'); + const setUp = harness.manager.setUp(); + pending.resolve({outcome: 'bound', shortcuts: listedAll()}); + await Promise.all([startup, setUp]); + assert.equal(harness.bindCalls.length, 1); + }); + + test('a session loss during a pending bind waits for the bind to settle', async () => { + const harness = createHarness({consent: 'unset'}); + const pending = deferred(); + harness.bindResults.push(pending.promise); + await harness.manager.activate(); + await harness.manager.probe(); + const setUp = harness.manager.setUp(); + await flush(); + const portal = harness.latestPortal(); + portal.emit({type: 'session-lost', reason: 'portal-restarted'}); + assert.equal(portal.closed, false); + assert.equal(harness.timers.length, 0); + pending.resolve({outcome: 'bound', shortcuts: listedAll()}); + await setUp; + assert.equal(portal.closed, true); + assert.equal(harness.consent, 'granted'); + assert.equal(await harness.runTimer(), PORTAL_RECOVERY_BACKOFF_MS[0]); + assert.equal(harness.manager.getState(), 'bound'); + assert.equal(harness.bindCalls.length, 2); + }); + + test('a second close during a recovery bind ends in the session-closed error', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + harness.latestPortal().emit({type: 'session-lost', reason: 'closed'}); + const pending = deferred(); + harness.bindResults.push(pending.promise); + const timer = harness.timers.find((entry) => !entry.cancelled && !entry.ran); + timer.ran = true; + timer.callback(); + await flush(); + harness.clock += 1000; + harness.latestPortal().emit({type: 'session-lost', reason: 'closed'}); + pending.resolve({outcome: 'bound', shortcuts: listedAll()}); + await harness.manager.probe(); + await flush(); + assert.equal(harness.manager.getState(), 'error'); + assert.equal(harness.manager.getStatus().error, 'session-closed'); + assert.equal(harness.timers.filter((entry) => !entry.cancelled && !entry.ran).length, 0); + }); + + test('a rejected bind closes and reopens so the next set up works', async () => { + const harness = createHarness({consent: 'unset'}); + harness.bindResults.push(new Error('error:no-session')); + await harness.manager.activate(); + await harness.manager.probe(); + await harness.manager.setUp(); + assert.equal(harness.manager.getState(), 'error'); + assert.equal(harness.manager.getStatus().error, 'bind-failed'); + assert.equal(harness.portals[0].closed, true); + assert.equal(harness.portals.length, 2); + await harness.manager.setUp(); + assert.equal(harness.portals.length, 2); + assert.equal(harness.portals[1].bindCalls.length, 1); + assert.equal(harness.manager.getState(), 'bound'); + }); +}); + +describe('LinuxPortalShortcutsManager configure and triggers', () => { + test('a session loss that lands before the open continuation schedules recovery', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + harness.openResults.push((portal) => { + portal.emit({type: 'session-lost', reason: 'portal-restarted'}); + return harness.defaultOpen; + }); + await harness.manager.activate(); + assert.equal(harness.portals[0].closed, true); + assert.equal(harness.bindCalls.length, 0); + assert.equal(harness.manager.getStatus().recovering, true); + assert.equal(harness.manager.getStatus().canConfigure, false); + assert.equal(await harness.runTimer(), PORTAL_RECOVERY_BACKOFF_MS[0]); + assert.equal(harness.portals.length, 2); + assert.equal(harness.portals[1].bindCalls.length, 1); + assert.equal(harness.manager.getState(), 'bound'); + assert.equal(harness.manager.getStatus().recovering, false); + }); + + test('a session loss from a replaced portal during a reopen is ignored', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + const stale = harness.latestPortal(); + harness.latestPortal().emit({type: 'session-lost', reason: 'portal-restarted'}); + harness.openResults.push(() => { + stale.emit({type: 'session-lost', reason: 'bus-error'}); + return harness.defaultOpen; + }); + await harness.runTimer(); + assert.equal(harness.manager.getState(), 'bound'); + assert.equal(harness.manager.getStatus().recovering, false); + assert.equal(harness.latestPortal().closed, false); + }); + + test('a recovery reopen that lists nothing keeps publishing the last triggers until the bind lands', async () => { + const harness = createHarness({consent: 'granted', listed: []}); + harness.bindResults.push({outcome: 'bound', shortcuts: listedAll('F13')}); + await harness.manager.activate(); + assert.equal(harness.manager.getState(), 'bound'); + const published = []; + const original = harness.manager.deps.onStatusChanged; + harness.manager.deps.onStatusChanged = () => { + published.push(harness.manager.getStatus().shortcuts.map((entry) => entry.triggerDescription)); + original(); + }; + harness.latestPortal().emit({type: 'session-lost', reason: 'portal-restarted'}); + const pending = deferred(); + harness.bindResults.push(pending.promise); + const timer = harness.timers.find((entry) => !entry.cancelled && !entry.ran); + timer.ran = true; + timer.callback(); + await flush(); + assert.equal(harness.manager.getState(), 'binding'); + assert.ok(published.length > 0); + assert.ok(published.every((triggers) => triggers.every((trigger) => trigger === 'F13'))); + pending.resolve({outcome: 'bound', shortcuts: listedAll('F14')}); + await harness.manager.probe(); + await flush(); + const status = harness.manager.getStatus(); + assert.equal(status.state, 'bound'); + assert.equal(status.recovering, false); + assert.ok(status.shortcuts.every((entry) => entry.triggerDescription === 'F14')); + }); + + test('a recovery that ends without a bind drops the retained triggers', async () => { + const harness = createHarness({consent: 'granted', listed: []}); + harness.bindResults.push({outcome: 'bound', shortcuts: listedAll('F13')}); + await harness.manager.activate(); + harness.latestPortal().emit({type: 'session-lost', reason: 'portal-restarted'}); + harness.bindResults.push({outcome: 'cancelled'}); + await harness.runTimer(); + const status = harness.manager.getStatus(); + assert.equal(status.state, 'declined'); + assert.ok(status.shortcuts.every((entry) => entry.triggerDescription === null)); + }); + + test('try again that binds with no triggers opens the system settings on version 2', async () => { + const harness = createHarness({consent: 'declined'}); + harness.bindResults.push({outcome: 'bound', shortcuts: listedAll('')}); + await harness.manager.activate(); + await harness.manager.probe(); + await harness.manager.setUp(); + assert.equal(harness.manager.getState(), 'bound'); + assert.deepEqual(harness.configureCalls, ['x11:2a']); + }); + + test('no configure call on version 1 or Hyprland', async () => { + for (const options of [ + {version: 1, desktop: 'gnome'}, + {version: 2, desktop: 'hyprland'}, + ]) { + const harness = createHarness({consent: 'declined', ...options}); + harness.bindResults.push({outcome: 'bound', shortcuts: listedAll('')}); + await harness.manager.activate(); + await harness.manager.probe(); + await harness.manager.setUp(); + assert.deepEqual(harness.configureCalls, [], options.desktop); + } + }); + + test('a transient configure failure keeps the button', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + harness.configureResults.push(new Error('error:timeout'), new Error('error:bus')); + await harness.manager.activate(); + await harness.manager.configure(); + await harness.manager.configure(); + assert.equal(harness.manager.getStatus().canConfigure, true); + await harness.manager.configure(); + assert.equal(harness.configureCalls.length, 3); + }); + + test('a configure failure falls back to instructions', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + harness.configureResults.push(new Error('unsupported:version')); + await harness.manager.activate(); + assert.equal(harness.manager.getStatus().canConfigure, true); + await harness.manager.configure(); + assert.equal(harness.manager.getStatus().canConfigure, false); + await harness.manager.configure(); + assert.equal(harness.configureCalls.length, 1); + }); + + test('a trigger change on a held shortcut releases it', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + const portal = harness.latestPortal(); + portal.emit({type: 'activated', id: 'voice_push_to_talk'}); + portal.emit({type: 'activated', id: 'voice_toggle_mute'}); + portal.emit({ + type: 'shortcuts-changed', + shortcuts: [{id: 'voice_push_to_talk', description: null, triggerDescription: 'F14'}], + }); + assert.deepEqual(harness.shortcuts, [ + 'press:voice_push_to_talk', + 'press:voice_toggle_mute', + 'release:voice_push_to_talk', + ]); + portal.emit({type: 'deactivated', id: 'voice_push_to_talk'}); + assert.equal(harness.shortcuts.length, 3); + }); + + test('a repeated activation of a toggle after a lost deactivation is a new press', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + const portal = harness.latestPortal(); + portal.emit({type: 'activated', id: 'voice_toggle_mute'}); + harness.clock += PORTAL_TOGGLE_REARM_MS + 1; + portal.emit({type: 'activated', id: 'voice_toggle_mute'}); + portal.emit({type: 'deactivated', id: 'voice_toggle_mute'}); + portal.emit({type: 'deactivated', id: 'voice_toggle_mute'}); + assert.deepEqual(harness.shortcuts, [ + 'press:voice_toggle_mute', + 'release:voice_toggle_mute', + 'press:voice_toggle_mute', + 'release:voice_toggle_mute', + ]); + }); + + test('autorepeat of a held toggle never re-arms it', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + const portal = harness.latestPortal(); + for (let index = 0; index < 10; index += 1) { + portal.emit({type: 'activated', id: 'voice_toggle_deafen'}); + harness.clock += index === 0 ? 500 : 30; + } + harness.clock += 2000; + portal.emit({type: 'deactivated', id: 'voice_toggle_deafen'}); + assert.deepEqual(harness.shortcuts, ['press:voice_toggle_deafen', 'release:voice_toggle_deafen']); + }); + + test('a hold shortcut never re-arms on a repeated activation', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + const portal = harness.latestPortal(); + portal.emit({type: 'activated', id: 'voice_push_to_talk'}); + harness.clock += 10 * PORTAL_TOGGLE_REARM_MS; + portal.emit({type: 'activated', id: 'voice_push_to_talk'}); + assert.deepEqual(harness.shortcuts, ['press:voice_push_to_talk']); + }); + + test('a press after releaseAll is not mistaken for a repeat', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + const portal = harness.latestPortal(); + portal.emit({type: 'activated', id: 'voice_push_to_talk'}); + harness.manager.releaseAll(); + portal.emit({type: 'activated', id: 'voice_push_to_talk'}); + assert.deepEqual(harness.shortcuts, [ + 'press:voice_push_to_talk', + 'release:voice_push_to_talk', + 'press:voice_push_to_talk', + ]); + }); + + test('releaseAll releases held shortcuts and ignores their later deactivation', async () => { + const harness = createHarness({consent: 'granted', listed: listedAll()}); + await harness.manager.activate(); + const portal = harness.latestPortal(); + portal.emit({type: 'activated', id: 'voice_push_to_talk'}); + harness.manager.releaseAll(); + portal.emit({type: 'deactivated', id: 'voice_push_to_talk'}); + assert.deepEqual(harness.shortcuts, ['press:voice_push_to_talk', 'release:voice_push_to_talk']); + }); +}); diff --git a/fluxer_desktop/src/main/LinuxGlobalShortcutsPortal.ts b/fluxer_desktop/src/main/LinuxGlobalShortcutsPortal.ts new file mode 100644 index 000000000..51ac4a140 --- /dev/null +++ b/fluxer_desktop/src/main/LinuxGlobalShortcutsPortal.ts @@ -0,0 +1,592 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import type {GlobalShortcutsPortalConsent} from '@electron/common/DesktopConfig'; +import { + GLOBAL_SHORTCUT_ACTIONS, + GLOBAL_SHORTCUT_HOLD_ACTIONS, + type GlobalShortcutAction, +} from '@electron/common/GlobalShortcutActions'; +import type {GlobalShortcutsPortalState, GlobalShortcutsPortalStatus, LinuxDesktopKind} from '@electron/common/Types'; + +export interface PortalShortcutDefinition { + id: string; + description: string; + preferredTrigger?: string | null; +} + +export interface PortalShortcutBinding { + id: string; + description: string | null; + triggerDescription: string | null; +} + +export type PortalEvent = + | {type: 'activated'; id: string} + | {type: 'deactivated'; id: string} + | {type: 'shortcuts-changed'; shortcuts: Array} + | {type: 'session-lost'; reason: PortalSessionLostReason} + | {type: 'portal-available'}; + +type PortalSessionLostReason = 'closed' | 'portal-restarted' | 'bus-error'; + +export interface PortalClientOptions { + portalAppId: string | null; + sandboxed: boolean; + sessionToken: string; + desktop: LinuxDesktopKind; +} + +export interface PortalOpenResult { + version: number; + appIdSource: 'sandbox' | 'registered' | 'unregistered'; + uniqueName: string; + listed: Array; +} + +export type PortalBindOutcome = + | {outcome: 'bound'; shortcuts: Array} + | {outcome: 'cancelled'} + | {outcome: 'denied'} + | {outcome: 'failed'; code: number}; + +export interface PortalClient { + open(): Promise; + bind(shortcuts: Array, parentWindow: string): Promise; + configure(parentWindow: string): Promise; + close(): void; +} + +export interface PortalTimer { + cancel(): void; +} + +export interface LinuxPortalShortcutsDeps { + createPortal: (onEvent: (event: PortalEvent) => void) => PortalClient; + desktop: LinuxDesktopKind; + plasma5: boolean; + portalAppId: string | null; + getConsent: () => GlobalShortcutsPortalConsent; + setConsent: (consent: GlobalShortcutsPortalConsent) => void; + getDefinitions: () => Array; + getParentWindow: () => string; + onShortcut: (action: GlobalShortcutAction, phase: 'press' | 'release') => void; + onStatusChanged: () => void; + schedule: (callback: () => void, delayMs: number) => PortalTimer; + now: () => number; + log: (message: string, details?: Record) => void; +} + +export const PORTAL_RECOVERY_BACKOFF_MS: ReadonlyArray = [1000, 2000, 5000, 10000, 30000]; +export const PORTAL_NO_PORTAL_RETRY_WINDOW_MS = 30000; +export const PORTAL_TOGGLE_REARM_MS = 1500; +const PORTAL_CLOSED_RECOVERY_WINDOW_MS = 60000; +const NO_PORTAL_ERROR = 'unsupported:no-portal'; +const CONFIGURE_UNSUPPORTED_ERROR = 'unsupported:version'; +const RESPONSE_CODE_ERROR = /^unsupported:\d+$/; + +type OpenFailure = {kind: 'no-portal' | 'unsupported' | 'identity' | 'timeout' | 'failed'; message: string}; + +function classifyOpenFailure(error: unknown): OpenFailure { + const message = error instanceof Error ? error.message : String(error); + if (message === NO_PORTAL_ERROR || RESPONSE_CODE_ERROR.test(message)) return {kind: 'no-portal', message}; + if (message.startsWith('unsupported:')) return {kind: 'unsupported', message}; + if (message.startsWith('identity:')) return {kind: 'identity', message}; + if (message === 'error:timeout') return {kind: 'timeout', message}; + return {kind: 'failed', message}; +} + +function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + +function isKnownAction(id: string): id is GlobalShortcutAction { + return (GLOBAL_SHORTCUT_ACTIONS as ReadonlyArray).includes(id); +} + +function normalizeTrigger(value: string | null): string | null { + return value !== null && value.trim().length > 0 ? value : null; +} + +export class LinuxPortalShortcutsManager { + private state: GlobalShortcutsPortalState = 'unknown'; + private error: string | null = null; + private version: number | null = null; + private registered = true; + private configureUnsupported = false; + private readonly triggers = new Map(); + private readonly listedActions = new Set(); + private retainedTriggers = false; + private readonly activeIds = new Set(); + private readonly lastActivatedAt = new Map(); + private portal: PortalClient | null = null; + private opened = false; + private generation = 0; + private active = false; + private queue: Promise = Promise.resolve(); + private recoveryTimer: PortalTimer | null = null; + private recoveryAttempt = 0; + private retry: 'session' | 'startup' | null = null; + private recovering = false; + private retrySince: number | null = null; + private lastClosedRecoveryAt: number | null = null; + private binding = false; + private pendingLoss: PortalSessionLostReason | null = null; + private setUpTask: Promise | null = null; + + constructor(private readonly deps: LinuxPortalShortcutsDeps) {} + + getState(): GlobalShortcutsPortalState { + return this.state; + } + + getStatus(): GlobalShortcutsPortalStatus { + return { + state: this.state, + version: this.version, + canConfigure: + this.state === 'bound' && + this.opened && + !this.recovering && + this.version !== null && + this.version >= 2 && + !this.configureUnsupported, + recovering: this.recovering, + canRecheck: !this.deps.plasma5 && (this.state === 'unsupported' || this.state === 'error'), + portalAppId: this.registered ? this.deps.portalAppId : null, + shortcuts: GLOBAL_SHORTCUT_ACTIONS.map((action) => ({ + action, + triggerDescription: this.triggers.get(action) ?? null, + })), + error: this.error, + }; + } + + activate(): Promise { + if (this.active) return this.queue.then(() => undefined); + this.active = true; + if (this.deps.plasma5) { + this.setState('unsupported', null); + return Promise.resolve(); + } + if (this.deps.getConsent() !== 'granted') return Promise.resolve(); + return this.enqueue(async () => { + if (!this.active) return; + await this.runStartupSequence(); + }); + } + + probe(): Promise { + if (!this.active || this.state !== 'unknown') return this.queue.then(() => undefined); + return this.enqueue(async () => { + if (!this.active || this.state !== 'unknown') return; + await this.runStartupSequence(); + }); + } + + deactivate(): void { + if (!this.active) return; + this.active = false; + this.cancelRecovery(); + this.resetRecovery(); + this.pendingLoss = null; + this.closePortal(); + this.clearTriggers(); + this.lastActivatedAt.clear(); + this.version = null; + this.setState('unknown', null); + } + + releaseAll(): void { + for (const action of [...this.activeIds]) { + this.activeIds.delete(action); + this.deps.onShortcut(action, 'release'); + } + } + + recheck(): Promise { + if (!this.active || this.deps.plasma5) return this.queue.then(() => undefined); + return this.enqueue(async () => { + if (!this.active || (this.state !== 'unsupported' && this.state !== 'error')) return; + this.cancelRecovery(); + this.resetRecovery(); + this.lastClosedRecoveryAt = null; + this.setState('probing', null); + await this.runStartupSequence(); + }); + } + + setUp(): Promise { + if (!this.active || this.deps.plasma5) return this.queue.then(() => undefined); + if (this.state === 'unsupported') return this.recheck(); + if (this.setUpTask !== null) return this.setUpTask; + if (this.binding) return this.queue.then(() => undefined); + const task = this.enqueue(async () => { + if (!this.active) return; + this.cancelRecovery(); + this.resetRecovery(); + this.lastClosedRecoveryAt = null; + if (!this.opened && !(await this.openSession())) return; + const outcome = await this.bind(); + if (outcome?.outcome === 'bound' && this.everyTriggerEmpty() && this.deps.desktop !== 'hyprland') { + await this.runConfigure(); + } + }); + this.setUpTask = task; + void task.finally(() => { + if (this.setUpTask === task) this.setUpTask = null; + }); + return task; + } + + configure(): Promise { + return this.enqueue(async () => { + if (!this.active || !this.opened || this.recovering || this.portal === null) return; + await this.runConfigure(); + }); + } + + private async runConfigure(): Promise { + const portal = this.portal; + if (portal === null || this.version === null || this.version < 2 || this.configureUnsupported) return; + try { + await portal.configure(this.deps.getParentWindow()); + } catch (error) { + const message = errorMessage(error); + this.deps.log('Global shortcuts portal configure failed', {message}); + if (message !== CONFIGURE_UNSUPPORTED_ERROR) return; + this.configureUnsupported = true; + this.deps.onStatusChanged(); + } + } + + private everyTriggerEmpty(): boolean { + return GLOBAL_SHORTCUT_ACTIONS.every((action) => (this.triggers.get(action) ?? null) === null); + } + + private async runStartupSequence(): Promise { + await this.runStartupSteps(); + if (!this.recovering || this.retry !== null) return; + this.recovering = false; + this.deps.onStatusChanged(); + } + + private async runStartupSteps(): Promise { + if (!(await this.openSession())) return; + const consent = this.deps.getConsent(); + if (consent === 'declined') { + this.setState('declined', null); + return; + } + if (consent === 'unset') { + this.setState('not-set-up', null); + return; + } + const version = this.version ?? 0; + if (version < 2) { + if (GLOBAL_SHORTCUT_ACTIONS.every((action) => this.listedActions.has(action))) { + this.setState('bound', null); + return; + } + if (this.deps.desktop === 'kde' && this.listedActions.size === 0) { + this.setState('not-set-up', null); + return; + } + } + await this.bind(); + } + + private async openSession(): Promise { + this.closePortal(); + this.pendingLoss = null; + const generation = ++this.generation; + if (this.state === 'unknown') this.setState('probing', null); + let result: PortalOpenResult; + try { + const portal = this.deps.createPortal((event) => this.handleEvent(generation, event)); + this.portal = portal; + result = await portal.open(); + } catch (error) { + if (generation !== this.generation) return false; + this.pendingLoss = null; + this.handleOpenFailure(classifyOpenFailure(error)); + return false; + } + if (generation !== this.generation) return false; + const lost = this.pendingLoss; + this.pendingLoss = null; + if (lost !== null) { + this.handleSessionLost(lost); + return false; + } + this.deps.log('Global shortcuts portal opened', { + version: result.version, + appIdSource: result.appIdSource, + uniqueName: result.uniqueName, + }); + this.opened = true; + this.version = result.version; + this.registered = result.appIdSource !== 'unregistered'; + this.configureUnsupported = false; + const recovering = this.recovering; + this.resetRecovery(); + this.recovering = recovering; + this.listedActions.clear(); + for (const binding of result.listed) { + if (isKnownAction(binding.id)) this.listedActions.add(binding.id); + } + if (!this.retainedTriggers || this.listedActions.size > 0) this.replaceTriggers(result.listed); + this.deps.onStatusChanged(); + return true; + } + + private handleOpenFailure(failure: OpenFailure): void { + this.deps.log('Global shortcuts portal open failed', {kind: failure.kind, message: failure.message}); + if (failure.kind === 'identity' || failure.kind === 'unsupported') { + this.closePortal(); + this.cancelRecovery(); + this.resetRecovery(); + if (failure.kind === 'identity') this.setState('error', 'identity'); + else this.setState('unsupported', null); + this.deps.onStatusChanged(); + return; + } + if (this.retry === 'session') { + if (this.recovering && this.recoveryAttempt >= PORTAL_RECOVERY_BACKOFF_MS.length) { + this.recovering = false; + this.setState('error', 'portal-unavailable'); + this.deps.onStatusChanged(); + } + this.scheduleRecovery(); + return; + } + const now = this.deps.now(); + this.retry = 'startup'; + this.retrySince ??= now; + const remaining = this.retrySince + PORTAL_NO_PORTAL_RETRY_WINDOW_MS - now; + const retryable = failure.kind === 'no-portal' || this.deps.getConsent() === 'granted'; + this.recovering = retryable && remaining > 0; + if (failure.kind === 'no-portal') this.setState('unsupported', null); + else this.setState('error', failure.kind === 'timeout' ? 'timeout' : 'open-failed'); + this.deps.onStatusChanged(); + if (this.recovering) this.scheduleRecovery(remaining); + } + + private async bind(): Promise { + const portal = this.portal; + if (portal === null) return null; + const generation = this.generation; + this.setState('binding', null); + this.binding = true; + let outcome: PortalBindOutcome | null = null; + let failure: unknown = null; + try { + outcome = await portal.bind(this.deps.getDefinitions(), this.deps.getParentWindow()); + } catch (error) { + failure = error; + } finally { + this.binding = false; + } + const lost = this.pendingLoss; + this.pendingLoss = null; + if (generation !== this.generation) return null; + if (outcome === null) { + this.deps.log('Global shortcuts portal bind failed', {message: errorMessage(failure)}); + if (lost !== null) { + this.handleSessionLost(lost); + return null; + } + this.closePortal(); + this.setState('error', 'bind-failed'); + await this.openSession(); + return null; + } + this.applyBindOutcome(outcome); + if (lost !== null) this.handleSessionLost(lost); + return outcome; + } + + private applyBindOutcome(outcome: PortalBindOutcome): void { + switch (outcome.outcome) { + case 'bound': + this.replaceTriggers(outcome.shortcuts); + this.deps.setConsent('granted'); + this.setState('bound', null); + return; + case 'cancelled': + case 'denied': + this.deps.setConsent('declined'); + this.setState('declined', null); + return; + case 'failed': + this.deps.log('Global shortcuts portal bind returned a failure response', {code: outcome.code}); + this.setState('error', 'bind-failed'); + return; + } + } + + private handleEvent(generation: number, event: PortalEvent): void { + if (generation !== this.generation) return; + switch (event.type) { + case 'activated': + this.handleActivated(event.id); + return; + case 'deactivated': + if (!isKnownAction(event.id) || !this.activeIds.delete(event.id)) return; + this.deps.onShortcut(event.id, 'release'); + return; + case 'shortcuts-changed': + this.handleShortcutsChanged(event.shortcuts); + return; + case 'session-lost': + if (!this.opened) { + if (this.portal !== null) this.pendingLoss ??= event.reason; + return; + } + if (this.binding) { + this.pendingLoss ??= event.reason; + return; + } + this.handleSessionLost(event.reason); + return; + case 'portal-available': + this.handlePortalAvailable(); + return; + } + } + + private handleActivated(id: string): void { + if (!isKnownAction(id)) return; + const now = this.deps.now(); + const previous = this.lastActivatedAt.get(id); + this.lastActivatedAt.set(id, now); + if (this.activeIds.has(id)) { + if (GLOBAL_SHORTCUT_HOLD_ACTIONS.has(id)) return; + if (previous !== undefined && now - previous < PORTAL_TOGGLE_REARM_MS) return; + this.deps.onShortcut(id, 'release'); + } else { + this.activeIds.add(id); + } + this.deps.onShortcut(id, 'press'); + } + + private handleShortcutsChanged(shortcuts: ReadonlyArray): void { + const previous = new Map(this.triggers); + if (this.retainedTriggers) this.replaceTriggers(shortcuts); + else this.applyBindings(shortcuts); + for (const action of [...this.activeIds]) { + const trigger = this.triggers.get(action) ?? null; + if (trigger !== null && trigger === (previous.get(action) ?? null)) continue; + this.activeIds.delete(action); + this.deps.onShortcut(action, 'release'); + } + this.deps.onStatusChanged(); + } + + private handlePortalAvailable(): void { + if (!this.active || this.opened || this.error === 'identity') return; + if (this.state === 'unsupported' && this.retrySince === null) return; + this.deps.log('Global shortcuts portal appeared on the bus'); + this.cancelRecovery(); + void this.enqueue(async () => { + if (!this.active || this.opened) return; + await this.runStartupSequence(); + }); + } + + private handleSessionLost(reason: PortalSessionLostReason): void { + this.deps.log('Global shortcuts portal session lost', {reason}); + this.closePortal(); + this.retainedTriggers = this.triggers.size > 0; + if (!this.active) return; + if (reason === 'closed') { + const now = this.deps.now(); + const recentlyRecovered = + this.lastClosedRecoveryAt !== null && now - this.lastClosedRecoveryAt < PORTAL_CLOSED_RECOVERY_WINDOW_MS; + if (recentlyRecovered) { + this.cancelRecovery(); + this.resetRecovery(); + this.setState('error', 'session-closed'); + this.deps.onStatusChanged(); + return; + } + this.lastClosedRecoveryAt = now; + } + this.retry = 'session'; + this.recovering = true; + this.deps.onStatusChanged(); + this.scheduleRecovery(); + } + + private scheduleRecovery(maxDelayMs = Number.POSITIVE_INFINITY): void { + this.cancelRecovery(); + const index = Math.min(this.recoveryAttempt, PORTAL_RECOVERY_BACKOFF_MS.length - 1); + const delay = Math.min(PORTAL_RECOVERY_BACKOFF_MS[index] ?? 30000, maxDelayMs); + this.recoveryAttempt += 1; + this.recoveryTimer = this.deps.schedule(() => { + this.recoveryTimer = null; + void this.enqueue(async () => { + if (!this.active || this.opened) return; + await this.runStartupSequence(); + }); + }, delay); + } + + private cancelRecovery(): void { + this.recoveryTimer?.cancel(); + this.recoveryTimer = null; + } + + private resetRecovery(): void { + this.retry = null; + this.recovering = false; + this.recoveryAttempt = 0; + this.retrySince = null; + } + + private replaceTriggers(bindings: ReadonlyArray): void { + this.clearTriggers(); + this.applyBindings(bindings); + } + + private clearTriggers(): void { + this.triggers.clear(); + this.retainedTriggers = false; + } + + private applyBindings(bindings: ReadonlyArray): void { + for (const binding of bindings) { + if (!isKnownAction(binding.id)) continue; + this.triggers.set(binding.id, normalizeTrigger(binding.triggerDescription)); + } + } + + private closePortal(): void { + this.generation += 1; + this.opened = false; + this.releaseAll(); + const portal = this.portal; + this.portal = null; + if (portal === null) return; + try { + portal.close(); + } catch (error) { + this.deps.log('Global shortcuts portal close threw', {message: errorMessage(error)}); + } + } + + private setState(state: GlobalShortcutsPortalState, error: string | null): void { + if (this.state === state && this.error === error) return; + if (this.retainedTriggers && state !== 'bound' && state !== 'binding') this.clearTriggers(); + this.state = state; + this.error = error; + this.deps.onStatusChanged(); + } + + private enqueue(task: () => Promise): Promise { + const run = this.queue.then(task); + this.queue = run.catch((error: unknown) => { + this.deps.log('Global shortcuts portal task failed', {message: errorMessage(error)}); + }); + return this.queue.then(() => undefined); + } +} diff --git a/fluxer_desktop/src/main/LinuxInputAccess.test.mjs b/fluxer_desktop/src/main/LinuxInputAccess.test.mjs deleted file mode 100644 index d44e1fd7a..000000000 --- a/fluxer_desktop/src/main/LinuxInputAccess.test.mjs +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -import assert from 'node:assert/strict'; -import {readFileSync} from 'node:fs'; -import {createRequire} from 'node:module'; -import {describe, test} from 'node:test'; -import {fileURLToPath} from 'node:url'; -import vm from 'node:vm'; - -const require = createRequire(import.meta.url); -const esbuild = require('esbuild'); - -const sourcePath = fileURLToPath(new URL('./LinuxInputAccess.ts', import.meta.url)); -const source = readFileSync(sourcePath, 'utf8'); -const transformedSource = esbuild.transformSync(source, { - loader: 'ts', - format: 'cjs', - platform: 'node', - target: 'node20', -}).code; - -function loadLinuxInputAccess() { - const module = {exports: {}}; - const context = vm.createContext({module, exports: module.exports}); - vm.runInContext(transformedSource, context, {filename: sourcePath}); - return module.exports; -} - -const {isFullKeyboardKeyCapabilities, hasLinuxEvdevAccess} = loadLinuxInputAccess(); - -function probe(overrides) { - return { - totalEventDevices: 4, - readableEventDevices: 0, - keyboardDevices: 0, - readableKeyboardDevices: 0, - inInputGroup: false, - ...overrides, - }; -} - -describe('isFullKeyboardKeyCapabilities', () => { - test('accepts a real keyboard bitmap', () => { - assert.equal( - isFullKeyboardKeyCapabilities( - '120013 0 0 0 0 0 0 0 0 1000000000007 ff9f207ac14057ff febeffdfffefffff fffffffffffffffe', - ), - true, - ); - }); - - test('accepts a keyboard bitmap from a 32-bit kernel', () => { - assert.equal(isFullKeyboardKeyCapabilities('ffefffff fffffffe'), true); - }); - - test('rejects a mouse bitmap', () => { - assert.equal(isFullKeyboardKeyCapabilities('1f0000 0 0 0 0'), false); - }); - - test('rejects a gamepad bitmap', () => { - assert.equal(isFullKeyboardKeyCapabilities('7fdb000000000000 0 0 0 0'), false); - }); - - test('rejects a media-key-only bitmap', () => { - assert.equal(isFullKeyboardKeyCapabilities('10000 0 0 0 1c000000000000 0'), false); - }); - - test('rejects empty and malformed input', () => { - assert.equal(isFullKeyboardKeyCapabilities(''), false); - assert.equal(isFullKeyboardKeyCapabilities('zz'), false); - }); -}); - -describe('hasLinuxEvdevAccess', () => { - test('a readable gamepad does not count when the keyboard is unreadable', () => { - assert.equal(hasLinuxEvdevAccess(probe({readableEventDevices: 1, keyboardDevices: 1})), false); - }); - - test('every keyboard must be readable', () => { - assert.equal( - hasLinuxEvdevAccess(probe({readableEventDevices: 3, keyboardDevices: 2, readableKeyboardDevices: 1})), - false, - ); - assert.equal( - hasLinuxEvdevAccess(probe({readableEventDevices: 4, keyboardDevices: 2, readableKeyboardDevices: 2})), - true, - ); - }); - - test('input group membership grants access', () => { - assert.equal(hasLinuxEvdevAccess(probe({keyboardDevices: 1, inInputGroup: true})), true); - }); - - test('falls back to any readable device when no keyboard is detected', () => { - assert.equal(hasLinuxEvdevAccess(probe({readableEventDevices: 1})), true); - assert.equal(hasLinuxEvdevAccess(probe({})), false); - }); -}); diff --git a/fluxer_desktop/src/main/LinuxInputAccess.ts b/fluxer_desktop/src/main/LinuxInputAccess.ts deleted file mode 100644 index e2686a04d..000000000 --- a/fluxer_desktop/src/main/LinuxInputAccess.ts +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-or-later - -const FULL_KEYBOARD_MASK = 0xfffffffe; - -export interface LinuxEvdevAccessProbe { - totalEventDevices: number; - readableEventDevices: number; - keyboardDevices: number; - readableKeyboardDevices: number; - inInputGroup: boolean; -} - -export function isFullKeyboardKeyCapabilities(keyCapabilities: string): boolean { - const words = keyCapabilities.trim().split(/\s+/); - const lowestWord = words[words.length - 1] ?? ''; - const lowestBits = Number.parseInt(lowestWord.slice(-8), 16); - if (!Number.isFinite(lowestBits)) return false; - return (lowestBits & FULL_KEYBOARD_MASK) >>> 0 === FULL_KEYBOARD_MASK; -} - -export function hasLinuxEvdevAccess(probe: LinuxEvdevAccessProbe): boolean { - if (probe.inInputGroup) return true; - if (probe.keyboardDevices > 0) return probe.readableKeyboardDevices === probe.keyboardDevices; - return probe.readableEventDevices > 0; -} diff --git a/fluxer_desktop/src/main/LinuxSandbox.ts b/fluxer_desktop/src/main/LinuxSandbox.ts index f4ece3290..a235ca8f0 100644 --- a/fluxer_desktop/src/main/LinuxSandbox.ts +++ b/fluxer_desktop/src/main/LinuxSandbox.ts @@ -1,13 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import fs from 'node:fs'; +import {LINUX_DESKTOP_ENTRY_ID} from '@electron/common/DesktopIdentity'; const FLATPAK_INFO_PATH = '/.flatpak-info'; export function isFlatpakRuntime(): boolean { if (process.platform !== 'linux') return false; - if (process.env.container === 'flatpak') return true; - if (process.env.FLATPAK_ID && process.env.FLATPAK_ID.length > 0) return true; try { return fs.existsSync(FLATPAK_INFO_PATH); } catch { @@ -15,8 +14,32 @@ export function isFlatpakRuntime(): boolean { } } +export function parseFlatpakInfoAppId(contents: string): string | null { + let inApplication = false; + for (const rawLine of contents.split('\n')) { + const line = rawLine.trim(); + if (line.startsWith('[')) { + inApplication = line === '[Application]'; + continue; + } + if (!inApplication || !line.startsWith('name=')) continue; + const name = line.slice('name='.length).trim(); + return name.length > 0 ? name : null; + } + return null; +} + export function getFlatpakAppId(): string | null { if (!isFlatpakRuntime()) return null; - const id = process.env.FLATPAK_ID?.trim(); - return id && id.length > 0 ? id : null; + const envId = process.env.FLATPAK_ID?.trim(); + if (envId && envId.length > 0) return envId; + try { + return parseFlatpakInfoAppId(fs.readFileSync(FLATPAK_INFO_PATH, 'utf8')); + } catch { + return null; + } +} + +export function getLinuxDesktopId(): string { + return getFlatpakAppId() ?? LINUX_DESKTOP_ENTRY_ID; } diff --git a/fluxer_desktop/src/main/LinuxSession.test.mjs b/fluxer_desktop/src/main/LinuxSession.test.mjs new file mode 100644 index 000000000..655ed8347 --- /dev/null +++ b/fluxer_desktop/src/main/LinuxSession.test.mjs @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import assert from 'node:assert/strict'; +import {describe, test} from 'node:test'; +import {loadTsModule} from './fixtures/TsModuleLoader.mjs'; + +const session = loadTsModule('@electron/main/LinuxSession', {stubs: {'node:fs': {existsSync: () => false}}}); +const sandbox = loadTsModule('@electron/main/LinuxSandbox', {stubs: {'node:fs': {existsSync: () => false}}}); + +describe('detectLinuxSessionType', () => { + const exists = (paths) => (candidate) => paths.includes(candidate); + + test('trusts XDG_SESSION_TYPE when it names x11 or wayland', () => { + assert.equal( + session.detectLinuxSessionType({XDG_SESSION_TYPE: 'x11', WAYLAND_DISPLAY: 'wayland-0'}, exists([])), + 'x11', + ); + assert.equal(session.detectLinuxSessionType({XDG_SESSION_TYPE: 'wayland'}, exists([])), 'wayland'); + }); + + test('otherwise requires the Wayland socket to exist', () => { + const env = { + XDG_SESSION_TYPE: 'tty', + WAYLAND_DISPLAY: 'wayland-0', + XDG_RUNTIME_DIR: '/run/user/1000', + DISPLAY: ':0', + }; + assert.equal(session.detectLinuxSessionType(env, exists(['/run/user/1000/wayland-0'])), 'wayland'); + assert.equal(session.detectLinuxSessionType(env, exists([])), 'x11'); + assert.equal(session.detectLinuxSessionType({WAYLAND_DISPLAY: '/tmp/wl'}, exists(['/tmp/wl'])), 'wayland'); + assert.equal(session.detectLinuxSessionType({}, exists([])), 'unknown'); + }); +}); + +describe('detectLinuxDesktop', () => { + test('scans every XDG_CURRENT_DESKTOP token', () => { + assert.equal(session.detectLinuxDesktop({XDG_CURRENT_DESKTOP: 'KDE'}), 'kde'); + assert.equal(session.detectLinuxDesktop({XDG_CURRENT_DESKTOP: 'GNOME'}), 'gnome'); + assert.equal(session.detectLinuxDesktop({XDG_CURRENT_DESKTOP: 'ubuntu:GNOME'}), 'gnome'); + assert.equal(session.detectLinuxDesktop({XDG_CURRENT_DESKTOP: 'zorin:GNOME'}), 'gnome'); + assert.equal(session.detectLinuxDesktop({XDG_CURRENT_DESKTOP: 'Custom:KDE'}), 'kde'); + assert.equal(session.detectLinuxDesktop({XDG_CURRENT_DESKTOP: 'Hyprland:GNOME'}), 'hyprland'); + assert.equal( + session.detectLinuxDesktop({XDG_CURRENT_DESKTOP: 'GNOME', HYPRLAND_INSTANCE_SIGNATURE: 'x'}), + 'hyprland', + ); + assert.equal(session.detectLinuxDesktop({XDG_CURRENT_DESKTOP: 'sway'}), 'other'); + assert.equal(session.detectLinuxDesktop({XDG_CURRENT_DESKTOP: 'Hyprland'}), 'hyprland'); + assert.equal(session.detectLinuxDesktop({HYPRLAND_INSTANCE_SIGNATURE: 'abc'}), 'hyprland'); + assert.equal(session.detectLinuxDesktop({}), 'other'); + }); + + test('detects Plasma 5 sessions', () => { + assert.equal(session.isKdePlasma5Session({KDE_SESSION_VERSION: '5'}), true); + assert.equal(session.isKdePlasma5Session({KDE_SESSION_VERSION: '6'}), false); + }); +}); + +describe('parseFlatpakInfoAppId', () => { + test('reads the application name', () => { + assert.equal( + sandbox.parseFlatpakInfoAppId('[Application]\nname=app.fluxer.FluxerCanary\nruntime=x\n[Instance]\nname=nope\n'), + 'app.fluxer.FluxerCanary', + ); + assert.equal(sandbox.parseFlatpakInfoAppId('[Instance]\nname=nope\n'), null); + }); +}); + +describe('getLinuxDesktopId', () => { + test('names the Flatpak app inside the sandbox and the host entry outside it', () => { + const platform = Object.getOwnPropertyDescriptor(process, 'platform'); + const previousId = process.env.FLATPAK_ID; + Object.defineProperty(process, 'platform', {...platform, value: 'linux'}); + try { + process.env.FLATPAK_ID = 'app.fluxer.Fluxer'; + const flatpak = loadTsModule('@electron/main/LinuxSandbox', {stubs: {'node:fs': {existsSync: () => true}}}); + assert.equal(flatpak.getLinuxDesktopId(), 'app.fluxer.Fluxer'); + const host = loadTsModule('@electron/main/LinuxSandbox', {stubs: {'node:fs': {existsSync: () => false}}}); + assert.equal(host.getLinuxDesktopId(), 'app.fluxer.FluxerDesktop'); + } finally { + Object.defineProperty(process, 'platform', platform); + if (previousId === undefined) delete process.env.FLATPAK_ID; + else process.env.FLATPAK_ID = previousId; + } + }); +}); diff --git a/fluxer_desktop/src/main/LinuxSession.ts b/fluxer_desktop/src/main/LinuxSession.ts new file mode 100644 index 000000000..ad15c81d8 --- /dev/null +++ b/fluxer_desktop/src/main/LinuxSession.ts @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import fs from 'node:fs'; +import type {LinuxDesktopKind, LinuxSessionType} from '@electron/common/Types'; + +type SessionEnv = Readonly>; + +export function detectLinuxSessionType(env: SessionEnv, pathExists: (candidate: string) => boolean): LinuxSessionType { + const sessionType = (env.XDG_SESSION_TYPE ?? '').trim().toLowerCase(); + if (sessionType === 'x11' || sessionType === 'wayland') return sessionType; + const waylandDisplay = (env.WAYLAND_DISPLAY ?? '').trim(); + if (waylandDisplay.length > 0) { + const runtimeDir = (env.XDG_RUNTIME_DIR ?? '').trim(); + const socketPath = waylandDisplay.startsWith('/') + ? waylandDisplay + : runtimeDir.length > 0 + ? `${runtimeDir.replace(/\/+$/, '')}/${waylandDisplay}` + : null; + if (socketPath !== null && pathExists(socketPath)) return 'wayland'; + } + if ((env.DISPLAY ?? '').trim().length > 0) return 'x11'; + return 'unknown'; +} + +export function detectLinuxDesktop(env: SessionEnv): LinuxDesktopKind { + const tokens = new Set((env.XDG_CURRENT_DESKTOP ?? '').split(':').map((token) => token.trim().toLowerCase())); + if (tokens.has('kde')) return 'kde'; + if (tokens.has('hyprland') || (env.HYPRLAND_INSTANCE_SIGNATURE ?? '').trim().length > 0) return 'hyprland'; + if (tokens.has('gnome')) return 'gnome'; + return 'other'; +} + +export function isKdePlasma5Session(env: SessionEnv): boolean { + return (env.KDE_SESSION_VERSION ?? '').trim() === '5'; +} + +function pathExists(candidate: string): boolean { + try { + return fs.existsSync(candidate); + } catch { + return false; + } +} + +export function getLinuxSessionType(): LinuxSessionType { + if (process.platform !== 'linux') return 'unknown'; + return detectLinuxSessionType(process.env, pathExists); +} + +export function isWaylandSession(): boolean { + return getLinuxSessionType() === 'wayland'; +} + +export function isX11Session(): boolean { + return getLinuxSessionType() === 'x11'; +} + +export function getLinuxDesktop(): LinuxDesktopKind { + return detectLinuxDesktop(process.env); +} diff --git a/fluxer_desktop/src/main/NotificationsIpc.ts b/fluxer_desktop/src/main/NotificationsIpc.ts index 745697ba9..414e2140d 100644 --- a/fluxer_desktop/src/main/NotificationsIpc.ts +++ b/fluxer_desktop/src/main/NotificationsIpc.ts @@ -1,10 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import {createRequire} from 'node:module'; -import {DESKTOP_APP_NAME, LINUX_DESKTOP_ENTRY_ID} from '@electron/common/DesktopIdentity'; +import {DESKTOP_APP_NAME} from '@electron/common/DesktopIdentity'; import {createChildLogger} from '@electron/common/Logger'; import type {NotificationOptions} from '@electron/common/Types'; import {getNativeNotificationsMode} from '@electron/main/LaunchOptions'; +import {getLinuxDesktopId} from '@electron/main/LinuxSandbox'; import {t} from '@electron/main/MainI18n'; import {resolveNotificationIcon} from '@electron/main/NotificationIcon'; import {shouldPlayNotificationSound} from '@electron/main/NotificationState'; @@ -286,7 +287,7 @@ async function showLinuxNativeNotification( hints: { urgency: 'normal', category: 'im', - desktopEntry: LINUX_DESKTOP_ENTRY_ID, + desktopEntry: getLinuxDesktopId(), suppressSound: true, transient: false, actionIcons: false, diff --git a/fluxer_desktop/src/main/PlatformInfo.test.mjs b/fluxer_desktop/src/main/PlatformInfo.test.mjs index a362db588..ff7674f45 100644 --- a/fluxer_desktop/src/main/PlatformInfo.test.mjs +++ b/fluxer_desktop/src/main/PlatformInfo.test.mjs @@ -54,6 +54,7 @@ function loadPlatformInfo(platform = 'win32', {nativeGpu = null, electronGpu = n if (specifier === '@electron/main/LinuxSandbox') { return {getFlatpakAppId: () => null, isFlatpakRuntime: () => false}; } + if (specifier === '@electron/main/LinuxSession') return {isWaylandSession: () => false}; throw new Error(`Unexpected import: ${specifier}`); } diff --git a/fluxer_desktop/src/main/PlatformInfo.ts b/fluxer_desktop/src/main/PlatformInfo.ts index 226b1c4b8..0e3ad4d29 100644 --- a/fluxer_desktop/src/main/PlatformInfo.ts +++ b/fluxer_desktop/src/main/PlatformInfo.ts @@ -13,6 +13,7 @@ import type { } from '@electron/common/Types'; import {isPortableMode} from '@electron/common/UserDataPath'; import {getFlatpakAppId, isFlatpakRuntime} from '@electron/main/LinuxSandbox'; +import {isWaylandSession} from '@electron/main/LinuxSession'; import {app} from 'electron'; const requireModule = createRequire(import.meta.url); @@ -143,9 +144,7 @@ export async function getDesktopInfo(options: DesktopInfoOptions = {}): Promise< electronVersion: process.versions.electron ?? 'unknown', chromeVersion: process.versions.chrome ?? 'unknown', nodeVersion: process.versions.node ?? 'unknown', - waylandSession: - process.platform === 'linux' && - (Boolean(process.env.WAYLAND_DISPLAY) || process.env.XDG_SESSION_TYPE === 'wayland'), + waylandSession: isWaylandSession(), portable: isPortableMode(), flatpak: isFlatpakRuntime(), flatpakAppId: getFlatpakAppId(), diff --git a/fluxer_desktop/src/main/fixtures/TsModuleLoader.mjs b/fluxer_desktop/src/main/fixtures/TsModuleLoader.mjs new file mode 100644 index 000000000..728776a2b --- /dev/null +++ b/fluxer_desktop/src/main/fixtures/TsModuleLoader.mjs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {readFileSync} from 'node:fs'; +import {createRequire} from 'node:module'; +import {fileURLToPath} from 'node:url'; +import vm from 'node:vm'; + +const require = createRequire(import.meta.url); +const esbuild = require('esbuild'); + +const sourceRoots = { + '@electron/main/': new URL('../', import.meta.url), + '@electron/common/': new URL('../../common/', import.meta.url), +}; + +function resolveSourcePath(specifier) { + for (const [prefix, root] of Object.entries(sourceRoots)) { + if (specifier.startsWith(prefix)) { + return fileURLToPath(new URL(`${specifier.slice(prefix.length)}.ts`, root)); + } + } + return null; +} + +export function loadTsModule(specifier, {stubs = {}} = {}) { + const cache = new Map(); + const load = (moduleSpecifier) => { + if (Object.hasOwn(stubs, moduleSpecifier)) return stubs[moduleSpecifier]; + const sourcePath = resolveSourcePath(moduleSpecifier); + if (sourcePath === null) throw new Error(`Unexpected import: ${moduleSpecifier}`); + const cached = cache.get(sourcePath); + if (cached) return cached.exports; + const transformed = esbuild.transformSync(readFileSync(sourcePath, 'utf8'), { + loader: 'ts', + format: 'cjs', + platform: 'node', + target: 'node20', + }).code; + const module = {exports: {}}; + cache.set(sourcePath, module); + const factory = vm.runInThisContext(`(function (require, module, exports) {${transformed}\n})`, { + filename: sourcePath, + }); + factory(load, module, module.exports); + return module.exports; + }; + return load(specifier); +} diff --git a/fluxer_desktop/src/main/index.ts b/fluxer_desktop/src/main/index.ts index 21cd5f084..d93dc5988 100644 --- a/fluxer_desktop/src/main/index.ts +++ b/fluxer_desktop/src/main/index.ts @@ -50,11 +50,12 @@ import { import {destroyDesktopTray, hasActiveDesktopTray, initializeDesktopTray} from '@electron/main/DesktopTray'; import {registerDisplayMediaHandlers} from '@electron/main/DisplayMedia'; import {initializeDockMenu} from '@electron/main/DockMenu'; -import {cleanupGlobalKeyHook, registerGlobalKeyHookHandlers} from '@electron/main/GlobalKeyHook'; +import {cleanupGlobalShortcuts, initializeGlobalShortcuts} from '@electron/main/GlobalShortcutsIpc'; import {cleanupIpcHandlers, registerIpcHandlers} from '@electron/main/IpcHandlers'; import {initializeJumpList} from '@electron/main/JumpList'; import {describeLaunchDiagnosticOptions} from '@electron/main/LaunchOptions'; import {cleanupVirtmic, registerVirtmicHandlers} from '@electron/main/LinuxAudioCapture'; +import {ensureLinuxDesktopEntry} from '@electron/main/LinuxDesktopEntry'; import {initializeMainI18n, t} from '@electron/main/MainI18n'; import {createApplicationMenu} from '@electron/main/Menu'; import {cleanupNativeAudio, registerNativeAudioHandlers} from '@electron/main/NativeAudio'; @@ -182,9 +183,6 @@ if (launchConfigurationError) { log.info('Launch diagnostic modes', launchDiagnosticOptions); const CHANNEL_APP_NAME = DESKTOP_APP_NAME; app.setName(CHANNEL_APP_NAME); - if (process.platform === 'linux') { - process.env.FLUXER_LINUX_DESKTOP_ENTRY_ID = LINUX_DESKTOP_ENTRY_ID; - } function recordStartupPhase(phase: string, phaseStartedAt: number): void { log.info('[Startup] Phase completed', { phase, @@ -315,6 +313,15 @@ if (launchConfigurationError) { } catch (error) { log.error('[DebugInfo] Failed to collect desktop debug info:', error); } + try { + runStartupPhase('linux-desktop-entry', () => { + if (ensureLinuxDesktopEntry()) { + process.env.FLUXER_LINUX_PORTAL_APP_ID = LINUX_DESKTOP_ENTRY_ID; + } + }); + } catch (error) { + log.error('[Init] Failed to ensure the Linux desktop entry:', error); + } try { runStartupPhase('deep-links', initializeDeepLinks); } catch (error) { @@ -341,9 +348,9 @@ if (launchConfigurationError) { log.error('[Init] Failed to register autostart handlers:', error); } try { - runStartupPhase('global-key-hook-handlers', registerGlobalKeyHookHandlers); + runStartupPhase('global-shortcuts', initializeGlobalShortcuts); } catch (error) { - log.error('[Init] Failed to register global key hook handlers:', error); + log.error('[Init] Failed to initialize global shortcuts:', error); } try { runStartupPhase('display-media-handlers', registerDisplayMediaHandlers); @@ -456,7 +463,7 @@ if (launchConfigurationError) { armQuitWatchdog('will-quit'); event.preventDefault(); cleanupIpcHandlers({quitting: true}); - cleanupGlobalKeyHook(); + cleanupGlobalShortcuts(); cleanupNativeAudio(); cleanupNativeScreenCapture(); cleanupNativeHardwareEncoderHandlers(); diff --git a/fluxer_desktop/src/preload/index.ts b/fluxer_desktop/src/preload/index.ts index 755748dbd..c26a4b6b5 100644 --- a/fluxer_desktop/src/preload/index.ts +++ b/fluxer_desktop/src/preload/index.ts @@ -20,6 +20,10 @@ import type { GlobalKeyEvent, GlobalKeyHookRegisterOptions, GlobalMouseEvent, + GlobalShortcutCaptureEvent, + GlobalShortcutEvent, + GlobalShortcutsStatus, + GlobalShortcutsSyncPayload, GpuInfo, InputMonitoringPermissionStatus, LinuxAppearanceSnapshot, @@ -635,6 +639,45 @@ const api: ElectronAPI = { ipcRenderer.removeListener('global-keybind-triggered', handler); }; }, + globalShortcuts: { + sync: (payload: GlobalShortcutsSyncPayload): Promise => ipcRenderer.invoke('global-shortcuts:sync', payload), + setPaused: (paused: boolean): Promise => ipcRenderer.invoke('global-shortcuts:set-paused', paused), + getStatus: (): Promise => ipcRenderer.invoke('global-shortcuts:get-status'), + onStatus: (callback: (status: GlobalShortcutsStatus) => void): (() => void) => { + const handler = (_event: Electron.IpcRendererEvent, data: GlobalShortcutsStatus): void => { + callback(data); + }; + ipcRenderer.on('global-shortcuts:status', handler); + return () => { + ipcRenderer.removeListener('global-shortcuts:status', handler); + }; + }, + onEvent: (callback: (event: GlobalShortcutEvent) => void): (() => void) => { + const handler = (_event: Electron.IpcRendererEvent, data: GlobalShortcutEvent): void => { + callback(data); + }; + ipcRenderer.on('global-shortcut-event', handler); + return () => { + ipcRenderer.removeListener('global-shortcut-event', handler); + }; + }, + setUp: (): Promise => ipcRenderer.invoke('global-shortcuts:set-up'), + recheck: (): Promise => ipcRenderer.invoke('global-shortcuts:recheck'), + configure: (): Promise => ipcRenderer.invoke('global-shortcuts:configure'), + setDirectInputEnabled: (enabled: boolean): Promise => + ipcRenderer.invoke('global-shortcuts:set-direct-input-enabled', enabled), + startCapture: (): Promise => ipcRenderer.invoke('global-shortcuts:start-capture'), + stopCapture: (captureId: number): Promise => ipcRenderer.invoke('global-shortcuts:stop-capture', captureId), + onCapture: (callback: (event: GlobalShortcutCaptureEvent) => void): (() => void) => { + const handler = (_event: Electron.IpcRendererEvent, data: GlobalShortcutCaptureEvent): void => { + callback(data); + }; + ipcRenderer.on('global-shortcuts:capture', handler); + return () => { + ipcRenderer.removeListener('global-shortcuts:capture', handler); + }; + }, + }, spellcheckGetState: (): Promise => ipcRenderer.invoke('spellcheck-get-state'), spellcheckSetState: (state: Partial): Promise => ipcRenderer.invoke('spellcheck-set-state', state),