Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/main/i18n/locales/en_US/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@
},
"edit": {
"label": "Edit",
"undo": "Undo",
"redo": "Redo",
"cut": "Cut",
"copy": "Copy",
"paste": "Paste",
"delete": "Delete",
"selectAll": "Select All",
"find": "Find"
},
"editor": {
Expand Down Expand Up @@ -98,6 +105,9 @@
},
"window": {
"label": "Window",
"minimize": "Minimize"
"minimize": "Minimize",
"zoom": "Zoom",
"front": "Bring All to Front",
"close": "Close"
}
}
12 changes: 11 additions & 1 deletion src/main/i18n/locales/ru_RU/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@
},
"edit": {
"label": "Правка",
"undo": "Отменить",
"redo": "Повторить",
"cut": "Вырезать",
"copy": "Копировать",
"paste": "Вставить",
"delete": "Удалить",
"selectAll": "Выделить всё",
"find": "Найти"
},
"editor": {
Expand Down Expand Up @@ -98,6 +105,9 @@
},
"window": {
"label": "Окно",
"minimize": "Свернуть"
"minimize": "Свернуть",
"zoom": "Изменить размер окна",
"front": "Все окна на передний план",
"close": "Закрыть"
}
}
85 changes: 85 additions & 0 deletions src/main/menu/__tests__/mainLocalization.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type { MenuItemConstructorOptions } from 'electron'
import process from 'node:process'
import { createInstance } from 'i18next'
import { afterEach, describe, expect, it, vi } from 'vitest'
import english from '../../i18n/locales/en_US/menu.json'
import russian from '../../i18n/locales/ru_RU/menu.json'

vi.mock('electron', () => ({
app: { getVersion: () => '1.0.0' },
BrowserWindow: { getFocusedWindow: () => null },
dialog: {},
Menu: { buildFromTemplate: (template: unknown) => template },
shell: {},
}))

vi.mock('../../i18n', async () => {
const i18n = createInstance()
await i18n.init({
lng: 'en_US',
fallbackLng: 'en_US',
resources: {
en_US: { menu: english },
ru_RU: { menu: russian },
},
})
return { default: i18n }
})

vi.mock('../../ipc', () => ({ send: vi.fn() }))
vi.mock('../../updates', () => ({ checkForUpdatesFromMenu: vi.fn() }))

afterEach(() => {
vi.unstubAllGlobals()
})

describe.each(['win32', 'darwin', 'linux'])('main menu localization on %s', (platform) => {
it.each(['ru_RU', 'en_US', 'missing_locale'])('uses %s labels and keeps native roles', async (locale) => {
vi.resetModules()
vi.stubGlobal('process', { ...process, platform })
const { default: i18n } = await import('../../i18n')
await i18n.changeLanguage(locale)
const { createMainMenu } = await import('../main')
const template = createMainMenu() as unknown as MenuItemConstructorOptions[]
const edit = template.find(item => item.role === 'editMenu')!
const window = template.find(item => item.role === 'windowMenu')!
const editItems = edit.submenu as MenuItemConstructorOptions[]
const windowItems = window.submenu as MenuItemConstructorOptions[]
const isRussian = locale === 'ru_RU'

expect(edit.label).toBe(isRussian ? 'Правка' : 'Edit')
expect(window.label).toBe(isRussian ? 'Окно' : 'Window')
expect(editItems.filter(item => item.role).map(item => [item.role, item.label]))
.toEqual([
['undo', isRussian ? 'Отменить' : 'Undo'],
['redo', isRussian ? 'Повторить' : 'Redo'],
['cut', isRussian ? 'Вырезать' : 'Cut'],
['copy', isRussian ? 'Копировать' : 'Copy'],
['paste', isRussian ? 'Вставить' : 'Paste'],
['delete', isRussian ? 'Удалить' : 'Delete'],
['selectAll', isRussian ? 'Выделить всё' : 'Select All'],
])
expect(editItems.at(-1)).toMatchObject({
label: isRussian ? 'Найти' : 'Find',
accelerator: 'CommandOrControl+F',
click: expect.any(Function),
})
expect(windowItems.map(item => item.role ?? item.type)).toEqual(
platform === 'darwin'
? ['minimize', 'zoom', 'separator', 'front']
: ['minimize', 'zoom', 'close'],
)
expect(windowItems.filter(item => item.role).map(item => item.label)).toEqual([
isRussian ? 'Свернуть' : 'Minimize',
isRussian ? 'Изменить размер окна' : 'Zoom',
platform === 'darwin'
? (isRussian ? 'Все окна на передний план' : 'Bring All to Front')
: (isRussian ? 'Закрыть' : 'Close'),
])
// Electron supplies the actions and platform-specific shortcuts for roles.
for (const item of [...editItems, ...windowItems].filter(item => item.role)) {
expect(item.click).toBeUndefined()
expect(item.accelerator).toBeUndefined()
}
})
})
29 changes: 29 additions & 0 deletions src/main/menu/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,30 +239,37 @@ if (isDev) {

const editMenuItems: MenuConfig[] = [
{
label: i18n.t('menu:edit.undo'),
role: 'undo',
},
{
label: i18n.t('menu:edit.redo'),
role: 'redo',
},
{
type: 'separator',
},
{
label: i18n.t('menu:edit.cut'),
role: 'cut',
},
{
label: i18n.t('menu:edit.copy'),
role: 'copy',
},
{
label: i18n.t('menu:edit.paste'),
role: 'paste',
},
{
label: i18n.t('menu:edit.delete'),
role: 'delete',
},
{
type: 'separator',
},
{
label: i18n.t('menu:edit.selectAll'),
role: 'selectAll',
},
{
Expand All @@ -275,6 +282,25 @@ const editMenuItems: MenuConfig[] = [
},
]

function createWindowMenuItems(): MenuItemConstructorOptions[] {
return [
{
label: i18n.t('menu:window.minimize'),
role: 'minimize',
},
{
label: i18n.t('menu:window.zoom'),
role: 'zoom',
},
...(process.platform === 'darwin'
? [
{ type: 'separator' as const },
{ label: i18n.t('menu:window.front'), role: 'front' as const },
]
: [{ label: i18n.t('menu:window.close'), role: 'close' as const }]),
]
}

function getPrimaryActionLabel(action: MainMenuPrimaryAction) {
if (action === 'new-snippet')
return i18n.t('action.new.snippet')
Expand Down Expand Up @@ -654,6 +680,7 @@ function createMainMenuTemplate(
submenu: createPlatformMenuItems(appMenuItems),
},
{
label: i18n.t('menu:edit.label'),
role: 'editMenu',
submenu: createPlatformMenuItems(editMenuItems),
},
Expand All @@ -662,7 +689,9 @@ function createMainMenuTemplate(
submenu: createPlatformMenuItems(historyMenuItems),
},
{
label: i18n.t('menu:window.label'),
role: 'windowMenu',
submenu: createWindowMenuItems(),
},
{
label: i18n.t('menu:help.label'),
Expand Down