Prepare IDE beta 6 with Turkish native dialogs and map errors

This commit is contained in:
2026-09-06 05:34:34 +03:00
parent 7724d65e92
commit 75afd01b4b
15 changed files with 265 additions and 57 deletions
+29 -29
View File
@@ -261,6 +261,7 @@ import {
resolveOauthRestAuth,
resolveReadinessProbeAuth
} from './native-auth-decisions'
import { nativeDialogCopy } from './native-dialog-copy'
import { localizeNativeMenu, type NativeMenuLocale, resolveNativeMenuLocale } from './native-menu-locale'
import {
nativeRefreshUrl,
@@ -2313,8 +2314,8 @@ async function waitForUpdateToFinish() {
rememberLog(`[updates] detached update finished with manual action (branch ${result.branch}): ${result.message}`)
dialog.showMessageBox({
type: 'warning',
title: 'Hermes update',
message: 'The update finished, but needs one more step',
title: nativeText().update,
message: nativeText().updateMore,
detail: result.message
})
} else if (result && result.ok) {
@@ -2322,8 +2323,8 @@ async function waitForUpdateToFinish() {
} else if (result) {
rememberLog(`[updates] detached update FAILED (exit ${result.exitCode}): ${result.message}`)
dialog.showErrorBox(
'Hermes update did not finish',
`${result.message}\n\nDetails: ${path.join(HERMES_HOME, 'logs', 'desktop-update-handoff.log')}`
nativeText().updateFailed,
`${result.message}\n\n${nativeText().details}: ${path.join(HERMES_HOME, 'logs', 'desktop-update-handoff.log')}`
)
}
} catch (err) {
@@ -6087,11 +6088,11 @@ async function saveImageFromUrl(rawUrl) {
}
const result = await dialog.showSaveDialog(mainWindow, {
title: 'Save Image',
title: nativeText().saveImage,
defaultPath: downloadsDir ? path.join(downloadsDir, fallbackName) : fallbackName,
filters: [
{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg'] },
{ name: 'All Files', extensions: ['*'] }
{ name: nativeText().images, extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg'] },
{ name: nativeText().allFiles, extensions: ['*'] }
]
})
@@ -6756,7 +6757,8 @@ function sendWindowStateChanged(nextIsFullscreen?: boolean, target = mainWindow)
webContents.send('hermes:window-state-changed', state)
}
let nativeMenuLocale: NativeMenuLocale = 'tr'
let nativeUiLocale: NativeMenuLocale = 'tr'
const nativeText = () => nativeDialogCopy(nativeUiLocale)
function buildApplicationMenu() {
const template = []
@@ -6889,7 +6891,7 @@ function buildApplicationMenu() {
submenu: [checkForUpdatesItem]
})
return Menu.buildFromTemplate(localizeNativeMenu(template, nativeMenuLocale, APP_NAME))
return Menu.buildFromTemplate(localizeNativeMenu(template, nativeUiLocale, APP_NAME))
}
function toggleDevTools(window) {
@@ -7179,13 +7181,13 @@ function installDownloadHandling() {
try {
item.setSaveDialogOptions({
title: 'Save File',
title: nativeText().saveFile,
defaultPath: path.join(app.getPath('downloads'), filename),
filters:
extension || /^image\//i.test(item.getMimeType() || '')
? [
{ name: 'Images', extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg'] },
{ name: 'All Files', extensions: ['*'] }
{ name: nativeText().images, extensions: ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg'] },
{ name: nativeText().allFiles, extensions: ['*'] }
]
: undefined
})
@@ -7538,7 +7540,7 @@ function openOauthLoginWindow(baseUrl, { silent = false } = {}) {
win = new BrowserWindow({
width: 520,
height: 720,
title: silent ? 'Connecting to Hermes Cloud agent…' : 'Sign in to Hermes gateway',
title: silent ? nativeText().cloudConnect : nativeText().gatewaySignIn,
autoHideMenuBar: true,
// Silent cascade: start HIDDEN. The auto-SSO 302 chain completes in
// well under a second, so the window normally never needs to show. We
@@ -7967,7 +7969,7 @@ async function finalizeGatewayDownload(res, statusCode, headers, ctx: any = {})
const result = await dialog.showSaveDialog(mainWindow, {
defaultPath: filename,
title: 'Save File'
title: nativeText().saveFile
})
if (result.canceled || !result.filePath) {
@@ -8124,7 +8126,7 @@ async function saveGatewayFileViaDataUrl(
const result = await dialog.showSaveDialog(mainWindow, {
defaultPath: filename,
title: 'Save File'
title: nativeText().saveFile
})
if (result.canceled || !result.filePath) {
@@ -8416,7 +8418,7 @@ function renewPortalAccessSilently() {
width: 520,
height: 720,
show: false,
title: 'Renewing Hermes Cloud session…',
title: nativeText().cloudRenew,
autoHideMenuBar: true,
webPreferences: {
contextIsolation: true,
@@ -8521,7 +8523,7 @@ function openPortalLoginWindow() {
win = new BrowserWindow({
width: 520,
height: 720,
title: 'Sign in to Hermes Cloud',
title: nativeText().cloudSignIn,
autoHideMenuBar: true,
webPreferences: {
contextIsolation: true,
@@ -16767,7 +16769,7 @@ ipcMain.handle('hermes:selectPaths', async (_event, options: any = {}) => {
}
const result = await dialog.showOpenDialog(mainWindow, {
title: options?.title || 'Add context',
title: options?.title || nativeText().addContext,
defaultPath: resolvedDefaultPath,
properties: properties as any,
filters: Array.isArray(options?.filters) ? options.filters : undefined
@@ -16790,7 +16792,7 @@ ipcMain.handle('hermes:writeClipboard', (_event, text) => {
// elsewhere (the backend, for profile archives); this only picks the path.
ipcMain.handle('hermes:selectSavePath', async (_event, options: any = {}) => {
const result = await dialog.showSaveDialog(mainWindow, {
title: options?.title || 'Save',
title: options?.title || nativeText().save,
defaultPath: options?.defaultPath ? String(options.defaultPath) : undefined,
filters: Array.isArray(options?.filters) ? options.filters : undefined
})
@@ -16960,18 +16962,16 @@ ipcMain.on('hermes:titlebar-theme', (_event, payload) => {
// Language stays in backend display.language; this only updates native labels.
ipcMain.on('hermes:ui-language', (event, locale) => {
if (!IS_MAC) {
return
}
const next = resolveNativeMenuLocale(locale, event.sender.id, mainWindow?.webContents.id)
if (next === null || next === nativeMenuLocale) {
if (next === null || next === nativeUiLocale) {
return
}
nativeMenuLocale = next
Menu.setApplicationMenu(buildApplicationMenu())
nativeUiLocale = next
if (IS_MAC) {
Menu.setApplicationMenu(buildApplicationMenu())
}
})
// Pin the native appearance to the app theme (see NATIVE_THEME_CONFIG_PATH).
@@ -17306,7 +17306,7 @@ ipcMain.handle('hermes:setting:defaultProjectDir:set', async (_event, dir) => {
ipcMain.handle('hermes:setting:defaultProjectDir:pick', async () => {
const result = await dialog.showOpenDialog({
title: 'Choose default project directory',
title: nativeText().chooseProjectDirectory,
properties: ['openDirectory', 'createDirectory'],
defaultPath: readDefaultProjectDir() || app.getPath('home')
})
@@ -17866,7 +17866,7 @@ function heldQuitForActiveWork(event: Electron.Event): boolean {
return false
}
const prompt = quitPromptFor(mergeActiveWork(activeWorkByWebContents.values()), isQuittingForHandoff)
const prompt = quitPromptFor(mergeActiveWork(activeWorkByWebContents.values()), isQuittingForHandoff, nativeUiLocale)
const parent = BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0]
if (!prompt || !parent || parent.isDestroyed()) {
@@ -17878,7 +17878,7 @@ function heldQuitForActiveWork(event: Electron.Event): boolean {
void dialog
.showMessageBox(parent, {
buttons: ['Keep Running', 'Quit Anyway'],
buttons: [nativeText().keepRunning, nativeText().quitAnyway],
cancelId: 0,
defaultId: 0,
detail: prompt.detail,
@@ -0,0 +1,45 @@
import type { NativeMenuLocale } from './native-menu-locale'
const en = {
saveImage: 'Save image',
saveFile: 'Save file',
images: 'Images',
allFiles: 'All files',
addContext: 'Add context',
save: 'Save',
chooseProjectDirectory: 'Choose default project directory',
keepRunning: 'Keep running',
quitAnyway: 'Quit anyway',
update: 'AITURK IDE update',
updateMore: 'The update finished, but needs one more step',
updateFailed: 'AITURK IDE update did not finish',
details: 'Details',
cloudConnect: 'Connecting to the cloud agent…',
gatewaySignIn: 'Sign in to the gateway',
cloudRenew: 'Renewing the cloud session…',
cloudSignIn: 'Sign in to the cloud'
}
const tr: typeof en = {
saveImage: 'Görseli kaydet',
saveFile: 'Dosyayı kaydet',
images: 'Görseller',
allFiles: 'Tüm dosyalar',
addContext: 'Bağlam ekle',
save: 'Kaydet',
chooseProjectDirectory: 'Varsayılan proje klasörünü seçin',
keepRunning: 'Çalışmaya devam et',
quitAnyway: 'Yine de çık',
update: 'AITURK IDE güncellemesi',
updateMore: 'Güncelleme tamamlandı, ancak bir adım daha gerekiyor',
updateFailed: 'AITURK IDE güncellemesi tamamlanamadı',
details: 'Ayrıntılar',
cloudConnect: 'Bulut asistanına bağlanılıyor…',
gatewaySignIn: 'Ağ geçidine giriş yapın',
cloudRenew: 'Bulut oturumu yenileniyor…',
cloudSignIn: 'Bulut hesabına giriş yapın'
}
export function nativeDialogCopy(locale: NativeMenuLocale): typeof en {
return locale === 'tr' ? tr : en
}
+17 -3
View File
@@ -38,7 +38,7 @@ test('quitPromptFor names the running chats', () => {
const prompt = quitPromptFor({ count: 2, titles: ['Fix login', 'Ship docs'] }, false)
assert.ok(prompt)
assert.equal(prompt.message, 'Hermes is still working on 2 chats.')
assert.equal(prompt.message, 'AITURK is still working on 2 chats.')
assert.ok(prompt.detail.includes('• Fix login'))
assert.ok(prompt.detail.includes('• Ship docs'))
})
@@ -47,7 +47,7 @@ test('quitPromptFor summarizes past the list cap and counts untitled work', () =
const prompt = quitPromptFor({ count: 9, titles: ['a', 'b', 'c', 'd', 'e', 'f'] }, false)
assert.ok(prompt)
assert.equal(prompt.message, 'Hermes is still working on 9 chats.')
assert.equal(prompt.message, 'AITURK is still working on 9 chats.')
assert.ok(prompt.detail.includes('• d'))
assert.ok(!prompt.detail.includes('• e'))
assert.ok(prompt.detail.includes('• 5 more'))
@@ -57,6 +57,20 @@ test('quitPromptFor speaks singular for one chat', () => {
const prompt = quitPromptFor({ count: 1, titles: [] }, false)
assert.ok(prompt)
assert.equal(prompt.message, 'Hermes is still working on 1 chat.')
assert.equal(prompt.message, 'AITURK is still working on 1 chat.')
assert.ok(prompt.detail.includes('mid-turn'))
})
test('Turkish exit warning preserves custom titles, counts and handoff behavior', () => {
const work = { count: 7, titles: ['Custom English title', 'Türkçe görev'] }
const prompt = quitPromptFor(work, false, 'tr')
assert.ok(prompt)
assert.equal(prompt.message, 'AITURK hâlâ 7 sohbet üzerinde çalışıyor.')
assert.ok(prompt.detail.includes('• Custom English title'))
assert.ok(prompt.detail.includes('• Türkçe görev'))
assert.ok(prompt.detail.includes('• 5 sohbet daha'))
assert.ok(prompt.detail.includes('çalışmasını durdurur'))
assert.equal(quitPromptFor(work, true, 'tr'), null)
assert.equal(quitPromptFor({ count: 0, titles: [] }, false, 'tr'), null)
})
+8 -4
View File
@@ -65,7 +65,7 @@ export interface QuitPrompt {
* are the app replacing itself, not the user walking away, and a modal there
* would strand the detached script waiting on a PID that never exits.
*/
export function quitPromptFor(work: ActiveWork, quittingForHandoff: boolean): null | QuitPrompt {
export function quitPromptFor(work: ActiveWork, quittingForHandoff: boolean, locale: 'en' | 'tr' = 'en'): null | QuitPrompt {
if (quittingForHandoff || work.count < 1) {
return null
}
@@ -75,18 +75,22 @@ export function quitPromptFor(work: ActiveWork, quittingForHandoff: boolean): nu
const lines = listed.map(title => `${title}`)
if (remaining > 0) {
lines.push(remaining === 1 ? '• 1 more' : `${remaining} more`)
lines.push(locale === 'tr' ? `${remaining} sohbet daha` : remaining === 1 ? '• 1 more' : `${remaining} more`)
}
return {
detail: [
lines.join('\n'),
lines.length > 0 ? '' : null,
'Quitting stops the agent mid-turn. Any work it has not finished writing is lost.'
locale === 'tr'
? 'Çıkış yapmak asistanın devam eden çalışmasını durdurur. Henüz yazmayı tamamlamadığı çalışma kaybolur.'
: 'Quitting stops the agent mid-turn. Any work it has not finished writing is lost.'
]
.filter(line => line !== null)
.join('\n')
.trim(),
message: work.count === 1 ? 'Hermes is still working on 1 chat.' : `Hermes is still working on ${work.count} chats.`
message: locale === 'tr'
? `AITURK hâlâ ${work.count} sohbet üzerinde çalışıyor.`
: work.count === 1 ? 'AITURK is still working on 1 chat.' : `AITURK is still working on ${work.count} chats.`
}
}