feat(desktop): persist i18n language in config

This commit is contained in:
Jim Liu 宝玉
2026-06-05 10:32:26 -07:00
committed by Teknium
parent 4a1907bd10
commit 1d9c3ebae0
21 changed files with 836 additions and 141 deletions
+4 -3
View File
@@ -1,6 +1,7 @@
import { atom } from 'nanostores'
import type { DesktopBootProgress } from '@/global'
import { translateNow } from '@/i18n'
export interface DesktopBootState extends DesktopBootProgress {
visible: boolean
@@ -9,7 +10,7 @@ export interface DesktopBootState extends DesktopBootProgress {
const INITIAL_BOOT_STATE: DesktopBootState = {
error: null,
fakeMode: false,
message: 'Starting Hermes Desktop',
message: translateNow('boot.steps.startingHermesDesktop'),
phase: 'renderer.init',
progress: 2,
running: true,
@@ -61,7 +62,7 @@ export function setDesktopBootStep(step: {
})
}
export function completeDesktopBoot(message = 'Hermes Desktop is ready') {
export function completeDesktopBoot(message = translateNow('boot.ready')) {
const current = $desktopBoot.get()
$desktopBoot.set({
...current,
@@ -80,7 +81,7 @@ export function failDesktopBoot(message: string) {
$desktopBoot.set({
...current,
error: message,
message: `Desktop boot failed: ${message}`,
message: translateNow('boot.desktopBootFailedWithMessage', message),
phase: 'renderer.error',
progress: clampProgress(current.progress),
running: false,
+10 -7
View File
@@ -1,5 +1,7 @@
import { atom } from 'nanostores'
import { translateNow } from '@/i18n'
export type NotificationKind = 'error' | 'warning' | 'info' | 'success'
export interface NotificationAction {
@@ -52,28 +54,29 @@ const ERROR_SUMMARIES: { test: (msg: string) => boolean; summarize: (msg: string
summarize: msg => {
const status = msg.match(/(?:error code|status(?:Code)?)[^\d]*(\d{3})/i)?.[1]
return `OpenAI rejected the API key${status ? ` (${status} invalid_api_key)` : ''}.`
return status
? translateNow('notifications.errors.openaiRejectedApiKeyWithStatus', status)
: translateNow('notifications.errors.openaiRejectedApiKey')
}
},
{
test: msg => /neither voice_tools_openai_key nor openai_api_key is set/i.test(msg),
summarize: () => 'OpenAI TTS needs VOICE_TOOLS_OPENAI_KEY or OPENAI_API_KEY.'
summarize: () => translateNow('notifications.errors.openaiTtsNeedsKey')
},
{
test: msg => /ELEVENLABS_API_KEY not set/i.test(msg) || /ElevenLabs STT API error \(HTTP 401\)/i.test(msg),
summarize: msg =>
/ELEVENLABS_API_KEY not set/i.test(msg)
? 'ElevenLabs STT needs ELEVENLABS_API_KEY.'
: 'ElevenLabs rejected the API key (401).'
? translateNow('notifications.errors.elevenLabsNeedsKey')
: translateNow('notifications.errors.elevenLabsRejectedKey')
},
{
test: msg => /method not allowed/i.test(msg),
summarize: () =>
'The desktop backend rejected that request (405 Method Not Allowed). Try restarting Hermes Desktop.'
summarize: () => translateNow('notifications.errors.methodNotAllowed')
},
{
test: msg => /microphone permission/i.test(msg),
summarize: () => 'Microphone permission was denied.'
summarize: () => translateNow('notifications.errors.microphonePermission')
}
]
+7 -6
View File
@@ -13,6 +13,7 @@ import type {
DesktopUpdateStatus,
DesktopVersionInfo
} from '@/global'
import { translateNow } from '@/i18n'
import { persistString, storedString } from '@/lib/storage'
import { dismissNotification, notify } from '@/store/notifications'
@@ -85,12 +86,12 @@ export function reportBackendContract(contract: number | undefined): void {
}
notify({
action: { label: 'Update Hermes', onClick: () => void applyUpdates() },
action: { label: translateNow('notifications.updateHermes'), onClick: () => void applyUpdates() },
durationMs: 0,
id: SKEW_TOAST_ID,
kind: 'warning',
message: 'Your Hermes backend is older than this desktop build and may not work correctly. Update to align them.',
title: 'Backend out of date'
message: translateNow('notifications.backendOutOfDateMessage'),
title: translateNow('notifications.backendOutOfDateTitle')
})
}
@@ -121,7 +122,7 @@ export function maybeNotifyUpdateAvailable(status: DesktopUpdateStatus | null) {
notify({
action: {
label: "See what's new",
label: translateNow('notifications.seeWhatsNew'),
onClick: () => {
snoozeUpdateToast()
openUpdatesWindow()
@@ -130,9 +131,9 @@ export function maybeNotifyUpdateAvailable(status: DesktopUpdateStatus | null) {
durationMs: 0,
id: UPDATE_TOAST_ID,
kind: 'info',
message: `${behind} new change${behind === 1 ? '' : 's'} available.`,
message: translateNow('notifications.updateReadyMessage', behind),
onDismiss: () => snoozeUpdateToast(),
title: 'Update ready'
title: translateNow('notifications.updateReadyTitle')
})
}