Skip to content
2 changes: 2 additions & 0 deletions src/core/hotkey/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const hotkeyCommandList = [
'playPauseAudio',
'seekForward',
'volumeDown',
'delayTestTap',

'copy',
'cut',
Expand Down Expand Up @@ -96,6 +97,7 @@ export const getDefaultHotkeyMap = () =>
seekForward: k('ArrowRight'),
volumeUp: k('ArrowUp'),
volumeDown: k('ArrowDown'),
delayTestTap: k('g'),
undo: k(Ctrl, 'z'),
redo: [k(Ctrl, 'y'), k(Ctrl, Shift, 'z')],
find: k(Ctrl, 'f'),
Expand Down
33 changes: 26 additions & 7 deletions src/core/pref/persistence.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import stableStringify from 'json-stable-stringify'
import { omit } from 'lodash-es'

import { getDefaultHotkeyMap } from '@core/hotkey'
import { getDefaultHotkeyMap, isHotkeyMatch } from '@core/hotkey'
import { reservedHotkeyCommands } from '@core/hotkey/schema'
import type { HotKey } from '@core/hotkey/types'

import { type PreferenceSchema, getDefaultPref } from './schema'

const STORAGE_KEY = 'amll_editor:preference'
const PREF_VERSION = 1
const PREF_VERSION = 2

const LEGACY_DELAY_TEST_TAP: HotKey.Key = {
code: 'Space',
ctrl: false,
alt: false,
shift: false,
}

interface PersistedPref {
appVersion: string
Expand All @@ -20,15 +28,26 @@ export function loadPreference(): PreferenceSchema {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return getDefaultPref()
const parsed = JSON.parse(raw) as PersistedPref
if (parsed.prefVersion > PREF_VERSION)
const prefVersion = parsed.prefVersion ?? 0
if (prefVersion > PREF_VERSION)
console.warn(
`Found preference version ${parsed.prefVersion}, newer than current version ${PREF_VERSION}.`,
`Found preference version ${prefVersion}, newer than current version ${PREF_VERSION}.`,
)
if (parsed.data.hotkeyMap)
parsed.data.hotkeyMap = {
...getDefaultHotkeyMap(),
if (parsed.data.hotkeyMap) {
const defaultHotkeyMap = getDefaultHotkeyMap()
const hotkeyMap = {
...defaultHotkeyMap,
...omit(parsed.data.hotkeyMap, reservedHotkeyCommands),
}
if (
prefVersion < PREF_VERSION &&
parsed.data.hotkeyMap.delayTestTap?.length === 1 &&
isHotkeyMatch(parsed.data.hotkeyMap.delayTestTap[0], LEGACY_DELAY_TEST_TAP)
) {
hotkeyMap.delayTestTap = defaultHotkeyMap.delayTestTap
}
parsed.data.hotkeyMap = hotkeyMap
}
return {
...getDefaultPref(),
...parsed.data,
Expand Down
2 changes: 2 additions & 0 deletions src/core/pref/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface PreferenceSchema {
macStyleShortcuts: boolean
hotkeyMap: HotKey.Map
audioSeekingStepMs: number
latencyTestBpm: number
// Timing
globalLatencyMs: number
alwaysIgnoreBackground: boolean
Expand Down Expand Up @@ -43,6 +44,7 @@ export const getDefaultPref = (): PreferenceSchema => ({
macStyleShortcuts: isAppleDevice(),
hotkeyMap: getDefaultHotkeyMap(),
audioSeekingStepMs: 5000,
latencyTestBpm: 120,
globalLatencyMs: 0,
alwaysIgnoreBackground: false,
hideLineTiming: true,
Expand Down
19 changes: 19 additions & 0 deletions src/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ const en = {
timeShift: {
groupLabel: 'Time Shift',
delayTest: 'Delay Test',
delayTestDesc: 'Open the latency test dialog to measure tap timing against the beep track.',
delay: 'Delay',
batchTimeShift: 'Batch Shift',
batchTimeShiftDesc:
Expand Down Expand Up @@ -287,6 +288,8 @@ const en = {
macStyleShortcutsDesc: 'Display shortcuts using ⌘, ⌥ symbols etc.',
audioSeekingStepMs: 'Seek step size',
audioSeekingStepMsDesc: 'Time to jump when using hotkeys (ms)',
latencyTestBpm: 'Delay test BPM',
latencyTestBpmDesc: 'Beep rate used by the latency test dialog',
swapTranslateRoman: 'Swap translation & romanization panels',
swapTranslateRomanDesc: 'Place romanization panel on the left',
hideTranslateRoman: 'Hide translation & romanization panels',
Expand Down Expand Up @@ -613,6 +616,7 @@ const en = {
playPauseAudio: 'Play / Pause',
seekForward: 'Seek Forward',
volumeDown: 'Volume Down',
delayTestTap: 'Delay Test Tap',
},
keyNames: {
space: 'Space',
Expand Down Expand Up @@ -741,6 +745,21 @@ const en = {
applyToLine: 'Apply to Selected Lines',
applyToAll: 'Apply to All',
},
delayTestDialog: {
header: 'Delay Test',
description: 'Press the configured key on every beep to measure input latency.',
tapHint: 'Press',
bpmLabel: 'BPM',
signHint: 'Positive = early, negative = late',
current: 'Current',
fastest: 'Fastest',
slowest: 'Slowest',
noSamples: 'No taps yet',
start: 'Start',
stop: 'Stop',
applyCurrent: 'Apply Current',
applyAverage: 'Apply Average',
},
consoleArt,
} as const satisfies Translations

Expand Down
140 changes: 140 additions & 0 deletions src/i18n/i18n-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,10 @@ type RootTranslation = {
* 延​迟​测​试
*/
delayTest: string
/**
* 打​开​延​迟​测​试​对​话​框​,​测​量​按​键​与​蜂​鸣​之​间​的​延​迟​。
*/
delayTestDesc: string
/**
* 延​迟
*/
Expand Down Expand Up @@ -832,6 +836,14 @@ type RootTranslation = {
* 按​键​快​进​或​快​退​时​跳​转​的​时​长​ ​(​毫​秒​)
*/
audioSeekingStepMsDesc: string
/**
* 延​迟​测​试​ ​B​P​M
*/
latencyTestBpm: string
/**
* 延​迟​测​试​对​话​框​使​用​的​蜂​鸣​节​拍
*/
latencyTestBpmDesc: string
/**
* 交​换​翻​译​与​音​译​框​位​置
*/
Expand Down Expand Up @@ -1768,6 +1780,10 @@ type RootTranslation = {
* 减​小​音​量
*/
volumeDown: string
/**
* 延​迟​测​试​按​键
*/
delayTestTap: string
}
keyNames: {
/**
Expand Down Expand Up @@ -2098,6 +2114,60 @@ type RootTranslation = {
*/
applyToAll: string
}
delayTestDialog: {
/**
* 延​迟​测​试
*/
header: string
/**
* 在​每​次​蜂​鸣​时​按​下​配​置​的​按​键​,​以​测​量​输​入​延​迟​。
*/
description: string
/**
* 按​下
*/
tapHint: string
/**
* B​P​M
*/
bpmLabel: string
/**
* 正​值​表​示​更​快​,​负​值​表​示​更​慢
*/
signHint: string
/**
* 当​前
*/
current: string
/**
* 最​快
*/
fastest: string
/**
* 最​慢
*/
slowest: string
/**
* 尚​无​按​键​记​录
*/
noSamples: string
/**
* 开​始
*/
start: string
/**
* 结​束
*/
stop: string
/**
* 应​用​当​前​值
*/
applyCurrent: string
/**
* 应​用​平​均​值
*/
applyAverage: string
}
/**
* ╭​─​─​─​─​─​─​─​─​─​─​╮​ ​ ​ ​ ​ ​ ​ ​╶​╴​ ​ ​ ​ ​┌​─​╴​ ​ ​╶​─​┐​┌​─​┐​ ​ ​ ​ ​┌​─​┐​ ​ ​ ​ ​ ​ ​┌​─​─​─​─​─​┐​ ​ ​ ​ ​┌​─​┐​┌​─​┐​
​│​ ​━​━​━​━​━​━​ ​ ​ ​│​ ​ ​ ​ ​ ​ ​╱​ ​ ​╲​ ​ ​ ​│​ ​ ​╲​╱​ ​ ​│​│​ ​│​ ​ ​ ​ ​│​ ​│​ ​ ​ ​ ​ ​ ​│​ ​┌​─​─​─​┘​ ​ ​ ​ ​│​ ​│​└​─​┘​┌​─​┐​
Expand Down Expand Up @@ -2529,6 +2599,10 @@ export type TranslationFunctions = {
* 延迟测试
*/
delayTest: () => LocalizedString
/**
* 打开延迟测试对话框,测量按键与蜂鸣之间的延迟。
*/
delayTestDesc: () => LocalizedString
/**
* 延迟
*/
Expand Down Expand Up @@ -2938,6 +3012,14 @@ export type TranslationFunctions = {
* 按键快进或快退时跳转的时长 (毫秒)
*/
audioSeekingStepMsDesc: () => LocalizedString
/**
* 延迟测试 BPM
*/
latencyTestBpm: () => LocalizedString
/**
* 延迟测试对话框使用的蜂鸣节拍
*/
latencyTestBpmDesc: () => LocalizedString
/**
* 交换翻译与音译框位置
*/
Expand Down Expand Up @@ -3870,6 +3952,10 @@ export type TranslationFunctions = {
* 减小音量
*/
volumeDown: () => LocalizedString
/**
* 延迟测试按键
*/
delayTestTap: () => LocalizedString
}
keyNames: {
/**
Expand Down Expand Up @@ -4199,6 +4285,60 @@ export type TranslationFunctions = {
*/
applyToAll: () => LocalizedString
}
delayTestDialog: {
/**
* 延迟测试
*/
header: () => LocalizedString
/**
* 在每次蜂鸣时按下配置的按键,以测量输入延迟。
*/
description: () => LocalizedString
/**
* 按下
*/
tapHint: () => LocalizedString
/**
* BPM
*/
bpmLabel: () => LocalizedString
/**
* 正值表示更快,负值表示更慢
*/
signHint: () => LocalizedString
/**
* 当前
*/
current: () => LocalizedString
/**
* 最快
*/
fastest: () => LocalizedString
/**
* 最慢
*/
slowest: () => LocalizedString
/**
* 尚无按键记录
*/
noSamples: () => LocalizedString
/**
* 开始
*/
start: () => LocalizedString
/**
* 结束
*/
stop: () => LocalizedString
/**
* 应用当前值
*/
applyCurrent: () => LocalizedString
/**
* 应用平均值
*/
applyAverage: () => LocalizedString
}
/**
* ╭──────────╮ ╶╴ ┌─╴ ╶─┐┌─┐ ┌─┐ ┌─────┐ ┌─┐┌─┐
│ ━━━━━━ │ ╱ ╲ │ ╲╱ ││ │ │ │ │ ┌───┘ │ │└─┘┌─┐
Expand Down
19 changes: 19 additions & 0 deletions src/i18n/zh-hans/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ const zhHans = {
timeShift: {
groupLabel: '时移',
delayTest: '延迟测试',
delayTestDesc: '打开延迟测试对话框,测量按键与蜂鸣之间的延迟。',
delay: '延迟',
batchTimeShift: '批量时移',
batchTimeShiftDesc: '打开批量时移对话框,调整多个音节或行的时间戳。',
Expand Down Expand Up @@ -278,6 +279,8 @@ const zhHans = {
macStyleShortcutsDesc: '使用 ⌘、⌥ 等符号展示组合键',
audioSeekingStepMs: '音频按键跳转步长',
audioSeekingStepMsDesc: '按键快进或快退时跳转的时长 (毫秒)',
latencyTestBpm: '延迟测试 BPM',
latencyTestBpmDesc: '延迟测试对话框使用的蜂鸣节拍',
swapTranslateRoman: '交换翻译与音译框位置',
swapTranslateRomanDesc: '在内容视图将音译框置于左侧,并影响查找顺序',
hideTranslateRoman: '隐藏翻译音译框',
Expand Down Expand Up @@ -591,6 +594,7 @@ const zhHans = {
playPauseAudio: '播放/暂停音频',
seekForward: '快进',
volumeDown: '减小音量',
delayTestTap: '延迟测试按键',
},
keyNames: {
space: '空格',
Expand Down Expand Up @@ -717,6 +721,21 @@ const zhHans = {
applyToLine: '应用到选定行',
applyToAll: '应用到全文',
},
delayTestDialog: {
header: '延迟测试',
description: '在每次蜂鸣时按下配置的按键,以测量输入延迟。',
tapHint: '按下',
bpmLabel: 'BPM',
signHint: '正值表示更快,负值表示更慢',
current: '当前',
fastest: '最快',
slowest: '最慢',
noSamples: '尚无按键记录',
start: '开始',
stop: '结束',
applyCurrent: '应用当前值',
applyAverage: '应用平均值',
},
consoleArt,
} satisfies BaseTranslation

Expand Down
Loading