Prepare IDE beta 6 with Turkish native dialogs and map errors
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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.`
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user