feat(keybinding): several shortcuts per action, mouse buttons, searchable picker

Shortcuts were defined in four places that drifted apart: per-action IPC channel
pairs, a hand-written VK table in the service, a second one in the renderer, and
three copies of the keycap styling. Adding an action meant editing all of them,
so two shortcuts stayed hardcoded in bootstrap and one had no settings entry at
all.

packages/core/src/keybinding.ts is now the single source for the binding type,
the selectable key catalog, the action catalog, normalization, validation,
conflict detection, display labels, search and deserialization. Main, preload
and renderer all read from it; nothing redefines keys or rules locally.

- Each action holds a list of bindings instead of one. AppConfig's four
  *Shortcut fields collapse into a single keyBindings map, migrated on launch.
- Mouse buttons can be bound. Left click is refused, right/middle need a
  modifier, side buttons are free. uiohook cannot swallow events, so the
  original click still fires and the UI says so.
- Keys can be picked from a grouped dropdown with a search box, not only by
  recording a keypress.
- HOTKEY's 14 channels become KEYBINDING's 9, taking the action as a parameter,
  so actions no longer multiply channels. The history and command popups moved
  out of bootstrap into ordinary actions.
- displayLabel is gone; labels derive from the binding and follow the app
  language and platform.

Fixes found on the way:
- Double-press hands-free was unreachable: lookup returned only the first
  matching action, and dictation shares its default binding.
- Reserved-combination checks compared joined key names, so a different modifier
  order let Ctrl+C through.
- Disabling shortcuts released every global registration in the process,
  including the popup ones, and never restored them.
- Enabling shortcuts after starting disabled left nothing registered.
- The dashboard stored the caption event payload instead of the state in it.
This commit is contained in:
Yun Chan 2026-09-21 13:41:47 +09:00
parent 0ca9e242fa
commit 4ad1ae6ed4
49 changed files with 5901 additions and 1792 deletions

View file

@ -0,0 +1,310 @@
// packages/core/__tests__/keybinding-i18n.test.ts
// 키바인딩 SSOT 가 참조하는 i18n 키가 실제 로케일 JSON 에 존재하는지 잠근다.
//
// keybinding.ts 는 i18n 키를 평범한 string 으로 노출한다 — 코어가 로케일 패키지에 의존하지
// 않게 하려는 의도다. 그래서 렌더러는 `as TranslationKey` 로 좁혀 쓰고, 캐스팅이라
// 존재하지 않는 키를 넘겨도 컴파일이 잡지 못한다. 런타임에 키 문자열이 그대로 화면에 노출된다.
//
// 그 구멍을 여기서 막는다. 소스에 i18n 의존을 들이지 않기 위해 JSON 을 직접 읽으며,
// 이 파일에서만 그렇게 한다.
//
// 검증 대상은 **core 가 참조하는 키만**이다. 렌더러 전용 `keybinding.ui.*` 는 대상이 아니다.
import { readFileSync, readdirSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { describe, it, expect } from 'vitest'
import {
KEYBINDING_ACTIONS,
KEY_CATALOG,
KEY_CATALOG_GROUP_LABEL_KEYS,
validateBinding
} from '../src/keybinding'
import type { BindingDevice, BindingRejectReason, KeyBinding } from '../src/keybinding'
// ------------------------------------------------------------
// 로케일 JSON 접근
// ------------------------------------------------------------
const I18N_SRC_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'i18n', 'src')
const LOCALES_DIR = join(I18N_SRC_DIR, 'locales')
/** 마스터 로케일. `TranslationKey = keyof typeof ko` 이므로 ko 가 키 집합의 정본이다. */
const MASTER_LOCALE = 'ko'
/** 렌더러 전용 키 접두사 — core 는 이걸 참조하지 않아야 한다. */
const RENDERER_ONLY_PREFIX = 'keybinding.ui.'
function localeNames(): string[] {
return readdirSync(LOCALES_DIR)
.filter((file) => file.endsWith('.json'))
.map((file) => file.slice(0, -'.json'.length))
.sort()
}
function readLocale(name: string): Record<string, string> {
const parsed: unknown = JSON.parse(readFileSync(join(LOCALES_DIR, `${name}.json`), 'utf8'))
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
throw new Error(`${name}.json 의 최상위가 평면 객체가 아니다`)
}
const table: Record<string, string> = {}
for (const [key, value] of Object.entries(parsed)) {
if (typeof value !== 'string') {
throw new Error(`${name}.json 의 "${key}" 값이 문자열이 아니다`)
}
table[key] = value
}
return table
}
// ------------------------------------------------------------
// core 가 참조하는 i18n 키 수집
// ------------------------------------------------------------
/** 카탈로그 · 그룹 · 액션 상수가 선언적으로 들고 있는 키. */
function declaredI18nKeys(): Set<string> {
const keys = new Set<string>()
for (const entry of KEY_CATALOG) {
if (entry.labelKey !== undefined) keys.add(entry.labelKey)
if (entry.disabledReasonKey !== undefined) keys.add(entry.disabledReasonKey)
if (entry.passthroughWarningKey !== undefined) keys.add(entry.passthroughWarningKey)
}
for (const groupLabelKey of Object.values(KEY_CATALOG_GROUP_LABEL_KEYS)) {
keys.add(groupLabelKey)
}
for (const action of KEYBINDING_ACTIONS) {
keys.add(action.labelKey)
keys.add(action.descriptionKey)
}
return keys
}
type ModFlags = Partial<Pick<KeyBinding, 'ctrl' | 'alt' | 'shift' | 'meta'>>
/** 거부·경고 경로를 폭넓게 태우기 위한 수정자 조합. */
const MOD_COMBOS: readonly ModFlags[] = [
{},
{ ctrl: true },
{ alt: true },
{ shift: true },
{ meta: true },
{ ctrl: true, alt: true },
{ ctrl: true, shift: true },
{ ctrl: true, alt: true, shift: true, meta: true }
]
/** 어느 카탈로그 그룹에도 없는 코드 — unknown-key 경로용 */
const UNKNOWN_VK = 0x99
const UNKNOWN_MOUSE = 9
function probe(device: BindingDevice, code: number, mods: ModFlags): KeyBinding {
return {
device,
code,
ctrl: mods.ctrl ?? false,
alt: mods.alt ?? false,
shift: mods.shift ?? false,
meta: mods.meta ?? false
}
}
interface ValidationSweep {
reasonKeys: Set<string>
warningKeys: Set<string>
reasons: Set<BindingRejectReason>
}
/**
* 카탈로그 전 엔트리 × 수정자 조합 + 카탈로그 밖 코드를 실제로 validateBinding 에 태워
* 반환된 i18n 키를 수집한다.
*
* 하드코딩 목록을 쓰지 않는 이유: 거부 사유나 카탈로그 항목이 늘어나면 그 키가 자동으로
* 수집 대상에 들어와야 한다.
*/
function sweepValidation(): ValidationSweep {
const sweep: ValidationSweep = {
reasonKeys: new Set<string>(),
warningKeys: new Set<string>(),
reasons: new Set<BindingRejectReason>()
}
const probes: KeyBinding[] = []
for (const entry of KEY_CATALOG) {
for (const mods of MOD_COMBOS) {
probes.push(probe(entry.device, entry.code, mods))
}
}
for (const mods of MOD_COMBOS) {
probes.push(probe('keyboard', UNKNOWN_VK, mods))
probes.push(probe('mouse', UNKNOWN_MOUSE, mods))
}
for (const binding of probes) {
const result = validateBinding(binding)
if (result.reasonKey !== null) sweep.reasonKeys.add(result.reasonKey)
if (result.warningKey !== null) sweep.warningKeys.add(result.warningKey)
if (result.reason !== null) sweep.reasons.add(result.reason)
}
return sweep
}
/** core 가 참조하는 i18n 키 전부 (선언 + 런타임 반환값). */
function allCoreI18nKeys(): string[] {
const sweep = sweepValidation()
const keys = new Set<string>([
...declaredI18nKeys(),
...sweep.reasonKeys,
...sweep.warningKeys
])
return [...keys].sort()
}
// ------------------------------------------------------------
// 수집기 자체가 비어있지 않은지 (이 아래 검증이 공회전하지 않게)
// ------------------------------------------------------------
describe('i18n 키 수집기', () => {
it('선언된 키를 실제로 모은다', () => {
const declared = declaredI18nKeys()
expect(declared.size).toBeGreaterThan(0)
for (const action of KEYBINDING_ACTIONS) {
expect(declared.has(action.labelKey), action.id).toBe(true)
expect(declared.has(action.descriptionKey), action.id).toBe(true)
}
for (const groupLabelKey of Object.values(KEY_CATALOG_GROUP_LABEL_KEYS)) {
expect(declared.has(groupLabelKey), groupLabelKey).toBe(true)
}
})
it('검증 스윕이 모든 거부 사유 경로를 실제로 태운다', () => {
const sweep = sweepValidation()
const reasons: BindingRejectReason[] = [
'device-disabled',
'modifier-required',
'system-reserved',
'unknown-key'
]
expect([...sweep.reasons].sort()).toEqual(reasons)
expect(sweep.reasonKeys.size).toBe(reasons.length)
expect(sweep.warningKeys.size).toBeGreaterThan(0)
})
it('수집한 키가 전부 keybinding 네임스페이스에 있다', () => {
const keys = allCoreI18nKeys()
expect(keys.length).toBeGreaterThan(0)
for (const key of keys) {
expect(key.startsWith('keybinding.'), key).toBe(true)
}
})
it('수집한 키 수가 계약 상수 규모에 못 미치지 않는다', () => {
// 액션당 라벨·설명 2개 + 그룹 라벨 전부는 최소한 들어와야 한다.
const floor =
KEYBINDING_ACTIONS.length * 2 + Object.keys(KEY_CATALOG_GROUP_LABEL_KEYS).length
expect(allCoreI18nKeys().length).toBeGreaterThanOrEqual(floor)
})
it('core 는 렌더러 전용 keybinding.ui.* 를 참조하지 않는다', () => {
const rendererKeys = allCoreI18nKeys().filter((key) =>
key.startsWith(RENDERER_ONLY_PREFIX)
)
expect(rendererKeys).toEqual([])
})
})
// ------------------------------------------------------------
// 마스터 로케일 (ko)
// ------------------------------------------------------------
describe('키바인딩 i18n 키 — 마스터 로케일 ko', () => {
it('카탈로그·그룹·액션이 선언한 키가 전부 ko.json 에 있다', () => {
const table = readLocale(MASTER_LOCALE)
const missing = [...declaredI18nKeys()].sort().filter((key) => !(key in table))
expect(missing).toEqual([])
})
it('validateBinding 이 실제로 반환하는 reasonKey 가 전부 ko.json 에 있다', () => {
const table = readLocale(MASTER_LOCALE)
const missing = [...sweepValidation().reasonKeys].sort().filter((key) => !(key in table))
expect(missing).toEqual([])
})
it('validateBinding 이 실제로 반환하는 warningKey 가 전부 ko.json 에 있다', () => {
const table = readLocale(MASTER_LOCALE)
const missing = [...sweepValidation().warningKeys].sort().filter((key) => !(key in table))
expect(missing).toEqual([])
})
it('ko.json 의 해당 값이 빈 문자열이 아니다', () => {
const table = readLocale(MASTER_LOCALE)
const blank = allCoreI18nKeys().filter((key) => {
const value = table[key]
return value !== undefined && value.trim() === ''
})
expect(blank).toEqual([])
})
})
// ------------------------------------------------------------
// 전체 로케일
// ------------------------------------------------------------
describe('키바인딩 i18n 키 — 전체 로케일', () => {
it('마스터 로케일 ko 를 포함해 로케일이 복수로 존재한다', () => {
const names = localeNames()
expect(names).toContain(MASTER_LOCALE)
expect(names.length).toBeGreaterThan(1)
})
it('로케일 디렉터리의 파일이 전부 index.tsx 에 등록되어 있다', () => {
const source = readFileSync(join(I18N_SRC_DIR, 'index.tsx'), 'utf8')
const unregistered = localeNames().filter(
(name) => !source.includes(`./locales/${name}.json`)
)
expect(unregistered).toEqual([])
})
it('모든 로케일이 core 참조 키를 빠짐없이 갖는다', () => {
const keys = allCoreI18nKeys()
const missing: string[] = []
for (const name of localeNames()) {
const table = readLocale(name)
for (const key of keys) {
if (!(key in table)) missing.push(`${name}: ${key}`)
}
}
expect(missing).toEqual([])
})
it('모든 로케일이 ko 와 동일한 core 참조 키 집합을 갖는다', () => {
const keys = allCoreI18nKeys()
const master = readLocale(MASTER_LOCALE)
const masterSubset = keys.filter((key) => key in master)
const mismatched: string[] = []
for (const name of localeNames()) {
const table = readLocale(name)
const subset = keys.filter((key) => key in table)
if (subset.join('\n') !== masterSubset.join('\n')) {
const missing = masterSubset.filter((key) => !subset.includes(key))
const extra = subset.filter((key) => !masterSubset.includes(key))
mismatched.push(
`${name}: missing=[${missing.join(', ')}] extra=[${extra.join(', ')}]`
)
}
}
expect(mismatched).toEqual([])
})
it('모든 로케일에서 core 참조 키의 값이 빈 문자열이 아니다', () => {
const keys = allCoreI18nKeys()
const blank: string[] = []
for (const name of localeNames()) {
const table = readLocale(name)
for (const key of keys) {
const value = table[key]
if (value !== undefined && value.trim() === '') blank.push(`${name}: ${key}`)
}
}
expect(blank).toEqual([])
})
})

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,9 @@
"private": true,
"description": "D3RO Voice 공유 비즈니스 로직 — 타입, 에러, IPC 채널, 상수, 유틸",
"license": "MIT",
"scripts": {
"test": "vitest run"
},
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
@ -15,6 +18,10 @@
"types": "./src/types.ts",
"default": "./src/types.ts"
},
"./keybinding": {
"types": "./src/keybinding.ts",
"default": "./src/keybinding.ts"
},
"./errors": {
"types": "./src/errors.ts",
"default": "./src/errors.ts"

View file

@ -3,6 +3,7 @@
// 본 파일은 편의를 위한 통합 re-export
export * from './types'
export * from './keybinding'
export * from './errors'
export * from './ipc-channels'
export * from './constants'

View file

@ -97,22 +97,30 @@ export const IPC_CHANNELS = {
PREMIUM_UPGRADE_REQUIRED: 'llm:premiumUpgradeRequired'
},
HOTKEY: {
GET_DICTATION_SHORTCUT: 'hotkey:getDictationShortcut',
SET_DICTATION_SHORTCUT: 'hotkey:setDictationShortcut',
GET_HANDS_FREE_SHORTCUT: 'hotkey:getHandsFreeShortcut',
SET_HANDS_FREE_SHORTCUT: 'hotkey:setHandsFreeShortcut',
GET_COMMAND_SHORTCUT: 'hotkey:getCommandShortcut',
SET_COMMAND_SHORTCUT: 'hotkey:setCommandShortcut',
GET_CAPTION_SHORTCUT: 'hotkey:getCaptionShortcut',
SET_CAPTION_SHORTCUT: 'hotkey:setCaptionShortcut',
IS_ENABLED: 'hotkey:isEnabled',
SET_ENABLED: 'hotkey:setEnabled',
START_RECORDING: 'hotkey:startRecording',
STOP_RECORDING: 'hotkey:stopRecording',
/**
* 키바인딩. 액션을 파라미터로 받는 단일 채널 집합이다 —
* 액션이 늘어도 채널을 늘리지 않는다. 계약 정본은 `packages/core/src/keybinding.ts`.
*/
KEYBINDING: {
/** 전체 액션의 바인딩 맵 조회 */
GET_MAP: 'keybinding:getMap',
/** 한 액션의 바인딩 목록 교체 */
SET_BINDINGS: 'keybinding:setBindings',
/** 한 액션을 기본값으로 되돌림 */
RESET_ACTION: 'keybinding:resetAction',
/** 전체를 기본값으로 되돌림 */
RESET_ALL: 'keybinding:resetAll',
/** 바인딩 유효성 + 액션 간 충돌 사전 검사 */
VALIDATE: 'keybinding:validate',
/** 전역 키바인딩 on/off 조회 */
IS_ENABLED: 'keybinding:isEnabled',
/** 전역 키바인딩 on/off 설정 */
SET_ENABLED: 'keybinding:setEnabled',
// Main → Renderer events
TRIGGERED: 'hotkey:triggered',
RECORDING_RESULT: 'hotkey:recordingResult'
/** 바인딩 입력 감지 알림 */
TRIGGERED: 'keybinding:triggered',
/** 바인딩 맵 변경 알림 (창 간 동기화) */
CHANGED: 'keybinding:changed'
},
CONFIG: {

File diff suppressed because it is too large Load diff

View file

@ -376,39 +376,40 @@ export interface LLMPullProgressEvent {
}
// ============================================================
// Hotkey (핫키)
// Keybinding (키바인딩)
// ============================================================
//
// 계약 정본은 `packages/core/src/keybinding.ts` 다.
// 키 목록 · 라벨 · 검증 · 충돌 판정을 여기 다시 정의하지 않는다.
export interface HotkeyBinding {
keyCode: number
ctrl: boolean
alt: boolean
shift: boolean
meta: boolean
displayLabel: string
}
export interface SetHotkeyParams {
binding: HotkeyBinding
}
export type {
BindingConflict,
BindingDevice,
BindingPlatform,
BindingRejectReason,
BindingSegment,
BindingValidation,
KeyBinding,
KeyBindingActionGroup,
KeyBindingActionId,
KeyBindingActionSpec,
KeyBindingChangedEvent,
KeyBindingList,
KeyBindingMap,
KeyBindingTriggeredEvent,
KeyBindingValidationResult,
KeyCatalogEntry,
KeyCatalogGroup,
MouseButtonCode,
ResetKeyBindingParams,
SetKeyBindingsParams,
ValidateKeyBindingParams
} from './keybinding'
export interface SetEnabledParams {
enabled: boolean
}
export type HotkeyAction = 'dictation' | 'hands-free' | 'command' | 'caption'
export interface HotkeyTriggeredEvent {
action: HotkeyAction
type: 'pressed' | 'released'
isDoublePress: boolean
}
export interface HotkeyRecordingResultEvent {
binding: HotkeyBinding | null
conflictReason: string | null
}
// ============================================================
// Config (설정)
// ============================================================
@ -451,11 +452,11 @@ export interface AppConfig {
*/
conversationBackend: 'local' | 'realtime'
defaultLLMAction: LLMActionSelection
dictationShortcut: HotkeyBinding
handsFreeShortcut: HotkeyBinding
commandShortcut: HotkeyBinding
/** 실시간 자막 토글 핫키 (Phase 10.1) */
captionShortcut: HotkeyBinding
/**
* 전체 키바인딩. 액션 id → 바인딩 목록(다중 바인딩).
* 구조·기본값·검증은 `packages/core/src/keybinding.ts` 가 정본이다.
*/
keyBindings: import('./keybinding').KeyBindingMap
hotkeyEnabled: boolean
insertMethod: 'clipboard' | 'keyboard'
autoInsert: boolean

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: false,
environment: 'node',
include: ['__tests__/**/*.test.ts'],
testTimeout: 10000
}
})

View file

@ -270,5 +270,60 @@
"mobile.works.historyDescription": "Transkripte von Aufnahmen und Importen suchen und verwalten.",
"mobile.meetings.templateEmpty": "Keine Meeting-Dokumentvorlagen verfügbar. Sie können das Meeting ohne Vorlage starten.",
"mobile.meetings.templateNone": "Ohne Vorlage starten",
"mobile.meetings.templateNoneDesc": "Vorlage später bei der Dokumenterzeugung wählen"
"mobile.meetings.templateNoneDesc": "Vorlage später bei der Dokumenterzeugung wählen",
"keybinding.mouse.left": "Linke Maustaste",
"keybinding.mouse.right": "Rechte Maustaste",
"keybinding.mouse.middle": "Mittlere Maustaste",
"keybinding.mouse.back": "Maustaste Zurück",
"keybinding.mouse.forward": "Maustaste Vorwärts",
"keybinding.group.mouse": "Maus",
"keybinding.group.modifier": "Modifikatortasten",
"keybinding.group.function": "Funktionstasten",
"keybinding.group.letter": "Buchstaben",
"keybinding.group.digit": "Ziffern",
"keybinding.group.numpad": "Ziffernblock",
"keybinding.group.navigation": "Navigation",
"keybinding.group.editing": "Bearbeiten",
"keybinding.group.punctuation": "Sonderzeichen",
"keybinding.group.system": "System",
"keybinding.action.dictation": "Diktat",
"keybinding.action.dictation.desc": "Nimmt auf, solange die Taste gedrückt bleibt.",
"keybinding.action.handsFree": "Ein-Tasten-Modus",
"keybinding.action.handsFree.desc": "Zweimal drücken zum Umschalten.",
"keybinding.action.command": "Sprachbefehle",
"keybinding.action.command.desc": "Führt einen Befehl per Sprache aus.",
"keybinding.action.caption": "Live-Untertitel",
"keybinding.action.caption.desc": "Schaltet Live-Untertitel ein oder aus.",
"keybinding.action.historyPopup": "Verlaufs-Popup",
"keybinding.action.historyPopup.desc": "Öffnet das Verlaufs-Popup.",
"keybinding.action.commandPopup": "Befehls-Popup",
"keybinding.action.commandPopup.desc": "Öffnet das Befehls-Popup.",
"keybinding.reject.unknownKey": "Diese Taste wird nicht unterstützt",
"keybinding.reject.systemReserved": "Dies ist ein vom System reserviertes Tastenkürzel",
"keybinding.reject.modifierRequired": "Muss mit einer Modifikatortaste (Strg/Alt/Umschalt) kombiniert werden",
"keybinding.disabled.mouseLeft": "Der Linksklick wird für jede Bildschirmbedienung gebraucht und lässt sich nicht belegen",
"keybinding.warning.mousePassthrough": "Maustasten führen zusätzlich weiterhin ihre ursprüngliche Aktion aus",
"keybinding.ui.add": "Hinzufügen",
"keybinding.ui.remove": "Löschen",
"keybinding.ui.reset": "Auf Standard zurücksetzen",
"keybinding.ui.tabRecord": "Taste drücken",
"keybinding.ui.tabList": "Aus Liste wählen",
"keybinding.ui.search": "Tasten suchen",
"keybinding.ui.searchEmpty": "Keine Suchergebnisse",
"keybinding.ui.pressKeys": "Drücken Sie die gewünschte Taste oder Maustaste",
"keybinding.ui.noBindings": "Kein Tastenkürzel festgelegt",
"keybinding.ui.conflictWith": "Überschneidet sich mit {{action}}",
"keybinding.ui.modifiers": "Modifikatortasten",
"keybinding.ui.selectedKey": "Ausgewählte Taste",
"keybinding.ui.sectionVoice": "Sprache",
"keybinding.ui.sectionWindow": "Fenster / Popups",
"keybinding.ui.duplicate": "Dieses Tastenkürzel wurde bereits hinzugefügt",
"keybinding.ui.holdMode": "Halten",
"keybinding.ui.doublePress": "Doppeldruck",
"keybinding.ui.pickerTitle": "Tastenkürzel einrichten",
"keybinding.ui.pressToRecord": "Drücken, um die Aufnahme zu starten",
"keybinding.ui.recordHint": "Gib eine Kombination (z. B. Ctrl+Shift+Q) oder eine einzelne Taste (z. B. F5) ein",
"keybinding.ui.recordAgain": "Erneut eingeben",
"keybinding.ui.saveFailed": "Das Tastenkürzel konnte nicht gespeichert werden",
"keybinding.ui.globalEnabled": "Globale Tastenkürzel aktivieren"
}

View file

@ -1652,5 +1652,60 @@
"mobile.works.section.data": "Data & alerts",
"mobile.works.historyDescription": "Search and manage transcripts from recordings and imports.",
"mobile.meetings.templateNone": "Start without template",
"mobile.meetings.templateNoneDesc": "Pick a template later when generating documents"
"mobile.meetings.templateNoneDesc": "Pick a template later when generating documents",
"keybinding.mouse.left": "Left Mouse Button",
"keybinding.mouse.right": "Right Mouse Button",
"keybinding.mouse.middle": "Middle Mouse Button",
"keybinding.mouse.back": "Mouse Back Button",
"keybinding.mouse.forward": "Mouse Forward Button",
"keybinding.group.mouse": "Mouse",
"keybinding.group.modifier": "Modifiers",
"keybinding.group.function": "Function Keys",
"keybinding.group.letter": "Letters",
"keybinding.group.digit": "Digits",
"keybinding.group.numpad": "Numpad",
"keybinding.group.navigation": "Navigation",
"keybinding.group.editing": "Editing",
"keybinding.group.punctuation": "Punctuation",
"keybinding.group.system": "System",
"keybinding.action.dictation": "Dictation",
"keybinding.action.dictation.desc": "Records while the key is held down.",
"keybinding.action.handsFree": "One-Touch Mode",
"keybinding.action.handsFree.desc": "Press twice to toggle.",
"keybinding.action.command": "Voice Commands",
"keybinding.action.command.desc": "Runs a command by voice.",
"keybinding.action.caption": "Live Caption",
"keybinding.action.caption.desc": "Turns live captions on and off.",
"keybinding.action.historyPopup": "History Popup",
"keybinding.action.historyPopup.desc": "Opens the history popup.",
"keybinding.action.commandPopup": "Command Popup",
"keybinding.action.commandPopup.desc": "Opens the command popup.",
"keybinding.reject.unknownKey": "This key is not supported",
"keybinding.reject.systemReserved": "This is a reserved system shortcut",
"keybinding.reject.modifierRequired": "Must be combined with a modifier (Ctrl/Alt/Shift)",
"keybinding.disabled.mouseLeft": "Left click is used for every on-screen interaction, so it cannot be bound",
"keybinding.warning.mousePassthrough": "Mouse buttons also keep performing their original action",
"keybinding.ui.add": "Add",
"keybinding.ui.remove": "Remove",
"keybinding.ui.reset": "Reset to Default",
"keybinding.ui.tabRecord": "Press a Key",
"keybinding.ui.tabList": "Choose from List",
"keybinding.ui.search": "Search keys",
"keybinding.ui.searchEmpty": "No matching keys",
"keybinding.ui.pressKeys": "Press the key or mouse button you want",
"keybinding.ui.noBindings": "No shortcut set",
"keybinding.ui.conflictWith": "Conflicts with {{action}}",
"keybinding.ui.modifiers": "Modifiers",
"keybinding.ui.selectedKey": "Selected key",
"keybinding.ui.sectionVoice": "Voice",
"keybinding.ui.sectionWindow": "Windows / Popups",
"keybinding.ui.duplicate": "This shortcut has already been added",
"keybinding.ui.holdMode": "Hold",
"keybinding.ui.doublePress": "Double-press",
"keybinding.ui.pickerTitle": "Set Shortcut",
"keybinding.ui.pressToRecord": "Press to start recording",
"keybinding.ui.recordHint": "Enter a combination (e.g. Ctrl+Shift+Q) or a single key (e.g. F5)",
"keybinding.ui.recordAgain": "Clear",
"keybinding.ui.saveFailed": "The shortcut could not be saved",
"keybinding.ui.globalEnabled": "Enable global shortcuts"
}

View file

@ -270,5 +270,60 @@
"mobile.works.historyDescription": "Busca y gestiona transcripciones de grabaciones e importaciones.",
"mobile.meetings.templateEmpty": "No hay plantillas de documento de reunión disponibles. Puedes iniciar la reunión sin una.",
"mobile.meetings.templateNone": "Empezar sin plantilla",
"mobile.meetings.templateNoneDesc": "Elige una plantilla al generar documentos"
"mobile.meetings.templateNoneDesc": "Elige una plantilla al generar documentos",
"keybinding.mouse.left": "Botón izquierdo del ratón",
"keybinding.mouse.right": "Botón derecho del ratón",
"keybinding.mouse.middle": "Botón central del ratón",
"keybinding.mouse.back": "Botón Atrás del ratón",
"keybinding.mouse.forward": "Botón Adelante del ratón",
"keybinding.group.mouse": "Ratón",
"keybinding.group.modifier": "Modificadores",
"keybinding.group.function": "Teclas de función",
"keybinding.group.letter": "Letras",
"keybinding.group.digit": "Números",
"keybinding.group.numpad": "Teclado numérico",
"keybinding.group.navigation": "Navegación",
"keybinding.group.editing": "Edición",
"keybinding.group.punctuation": "Símbolos",
"keybinding.group.system": "Sistema",
"keybinding.action.dictation": "Dictado",
"keybinding.action.dictation.desc": "Graba mientras se mantiene pulsada la tecla.",
"keybinding.action.handsFree": "Modo un toque",
"keybinding.action.handsFree.desc": "Pulsa dos veces para activarlo o desactivarlo.",
"keybinding.action.command": "Comandos de voz",
"keybinding.action.command.desc": "Ejecuta un comando por voz.",
"keybinding.action.caption": "Subtítulos en directo",
"keybinding.action.caption.desc": "Activa o desactiva los subtítulos en directo.",
"keybinding.action.historyPopup": "Ventana de historial",
"keybinding.action.historyPopup.desc": "Abre la ventana emergente del historial.",
"keybinding.action.commandPopup": "Ventana de comandos",
"keybinding.action.commandPopup.desc": "Abre la ventana emergente de comandos.",
"keybinding.reject.unknownKey": "Esta tecla no es compatible",
"keybinding.reject.systemReserved": "Es un atajo reservado por el sistema",
"keybinding.reject.modifierRequired": "Debe combinarse con un modificador (Ctrl/Alt/Mayús)",
"keybinding.disabled.mouseLeft": "El clic izquierdo se usa en toda la interfaz, así que no se puede asignar",
"keybinding.warning.mousePassthrough": "Los botones del ratón siguen ejecutando también su acción original",
"keybinding.ui.add": "Añadir",
"keybinding.ui.remove": "Eliminar",
"keybinding.ui.reset": "Restablecer valores",
"keybinding.ui.tabRecord": "Pulsar tecla",
"keybinding.ui.tabList": "Elegir de la lista",
"keybinding.ui.search": "Buscar teclas",
"keybinding.ui.searchEmpty": "No hay resultados",
"keybinding.ui.pressKeys": "Pulsa la tecla o el botón del ratón que quieras",
"keybinding.ui.noBindings": "Sin atajo configurado",
"keybinding.ui.conflictWith": "Entra en conflicto con {{action}}",
"keybinding.ui.modifiers": "Modificadores",
"keybinding.ui.selectedKey": "Tecla seleccionada",
"keybinding.ui.sectionVoice": "Voz",
"keybinding.ui.sectionWindow": "Ventanas / Emergentes",
"keybinding.ui.duplicate": "Este atajo ya está añadido",
"keybinding.ui.holdMode": "Mantener",
"keybinding.ui.doublePress": "Doble pulsación",
"keybinding.ui.pickerTitle": "Configurar atajo",
"keybinding.ui.pressToRecord": "Pulsa para empezar a grabar",
"keybinding.ui.recordHint": "Introduce una combinación (ej: Ctrl+Shift+Q) o una tecla sola (ej: F5)",
"keybinding.ui.recordAgain": "Volver a introducir",
"keybinding.ui.saveFailed": "No se ha podido guardar el atajo",
"keybinding.ui.globalEnabled": "Usar atajos globales"
}

View file

@ -270,5 +270,60 @@
"mobile.works.historyDescription": "Recherchez et gérez les transcriptions des enregistrements et importations.",
"mobile.meetings.templateEmpty": "Aucun modèle de document de réunion disponible. Vous pouvez démarrer la réunion sans modèle.",
"mobile.meetings.templateNone": "Démarrer sans modèle",
"mobile.meetings.templateNoneDesc": "Choisissez un modèle lors de la génération de documents"
"mobile.meetings.templateNoneDesc": "Choisissez un modèle lors de la génération de documents",
"keybinding.mouse.left": "Bouton gauche de la souris",
"keybinding.mouse.right": "Bouton droit de la souris",
"keybinding.mouse.middle": "Bouton du milieu de la souris",
"keybinding.mouse.back": "Bouton Précédent de la souris",
"keybinding.mouse.forward": "Bouton Suivant de la souris",
"keybinding.group.mouse": "Souris",
"keybinding.group.modifier": "Touches de modification",
"keybinding.group.function": "Touches de fonction",
"keybinding.group.letter": "Lettres",
"keybinding.group.digit": "Chiffres",
"keybinding.group.numpad": "Pavé numérique",
"keybinding.group.navigation": "Navigation",
"keybinding.group.editing": "Édition",
"keybinding.group.punctuation": "Symboles",
"keybinding.group.system": "Système",
"keybinding.action.dictation": "Dictée",
"keybinding.action.dictation.desc": "Enregistre tant que la touche est maintenue.",
"keybinding.action.handsFree": "Mode une touche",
"keybinding.action.handsFree.desc": "Appuyez deux fois pour activer ou désactiver.",
"keybinding.action.command": "Commandes vocales",
"keybinding.action.command.desc": "Exécute une commande à la voix.",
"keybinding.action.caption": "Sous-titres en direct",
"keybinding.action.caption.desc": "Active ou désactive les sous-titres en direct.",
"keybinding.action.historyPopup": "Fenêtre d'historique",
"keybinding.action.historyPopup.desc": "Ouvre la fenêtre contextuelle de l'historique.",
"keybinding.action.commandPopup": "Fenêtre de commandes",
"keybinding.action.commandPopup.desc": "Ouvre la fenêtre contextuelle des commandes.",
"keybinding.reject.unknownKey": "Cette touche n'est pas prise en charge",
"keybinding.reject.systemReserved": "Il s'agit d'un raccourci réservé au système",
"keybinding.reject.modifierRequired": "Doit être combinée avec une touche de modification (Ctrl/Alt/Maj)",
"keybinding.disabled.mouseLeft": "Le clic gauche sert à toutes les interactions à l'écran, il ne peut pas être affecté",
"keybinding.warning.mousePassthrough": "Les boutons de la souris conservent aussi leur action d'origine",
"keybinding.ui.add": "Ajouter",
"keybinding.ui.remove": "Supprimer",
"keybinding.ui.reset": "Valeurs par défaut",
"keybinding.ui.tabRecord": "Appuyer sur une touche",
"keybinding.ui.tabList": "Choisir dans la liste",
"keybinding.ui.search": "Rechercher une touche",
"keybinding.ui.searchEmpty": "Aucun résultat",
"keybinding.ui.pressKeys": "Appuyez sur la touche ou le bouton de souris souhaité",
"keybinding.ui.noBindings": "Aucun raccourci défini",
"keybinding.ui.conflictWith": "En conflit avec {{action}}",
"keybinding.ui.modifiers": "Touches de modification",
"keybinding.ui.selectedKey": "Touche sélectionnée",
"keybinding.ui.sectionVoice": "Voix",
"keybinding.ui.sectionWindow": "Fenêtres / Pop-ups",
"keybinding.ui.duplicate": "Ce raccourci est déjà ajouté",
"keybinding.ui.holdMode": "Maintien",
"keybinding.ui.doublePress": "Double appui",
"keybinding.ui.pickerTitle": "Configurer le raccourci",
"keybinding.ui.pressToRecord": "Appuyez pour commencer l'enregistrement",
"keybinding.ui.recordHint": "Saisissez une combinaison (ex : Ctrl+Shift+Q) ou une touche seule (ex : F5)",
"keybinding.ui.recordAgain": "Ressaisir",
"keybinding.ui.saveFailed": "Le raccourci n'a pas pu être enregistré",
"keybinding.ui.globalEnabled": "Activer les raccourcis globaux"
}

View file

@ -270,5 +270,60 @@
"mobile.works.historyDescription": "録音とインポートの文字起こしを検索・管理します。",
"mobile.meetings.templateEmpty": "利用可能な会議ドキュメントテンプレートがありません。テンプレートなしで会議を開始できます。",
"mobile.meetings.templateNone": "テンプレートなしで開始",
"mobile.meetings.templateNoneDesc": "ドキュメント生成時にテンプレートを選択します"
"mobile.meetings.templateNoneDesc": "ドキュメント生成時にテンプレートを選択します",
"keybinding.mouse.left": "マウス左ボタン",
"keybinding.mouse.right": "マウス右ボタン",
"keybinding.mouse.middle": "マウス中ボタン",
"keybinding.mouse.back": "マウス戻るボタン",
"keybinding.mouse.forward": "マウス進むボタン",
"keybinding.group.mouse": "マウス",
"keybinding.group.modifier": "修飾キー",
"keybinding.group.function": "ファンクションキー",
"keybinding.group.letter": "文字",
"keybinding.group.digit": "数字",
"keybinding.group.numpad": "テンキー",
"keybinding.group.navigation": "ナビゲーション",
"keybinding.group.editing": "編集",
"keybinding.group.punctuation": "記号",
"keybinding.group.system": "システム",
"keybinding.action.dictation": "ディクテーション",
"keybinding.action.dictation.desc": "押している間、録音します。",
"keybinding.action.handsFree": "ワンタッチモード",
"keybinding.action.handsFree.desc": "2回押して切り替えます。",
"keybinding.action.command": "音声コマンド",
"keybinding.action.command.desc": "音声でコマンドを実行します。",
"keybinding.action.caption": "リアルタイム字幕",
"keybinding.action.caption.desc": "リアルタイム字幕をオン・オフします。",
"keybinding.action.historyPopup": "履歴ポップアップ",
"keybinding.action.historyPopup.desc": "履歴ポップアップを開きます。",
"keybinding.action.commandPopup": "コマンドポップアップ",
"keybinding.action.commandPopup.desc": "コマンドポップアップを開きます。",
"keybinding.reject.unknownKey": "サポートされていないキーです",
"keybinding.reject.systemReserved": "システム予約のショートカットです",
"keybinding.reject.modifierRequired": "修飾キー(Ctrl/Alt/Shift)と組み合わせて使用してください",
"keybinding.disabled.mouseLeft": "左クリックはすべての画面操作に使われるため、割り当てできません",
"keybinding.warning.mousePassthrough": "マウスボタンは本来の動作も同時に実行されます",
"keybinding.ui.add": "追加",
"keybinding.ui.remove": "削除",
"keybinding.ui.reset": "既定値に戻す",
"keybinding.ui.tabRecord": "直接入力",
"keybinding.ui.tabList": "一覧から選択",
"keybinding.ui.search": "キーを検索",
"keybinding.ui.searchEmpty": "検索結果がありません",
"keybinding.ui.pressKeys": "使用するキーまたはマウスボタンを押してください",
"keybinding.ui.noBindings": "ショートカット未設定",
"keybinding.ui.conflictWith": "{{action}}と重複しています",
"keybinding.ui.modifiers": "修飾キー",
"keybinding.ui.selectedKey": "選択したキー",
"keybinding.ui.sectionVoice": "音声",
"keybinding.ui.sectionWindow": "ウィンドウ / ポップアップ",
"keybinding.ui.duplicate": "すでに追加されているショートカットです",
"keybinding.ui.holdMode": "長押し",
"keybinding.ui.doublePress": "2回押し",
"keybinding.ui.pickerTitle": "ショートカット設定",
"keybinding.ui.pressToRecord": "押して録音開始",
"keybinding.ui.recordHint": "組み合わせキー(例: Ctrl+Shift+Q)または単一キー(例: F5)を入力してください",
"keybinding.ui.recordAgain": "再入力",
"keybinding.ui.saveFailed": "ショートカットを保存できませんでした",
"keybinding.ui.globalEnabled": "グローバルショートカットを使用"
}

View file

@ -1659,5 +1659,60 @@
"mobile.works.section.data": "데이터 · 알림",
"mobile.works.historyDescription": "녹음과 가져오기의 전사 기록을 검색하고 관리합니다.",
"mobile.meetings.templateNone": "템플릿 없이 시작",
"mobile.meetings.templateNoneDesc": "문서 생성 시점에 템플릿을 선택합니다"
"mobile.meetings.templateNoneDesc": "문서 생성 시점에 템플릿을 선택합니다",
"keybinding.mouse.left": "마우스 왼쪽 버튼",
"keybinding.mouse.right": "마우스 오른쪽 버튼",
"keybinding.mouse.middle": "마우스 가운데 버튼",
"keybinding.mouse.back": "마우스 뒤로 버튼",
"keybinding.mouse.forward": "마우스 앞으로 버튼",
"keybinding.group.mouse": "마우스",
"keybinding.group.modifier": "조합키",
"keybinding.group.function": "기능키",
"keybinding.group.letter": "문자",
"keybinding.group.digit": "숫자",
"keybinding.group.numpad": "숫자패드",
"keybinding.group.navigation": "탐색",
"keybinding.group.editing": "편집",
"keybinding.group.punctuation": "기호",
"keybinding.group.system": "시스템",
"keybinding.action.dictation": "받아쓰기",
"keybinding.action.dictation.desc": "누르고 있는 동안 녹음합니다.",
"keybinding.action.handsFree": "원터치 모드",
"keybinding.action.handsFree.desc": "두 번 눌러 토글합니다.",
"keybinding.action.command": "음성 명령어",
"keybinding.action.command.desc": "음성으로 명령을 실행합니다.",
"keybinding.action.caption": "실시간 자막",
"keybinding.action.caption.desc": "실시간 자막을 켜고 끕니다.",
"keybinding.action.historyPopup": "기록 팝업",
"keybinding.action.historyPopup.desc": "기록 팝업을 엽니다.",
"keybinding.action.commandPopup": "명령어 팝업",
"keybinding.action.commandPopup.desc": "명령어 팝업을 엽니다.",
"keybinding.reject.unknownKey": "지원하지 않는 키입니다",
"keybinding.reject.systemReserved": "시스템 예약 단축키입니다",
"keybinding.reject.modifierRequired": "조합키(Ctrl/Alt/Shift)와 함께 사용해야 합니다",
"keybinding.disabled.mouseLeft": "왼쪽 클릭은 모든 화면 조작에 쓰이므로 바인딩할 수 없습니다",
"keybinding.warning.mousePassthrough": "마우스 버튼은 원래 동작도 함께 실행됩니다",
"keybinding.ui.add": "추가",
"keybinding.ui.remove": "삭제",
"keybinding.ui.reset": "기본값으로",
"keybinding.ui.tabRecord": "직접 입력",
"keybinding.ui.tabList": "목록에서 선택",
"keybinding.ui.search": "키 검색",
"keybinding.ui.searchEmpty": "검색 결과가 없습니다",
"keybinding.ui.pressKeys": "원하는 키 또는 마우스 버튼을 누르세요",
"keybinding.ui.noBindings": "설정된 단축키 없음",
"keybinding.ui.conflictWith": "{{action}}와(과) 중복됩니다",
"keybinding.ui.modifiers": "조합키",
"keybinding.ui.selectedKey": "선택한 키",
"keybinding.ui.sectionVoice": "음성",
"keybinding.ui.sectionWindow": "창 / 팝업",
"keybinding.ui.duplicate": "이미 추가된 단축키입니다",
"keybinding.ui.holdMode": "누름 유지",
"keybinding.ui.doublePress": "두 번 누름",
"keybinding.ui.pickerTitle": "단축키 설정",
"keybinding.ui.pressToRecord": "눌러 녹음 시작",
"keybinding.ui.recordHint": "조합키(예: Ctrl+Shift+Q) 또는 단일키(예: F5)를 입력하세요",
"keybinding.ui.recordAgain": "다시 입력",
"keybinding.ui.saveFailed": "단축키를 저장하지 못했습니다",
"keybinding.ui.globalEnabled": "전역 단축키 사용"
}

View file

@ -270,5 +270,60 @@
"mobile.works.historyDescription": "Pesquise e gerencie transcrições de gravações e importações.",
"mobile.meetings.templateEmpty": "Nenhuma template de documento de reunião disponível. Você pode iniciar a reunião sem uma.",
"mobile.meetings.templateNone": "Começar sem template",
"mobile.meetings.templateNoneDesc": "Escolha uma template ao gerar documentos"
"mobile.meetings.templateNoneDesc": "Escolha uma template ao gerar documentos",
"keybinding.mouse.left": "Botão esquerdo do mouse",
"keybinding.mouse.right": "Botão direito do mouse",
"keybinding.mouse.middle": "Botão do meio do mouse",
"keybinding.mouse.back": "Botão Voltar do mouse",
"keybinding.mouse.forward": "Botão Avançar do mouse",
"keybinding.group.mouse": "Mouse",
"keybinding.group.modifier": "Modificadores",
"keybinding.group.function": "Teclas de função",
"keybinding.group.letter": "Letras",
"keybinding.group.digit": "Números",
"keybinding.group.numpad": "Teclado numérico",
"keybinding.group.navigation": "Navegação",
"keybinding.group.editing": "Edição",
"keybinding.group.punctuation": "Símbolos",
"keybinding.group.system": "Sistema",
"keybinding.action.dictation": "Ditado",
"keybinding.action.dictation.desc": "Grava enquanto a tecla estiver pressionada.",
"keybinding.action.handsFree": "Modo de um toque",
"keybinding.action.handsFree.desc": "Pressione duas vezes para alternar.",
"keybinding.action.command": "Comandos de voz",
"keybinding.action.command.desc": "Executa um comando por voz.",
"keybinding.action.caption": "Legendas ao vivo",
"keybinding.action.caption.desc": "Ativa ou desativa as legendas ao vivo.",
"keybinding.action.historyPopup": "Pop-up de histórico",
"keybinding.action.historyPopup.desc": "Abre o pop-up de histórico.",
"keybinding.action.commandPopup": "Pop-up de comandos",
"keybinding.action.commandPopup.desc": "Abre o pop-up de comandos.",
"keybinding.reject.unknownKey": "Esta tecla não é compatível",
"keybinding.reject.systemReserved": "É um atalho reservado pelo sistema",
"keybinding.reject.modifierRequired": "Precisa ser combinada com um modificador (Ctrl/Alt/Shift)",
"keybinding.disabled.mouseLeft": "O clique esquerdo é usado em toda a interface, por isso não pode ser vinculado",
"keybinding.warning.mousePassthrough": "Os botões do mouse continuam executando também a ação original",
"keybinding.ui.add": "Adicionar",
"keybinding.ui.remove": "Excluir",
"keybinding.ui.reset": "Restaurar padrão",
"keybinding.ui.tabRecord": "Pressionar tecla",
"keybinding.ui.tabList": "Escolher da lista",
"keybinding.ui.search": "Pesquisar teclas",
"keybinding.ui.searchEmpty": "Nenhum resultado encontrado",
"keybinding.ui.pressKeys": "Pressione a tecla ou o botão do mouse desejado",
"keybinding.ui.noBindings": "Nenhum atalho definido",
"keybinding.ui.conflictWith": "Conflita com {{action}}",
"keybinding.ui.modifiers": "Modificadores",
"keybinding.ui.selectedKey": "Tecla selecionada",
"keybinding.ui.sectionVoice": "Voz",
"keybinding.ui.sectionWindow": "Janelas / Pop-ups",
"keybinding.ui.duplicate": "Este atalho já foi adicionado",
"keybinding.ui.holdMode": "Manter",
"keybinding.ui.doublePress": "Pressão dupla",
"keybinding.ui.pickerTitle": "Configurar atalho",
"keybinding.ui.pressToRecord": "Pressione para iniciar a gravação",
"keybinding.ui.recordHint": "Insira uma combinação (ex: Ctrl+Shift+Q) ou uma tecla única (ex: F5)",
"keybinding.ui.recordAgain": "Inserir novamente",
"keybinding.ui.saveFailed": "Não foi possível salvar o atalho",
"keybinding.ui.globalEnabled": "Usar atalhos globais"
}

View file

@ -270,5 +270,60 @@
"mobile.works.historyDescription": "Поиск и управление расшифровками записей и импорта.",
"mobile.meetings.templateEmpty": "Нет доступных шаблонов документов встреч. Можно начать встречу без шаблона.",
"mobile.meetings.templateNone": "Начать без шаблона",
"mobile.meetings.templateNoneDesc": "Шаблон можно выбрать при создании документов"
"mobile.meetings.templateNoneDesc": "Шаблон можно выбрать при создании документов",
"keybinding.mouse.left": "Левая кнопка мыши",
"keybinding.mouse.right": "Правая кнопка мыши",
"keybinding.mouse.middle": "Средняя кнопка мыши",
"keybinding.mouse.back": "Кнопка мыши «Назад»",
"keybinding.mouse.forward": "Кнопка мыши «Вперёд»",
"keybinding.group.mouse": "Мышь",
"keybinding.group.modifier": "Модификаторы",
"keybinding.group.function": "Функциональные клавиши",
"keybinding.group.letter": "Буквы",
"keybinding.group.digit": "Цифры",
"keybinding.group.numpad": "Цифровой блок",
"keybinding.group.navigation": "Навигация",
"keybinding.group.editing": "Редактирование",
"keybinding.group.punctuation": "Символы",
"keybinding.group.system": "Система",
"keybinding.action.dictation": "Диктовка",
"keybinding.action.dictation.desc": "Записывает, пока клавиша удерживается.",
"keybinding.action.handsFree": "Режим одного нажатия",
"keybinding.action.handsFree.desc": "Нажмите дважды для переключения.",
"keybinding.action.command": "Голосовые команды",
"keybinding.action.command.desc": "Выполняет команду голосом.",
"keybinding.action.caption": "Живые субтитры",
"keybinding.action.caption.desc": "Включает или выключает живые субтитры.",
"keybinding.action.historyPopup": "Окно истории",
"keybinding.action.historyPopup.desc": "Открывает всплывающее окно истории.",
"keybinding.action.commandPopup": "Окно команд",
"keybinding.action.commandPopup.desc": "Открывает всплывающее окно команд.",
"keybinding.reject.unknownKey": "Эта клавиша не поддерживается",
"keybinding.reject.systemReserved": "Это системное сочетание клавиш",
"keybinding.reject.modifierRequired": "Нужно сочетать с модификатором (Ctrl/Alt/Shift)",
"keybinding.disabled.mouseLeft": "Левый клик используется для всех действий на экране, поэтому его нельзя назначить",
"keybinding.warning.mousePassthrough": "Кнопки мыши при этом продолжают выполнять и своё обычное действие",
"keybinding.ui.add": "Добавить",
"keybinding.ui.remove": "Удалить",
"keybinding.ui.reset": "Сбросить по умолчанию",
"keybinding.ui.tabRecord": "Нажать клавишу",
"keybinding.ui.tabList": "Выбрать из списка",
"keybinding.ui.search": "Поиск клавиш",
"keybinding.ui.searchEmpty": "Ничего не найдено",
"keybinding.ui.pressKeys": "Нажмите нужную клавишу или кнопку мыши",
"keybinding.ui.noBindings": "Сочетание не задано",
"keybinding.ui.conflictWith": "Конфликтует с «{{action}}»",
"keybinding.ui.modifiers": "Модификаторы",
"keybinding.ui.selectedKey": "Выбранная клавиша",
"keybinding.ui.sectionVoice": "Голос",
"keybinding.ui.sectionWindow": "Окна / всплывающие окна",
"keybinding.ui.duplicate": "Это сочетание уже добавлено",
"keybinding.ui.holdMode": "Удержание",
"keybinding.ui.doublePress": "Двойное нажатие",
"keybinding.ui.pickerTitle": "Настройка горячей клавиши",
"keybinding.ui.pressToRecord": "Нажмите для начала записи",
"keybinding.ui.recordHint": "Введите сочетание клавиш (например: Ctrl+Shift+Q) или одну клавишу (например: F5)",
"keybinding.ui.recordAgain": "Ввести снова",
"keybinding.ui.saveFailed": "Не удалось сохранить сочетание клавиш",
"keybinding.ui.globalEnabled": "Использовать глобальные горячие клавиши"
}

View file

@ -270,5 +270,60 @@
"mobile.works.historyDescription": "ค้นหาและจัดการบทถอดความจากการบันทึกเสียงและการนำเข้า",
"mobile.meetings.templateEmpty": "ไม่มีเทมเพลตเอกสารประชุมที่ใช้ได้ เริ่มประชุมโดยไม่ใช้เทมเพลตได้",
"mobile.meetings.templateNone": "เริ่มโดยไม่ใช้เทมเพลต",
"mobile.meetings.templateNoneDesc": "เลือกเทมเพลตภายหลังเมื่อสร้างเอกสาร"
"mobile.meetings.templateNoneDesc": "เลือกเทมเพลตภายหลังเมื่อสร้างเอกสาร",
"keybinding.mouse.left": "ปุ่มซ้ายของเมาส์",
"keybinding.mouse.right": "ปุ่มขวาของเมาส์",
"keybinding.mouse.middle": "ปุ่มกลางของเมาส์",
"keybinding.mouse.back": "ปุ่มย้อนกลับของเมาส์",
"keybinding.mouse.forward": "ปุ่มไปข้างหน้าของเมาส์",
"keybinding.group.mouse": "เมาส์",
"keybinding.group.modifier": "ปุ่มปรับแต่ง",
"keybinding.group.function": "ปุ่มฟังก์ชัน",
"keybinding.group.letter": "ตัวอักษร",
"keybinding.group.digit": "ตัวเลข",
"keybinding.group.numpad": "แป้นตัวเลข",
"keybinding.group.navigation": "การนำทาง",
"keybinding.group.editing": "การแก้ไข",
"keybinding.group.punctuation": "สัญลักษณ์",
"keybinding.group.system": "ระบบ",
"keybinding.action.dictation": "การบอกเล่า",
"keybinding.action.dictation.desc": "บันทึกเสียงขณะที่กดปุ่มค้างไว้",
"keybinding.action.handsFree": "โหมดสัมผัสเดียว",
"keybinding.action.handsFree.desc": "กดสองครั้งเพื่อเปิดหรือปิด",
"keybinding.action.command": "คำสั่งเสียง",
"keybinding.action.command.desc": "สั่งงานด้วยเสียง",
"keybinding.action.caption": "คำบรรยายสด",
"keybinding.action.caption.desc": "เปิดหรือปิดคำบรรยายสด",
"keybinding.action.historyPopup": "ป๊อปอัปประวัติ",
"keybinding.action.historyPopup.desc": "เปิดป๊อปอัปประวัติ",
"keybinding.action.commandPopup": "ป๊อปอัปคำสั่ง",
"keybinding.action.commandPopup.desc": "เปิดป๊อปอัปคำสั่ง",
"keybinding.reject.unknownKey": "ไม่รองรับปุ่มนี้",
"keybinding.reject.systemReserved": "เป็นปุ่มลัดที่ระบบสงวนไว้",
"keybinding.reject.modifierRequired": "ต้องใช้ร่วมกับปุ่มปรับแต่ง (Ctrl/Alt/Shift)",
"keybinding.disabled.mouseLeft": "คลิกซ้ายถูกใช้กับการควบคุมหน้าจอทั้งหมด จึงกำหนดเป็นปุ่มลัดไม่ได้",
"keybinding.warning.mousePassthrough": "ปุ่มเมาส์จะยังทำงานตามการทำงานเดิมไปพร้อมกันด้วย",
"keybinding.ui.add": "เพิ่ม",
"keybinding.ui.remove": "ลบ",
"keybinding.ui.reset": "คืนค่าเริ่มต้น",
"keybinding.ui.tabRecord": "กดปุ่มเอง",
"keybinding.ui.tabList": "เลือกจากรายการ",
"keybinding.ui.search": "ค้นหาปุ่ม",
"keybinding.ui.searchEmpty": "ไม่พบผลการค้นหา",
"keybinding.ui.pressKeys": "กดปุ่มหรือปุ่มเมาส์ที่ต้องการ",
"keybinding.ui.noBindings": "ยังไม่ได้ตั้งปุ่มลัด",
"keybinding.ui.conflictWith": "ซ้ำกับ {{action}}",
"keybinding.ui.modifiers": "ปุ่มปรับแต่ง",
"keybinding.ui.selectedKey": "ปุ่มที่เลือก",
"keybinding.ui.sectionVoice": "เสียง",
"keybinding.ui.sectionWindow": "หน้าต่าง / ป๊อปอัป",
"keybinding.ui.duplicate": "เพิ่มปุ่มลัดนี้ไว้แล้ว",
"keybinding.ui.holdMode": "กดค้าง",
"keybinding.ui.doublePress": "กดสองครั้ง",
"keybinding.ui.pickerTitle": "การตั้งค่าปุ่มลัด",
"keybinding.ui.pressToRecord": "กดเพื่อเริ่มบันทึก",
"keybinding.ui.recordHint": "กรอกปุ่มลัด (เช่น: Ctrl+Shift+Q) หรือปุ่มเดี่ยว (เช่น: F5)",
"keybinding.ui.recordAgain": "กรอกใหม่",
"keybinding.ui.saveFailed": "บันทึกปุ่มลัดไม่สำเร็จ",
"keybinding.ui.globalEnabled": "ใช้ปุ่มลัดส่วนกลาง"
}

View file

@ -270,5 +270,60 @@
"mobile.works.historyDescription": "Tìm kiếm và quản lý bản chép lời từ bản ghi và tệp nhập.",
"mobile.meetings.templateEmpty": "Không có mẫu tài liệu họp nào. Bạn có thể bắt đầu cuộc họp mà không cần mẫu.",
"mobile.meetings.templateNone": "Bắt đầu không cần mẫu",
"mobile.meetings.templateNoneDesc": "Chọn mẫu sau khi khi tạo tài liệu"
"mobile.meetings.templateNoneDesc": "Chọn mẫu sau khi khi tạo tài liệu",
"keybinding.mouse.left": "Chuột trái",
"keybinding.mouse.right": "Chuột phải",
"keybinding.mouse.middle": "Chuột giữa",
"keybinding.mouse.back": "Nút Lùi của chuột",
"keybinding.mouse.forward": "Nút Tiến của chuột",
"keybinding.group.mouse": "Chuột",
"keybinding.group.modifier": "Phím bổ trợ",
"keybinding.group.function": "Phím chức năng",
"keybinding.group.letter": "Chữ cái",
"keybinding.group.digit": "Chữ số",
"keybinding.group.numpad": "Bàn phím số",
"keybinding.group.navigation": "Điều hướng",
"keybinding.group.editing": "Chỉnh sửa",
"keybinding.group.punctuation": "Ký hiệu",
"keybinding.group.system": "Hệ thống",
"keybinding.action.dictation": "Chính tả",
"keybinding.action.dictation.desc": "Ghi âm trong khi giữ phím.",
"keybinding.action.handsFree": "Chế độ một chạm",
"keybinding.action.handsFree.desc": "Nhấn hai lần để bật hoặc tắt.",
"keybinding.action.command": "Lệnh thoại",
"keybinding.action.command.desc": "Thực hiện lệnh bằng giọng nói.",
"keybinding.action.caption": "Phụ đề trực tiếp",
"keybinding.action.caption.desc": "Bật hoặc tắt phụ đề trực tiếp.",
"keybinding.action.historyPopup": "Cửa sổ lịch sử",
"keybinding.action.historyPopup.desc": "Mở cửa sổ bật lên lịch sử.",
"keybinding.action.commandPopup": "Cửa sổ lệnh",
"keybinding.action.commandPopup.desc": "Mở cửa sổ bật lên lệnh.",
"keybinding.reject.unknownKey": "Phím này không được hỗ trợ",
"keybinding.reject.systemReserved": "Đây là phím tắt dành riêng cho hệ thống",
"keybinding.reject.modifierRequired": "Phải kết hợp với phím bổ trợ (Ctrl/Alt/Shift)",
"keybinding.disabled.mouseLeft": "Nhấp chuột trái được dùng cho mọi thao tác trên màn hình nên không thể gán",
"keybinding.warning.mousePassthrough": "Các nút chuột vẫn đồng thời thực hiện hành động gốc của chúng",
"keybinding.ui.add": "Thêm",
"keybinding.ui.remove": "Xóa",
"keybinding.ui.reset": "Khôi phục mặc định",
"keybinding.ui.tabRecord": "Nhấn phím",
"keybinding.ui.tabList": "Chọn từ danh sách",
"keybinding.ui.search": "Tìm phím",
"keybinding.ui.searchEmpty": "Không có kết quả tìm kiếm",
"keybinding.ui.pressKeys": "Hãy nhấn phím hoặc nút chuột bạn muốn",
"keybinding.ui.noBindings": "Chưa đặt phím tắt",
"keybinding.ui.conflictWith": "Trùng với {{action}}",
"keybinding.ui.modifiers": "Phím bổ trợ",
"keybinding.ui.selectedKey": "Phím đã chọn",
"keybinding.ui.sectionVoice": "Giọng nói",
"keybinding.ui.sectionWindow": "Cửa sổ / Cửa sổ bật lên",
"keybinding.ui.duplicate": "Phím tắt này đã được thêm",
"keybinding.ui.holdMode": "Giữ phím",
"keybinding.ui.doublePress": "Nhấn hai lần",
"keybinding.ui.pickerTitle": "Cài đặt phím tắt",
"keybinding.ui.pressToRecord": "Nhấn để bắt đầu ghi âm",
"keybinding.ui.recordHint": "Nhập tổ hợp phím (ví dụ: Ctrl+Shift+Q) hoặc phím đơn (ví dụ: F5)",
"keybinding.ui.recordAgain": "Nhập lại",
"keybinding.ui.saveFailed": "Không thể lưu phím tắt",
"keybinding.ui.globalEnabled": "Bật phím tắt toàn cục"
}

View file

@ -270,5 +270,60 @@
"mobile.works.historyDescription": "搜尋和管理錄音與匯入的逐字稿。",
"mobile.meetings.templateEmpty": "沒有可用的會議文件範本。可以不使用範本開始會議。",
"mobile.meetings.templateNone": "不使用範本開始",
"mobile.meetings.templateNoneDesc": "稍後產生文件時再選擇範本"
"mobile.meetings.templateNoneDesc": "稍後產生文件時再選擇範本",
"keybinding.mouse.left": "滑鼠左鍵",
"keybinding.mouse.right": "滑鼠右鍵",
"keybinding.mouse.middle": "滑鼠中鍵",
"keybinding.mouse.back": "滑鼠上一頁鍵",
"keybinding.mouse.forward": "滑鼠下一頁鍵",
"keybinding.group.mouse": "滑鼠",
"keybinding.group.modifier": "輔助鍵",
"keybinding.group.function": "功能鍵",
"keybinding.group.letter": "字母",
"keybinding.group.digit": "數字",
"keybinding.group.numpad": "數字鍵台",
"keybinding.group.navigation": "導覽",
"keybinding.group.editing": "編輯",
"keybinding.group.punctuation": "符號",
"keybinding.group.system": "系統",
"keybinding.action.dictation": "聽寫",
"keybinding.action.dictation.desc": "按住期間進行錄音。",
"keybinding.action.handsFree": "一鍵模式",
"keybinding.action.handsFree.desc": "按兩下切換開關。",
"keybinding.action.command": "語音指令",
"keybinding.action.command.desc": "以語音執行指令。",
"keybinding.action.caption": "即時字幕",
"keybinding.action.caption.desc": "開啟或關閉即時字幕。",
"keybinding.action.historyPopup": "歷史記錄快顯視窗",
"keybinding.action.historyPopup.desc": "開啟歷史記錄快顯視窗。",
"keybinding.action.commandPopup": "指令快顯視窗",
"keybinding.action.commandPopup.desc": "開啟指令快顯視窗。",
"keybinding.reject.unknownKey": "不支援這個按鍵",
"keybinding.reject.systemReserved": "這是系統保留的快速鍵",
"keybinding.reject.modifierRequired": "必須與輔助鍵(Ctrl/Alt/Shift)搭配使用",
"keybinding.disabled.mouseLeft": "左鍵用於所有畫面操作,無法綁定",
"keybinding.warning.mousePassthrough": "滑鼠按鍵仍會一併執行原本的動作",
"keybinding.ui.add": "新增",
"keybinding.ui.remove": "刪除",
"keybinding.ui.reset": "回復預設值",
"keybinding.ui.tabRecord": "直接輸入",
"keybinding.ui.tabList": "從清單選擇",
"keybinding.ui.search": "搜尋按鍵",
"keybinding.ui.searchEmpty": "沒有搜尋結果",
"keybinding.ui.pressKeys": "請按下想使用的按鍵或滑鼠按鍵",
"keybinding.ui.noBindings": "未設定快速鍵",
"keybinding.ui.conflictWith": "與{{action}}重複",
"keybinding.ui.modifiers": "輔助鍵",
"keybinding.ui.selectedKey": "已選按鍵",
"keybinding.ui.sectionVoice": "語音",
"keybinding.ui.sectionWindow": "視窗 / 快顯視窗",
"keybinding.ui.duplicate": "這個快速鍵已經新增過了",
"keybinding.ui.holdMode": "長按",
"keybinding.ui.doublePress": "按兩下",
"keybinding.ui.pickerTitle": "快速鍵設定",
"keybinding.ui.pressToRecord": "按下開始錄音",
"keybinding.ui.recordHint": "請輸入組合鍵(例如:Ctrl+Shift+Q)或單一鍵(例如:F5)",
"keybinding.ui.recordAgain": "重新輸入",
"keybinding.ui.saveFailed": "無法儲存快速鍵",
"keybinding.ui.globalEnabled": "啟用全域快速鍵"
}

View file

@ -270,5 +270,60 @@
"mobile.works.historyDescription": "搜索和管理录音与导入的转写记录。",
"mobile.meetings.templateEmpty": "没有可用的会议文档模板。可以不使用模板开始会议。",
"mobile.meetings.templateNone": "不使用模板开始",
"mobile.meetings.templateNoneDesc": "稍后生成文档时再选择模板"
"mobile.meetings.templateNoneDesc": "稍后生成文档时再选择模板",
"keybinding.mouse.left": "鼠标左键",
"keybinding.mouse.right": "鼠标右键",
"keybinding.mouse.middle": "鼠标中键",
"keybinding.mouse.back": "鼠标后退键",
"keybinding.mouse.forward": "鼠标前进键",
"keybinding.group.mouse": "鼠标",
"keybinding.group.modifier": "修饰键",
"keybinding.group.function": "功能键",
"keybinding.group.letter": "字母",
"keybinding.group.digit": "数字",
"keybinding.group.numpad": "小键盘",
"keybinding.group.navigation": "导航",
"keybinding.group.editing": "编辑",
"keybinding.group.punctuation": "符号",
"keybinding.group.system": "系统",
"keybinding.action.dictation": "听写",
"keybinding.action.dictation.desc": "按住期间进行录音。",
"keybinding.action.handsFree": "一触即发模式",
"keybinding.action.handsFree.desc": "按两下切换开关。",
"keybinding.action.command": "语音命令",
"keybinding.action.command.desc": "用语音执行命令。",
"keybinding.action.caption": "实时字幕",
"keybinding.action.caption.desc": "开启或关闭实时字幕。",
"keybinding.action.historyPopup": "历史记录弹窗",
"keybinding.action.historyPopup.desc": "打开历史记录弹窗。",
"keybinding.action.commandPopup": "命令弹窗",
"keybinding.action.commandPopup.desc": "打开命令弹窗。",
"keybinding.reject.unknownKey": "不支持该按键",
"keybinding.reject.systemReserved": "这是系统保留的快捷键",
"keybinding.reject.modifierRequired": "必须与修饰键(Ctrl/Alt/Shift)组合使用",
"keybinding.disabled.mouseLeft": "左键用于所有界面操作,无法绑定",
"keybinding.warning.mousePassthrough": "鼠标按键仍会同时执行其原本的操作",
"keybinding.ui.add": "添加",
"keybinding.ui.remove": "删除",
"keybinding.ui.reset": "恢复默认",
"keybinding.ui.tabRecord": "直接输入",
"keybinding.ui.tabList": "从列表选择",
"keybinding.ui.search": "搜索按键",
"keybinding.ui.searchEmpty": "没有搜索结果",
"keybinding.ui.pressKeys": "请按下想要使用的按键或鼠标按键",
"keybinding.ui.noBindings": "未设置快捷键",
"keybinding.ui.conflictWith": "与{{action}}冲突",
"keybinding.ui.modifiers": "修饰键",
"keybinding.ui.selectedKey": "已选按键",
"keybinding.ui.sectionVoice": "语音",
"keybinding.ui.sectionWindow": "窗口 / 弹窗",
"keybinding.ui.duplicate": "该快捷键已添加",
"keybinding.ui.holdMode": "长按",
"keybinding.ui.doublePress": "按两下",
"keybinding.ui.pickerTitle": "快捷键设置",
"keybinding.ui.pressToRecord": "按下开始录音",
"keybinding.ui.recordHint": "请输入组合键(例如:Ctrl+Shift+Q)或单个键(例如:F5)",
"keybinding.ui.recordAgain": "重新输入",
"keybinding.ui.saveFailed": "无法保存快捷键",
"keybinding.ui.globalEnabled": "启用全局快捷键"
}