diff --git a/openless-all/app/src/i18n/en.ts b/openless-all/app/src/i18n/en.ts index 21f48fc1..310d295d 100644 --- a/openless-all/app/src/i18n/en.ts +++ b/openless-all/app/src/i18n/en.ts @@ -10,6 +10,8 @@ export const en: typeof zhCN = { }, common: { loading: 'Loading…', + retry: 'Retry', + settingsLoadFailed: 'Settings load failed', refresh: 'Refresh', clear: 'Clear', copy: 'Copy', diff --git a/openless-all/app/src/i18n/ja.ts b/openless-all/app/src/i18n/ja.ts index 5708460d..d95c18ff 100644 --- a/openless-all/app/src/i18n/ja.ts +++ b/openless-all/app/src/i18n/ja.ts @@ -12,6 +12,8 @@ export const ja: typeof zhCN = { }, common: { loading: '読み込み中…', + retry: '再試行', + settingsLoadFailed: '設定の読み込みに失敗しました', refresh: '更新', clear: 'クリア', copy: 'コピー', diff --git a/openless-all/app/src/i18n/ko.ts b/openless-all/app/src/i18n/ko.ts index 2861d536..c884d308 100644 --- a/openless-all/app/src/i18n/ko.ts +++ b/openless-all/app/src/i18n/ko.ts @@ -12,6 +12,8 @@ export const ko: typeof zhCN = { }, common: { loading: '로딩 중…', + retry: '다시 시도', + settingsLoadFailed: '설정 로드 실패', refresh: '새로고침', clear: '지우기', copy: '복사', diff --git a/openless-all/app/src/i18n/zh-CN.ts b/openless-all/app/src/i18n/zh-CN.ts index c71f2e34..09d77618 100644 --- a/openless-all/app/src/i18n/zh-CN.ts +++ b/openless-all/app/src/i18n/zh-CN.ts @@ -8,6 +8,8 @@ export const zhCN = { }, common: { loading: '加载中…', + retry: '重试', + settingsLoadFailed: '设置加载失败', refresh: '刷新', clear: '清空', copy: '复制', diff --git a/openless-all/app/src/i18n/zh-TW.ts b/openless-all/app/src/i18n/zh-TW.ts index ea1e00e2..1efc827f 100644 --- a/openless-all/app/src/i18n/zh-TW.ts +++ b/openless-all/app/src/i18n/zh-TW.ts @@ -10,6 +10,8 @@ export const zhTW: typeof zhCN = { }, common: { loading: '加載中…', + retry: '重試', + settingsLoadFailed: '設置加載失敗', refresh: '刷新', clear: '清空', copy: '複製', diff --git a/openless-all/app/src/pages/Translation.tsx b/openless-all/app/src/pages/Translation.tsx index bea8deb4..6b173b9c 100644 --- a/openless-all/app/src/pages/Translation.tsx +++ b/openless-all/app/src/pages/Translation.tsx @@ -18,7 +18,7 @@ type SaveState = 'idle' | 'saving' | 'saved' | 'failed'; export function Translation() { const { t } = useTranslation(); - const { prefs, refresh, updatePrefs: savePrefs } = useHotkeySettings(); + const { prefs, loading, error, refresh, updatePrefs: savePrefs } = useHotkeySettings(); const [saveState, setSaveState] = useState('idle'); const [saveMessage, setSaveMessage] = useState(''); const statusTimer = useRef(null); @@ -69,7 +69,34 @@ export function Translation() { desc={t('translation.desc')} /> -
{t('common.loading')}
+ {error ? ( +
+
+ {t('common.settingsLoadFailed')}:{error} +
+ +
+ ) : ( +
{t('common.loading')}
+ )}
); @@ -121,6 +148,46 @@ export function Translation() { />
+ {error && ( +
+ {t('common.settingsLoadFailed')}:{error} + +
+ )} + {saveState !== 'idle' && (
Promise; updatePrefs: ( next: UserPreferences | ((current: UserPreferences) => UserPreferences), @@ -25,18 +26,45 @@ interface HotkeySettingsContextValue { const HotkeySettingsContext = createContext(null); +const errorMessage = (error: unknown) => + String(error instanceof Error ? error.message : error); + export function HotkeySettingsProvider({ children }: { children: ReactNode }) { const [prefs, setPrefs] = useState(null); const [capability, setCapability] = useState(null); const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); const persistQueueRef = useRef>(Promise.resolve()); const latestPrefsRef = useRef(null); const refresh = useCallback(async () => { - const [nextPrefs, nextCapability] = await Promise.all([getSettings(), getHotkeyCapability()]); - setPrefs(nextPrefs); - setCapability(nextCapability); - setLoading(false); + setLoading(true); + setError(null); + try { + const [prefsResult, capabilityResult] = await Promise.allSettled([ + getSettings(), + getHotkeyCapability(), + ]); + let nextError: string | null = null; + if (prefsResult.status === 'fulfilled') { + setPrefs(prefsResult.value); + } else { + console.error('[hotkey-settings] failed to load preferences', prefsResult.reason); + nextError = errorMessage(prefsResult.reason); + } + if (capabilityResult.status === 'fulfilled') { + setCapability(capabilityResult.value); + } else { + console.error('[hotkey-settings] failed to load hotkey capability', capabilityResult.reason); + nextError = errorMessage(capabilityResult.reason); + } + setError(nextError); + } catch (error) { + console.error('[hotkey-settings] failed to refresh hotkey settings', error); + setError(errorMessage(error)); + } finally { + setLoading(false); + } }, []); const queueSetSettings = useCallback((resolveNext: (current: UserPreferences) => UserPreferences) => { @@ -137,10 +165,11 @@ export function HotkeySettingsProvider({ children }: { children: ReactNode }) { hotkey: prefs?.hotkey ?? null, capability, loading, + error, refresh, updatePrefs, }), - [capability, loading, prefs, refresh, updatePrefs], + [capability, error, loading, prefs, refresh, updatePrefs], ); return {children};