feat(app-menu): add full i18n support and sync lanuage with app language settings (#11131)

Previously, the macOS menu bar was always displayed in English regardless of
system language or in-app language settings. This change enables the menu bar
to dynamically follow the application's language preference.

Key changes:
- Add language change listener to automatically update menu when user switches language
- Refactor AppMenuService with proper subscription management and cleanup
- Add appMenu translations for en-us, zh-cn, and zh-tw locales
- Implement destroy method to prevent memory leaks from config subscriptions
- Convert all menu items (File, Edit, View, Window, Help) to use localized labels

The menu bar now respects the in-app language setting and updates in real-time
when users change their preferences, providing a consistent multilingual experience.
This commit is contained in:
Jake Jia
2025-11-06 14:46:42 +08:00
committed by GitHub
parent 83e4d4363f
commit 816a92c609
4 changed files with 166 additions and 16 deletions

View File

@@ -7,16 +7,33 @@ import { app, Menu, shell } from 'electron'
import { configManager } from './ConfigManager'
export class AppMenuService {
private languageChangeCallback?: (newLanguage: string) => void
constructor() {
// Subscribe to language change events
this.languageChangeCallback = () => {
this.setupApplicationMenu()
}
configManager.subscribe('language', this.languageChangeCallback)
}
public destroy(): void {
// Clean up subscription to prevent memory leaks
if (this.languageChangeCallback) {
configManager.unsubscribe('language', this.languageChangeCallback)
}
}
public setupApplicationMenu(): void {
const locale = locales[configManager.getLanguage()]
const { common } = locale.translation
const { appMenu } = locale.translation
const template: MenuItemConstructorOptions[] = [
{
label: app.name,
submenu: [
{
label: common.about + ' ' + app.name,
label: appMenu.about + ' ' + app.name,
click: () => {
// Emit event to navigate to About page
const mainWindow = windowService.getMainWindow()
@@ -27,50 +44,78 @@ export class AppMenuService {
}
},
{ type: 'separator' },
{ role: 'services' },
{ role: 'services', label: appMenu.services },
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideOthers' },
{ role: 'unhide' },
{ role: 'hide', label: `${appMenu.hide} ${app.name}` },
{ role: 'hideOthers', label: appMenu.hideOthers },
{ role: 'unhide', label: appMenu.unhide },
{ type: 'separator' },
{ role: 'quit' }
{ role: 'quit', label: `${appMenu.quit} ${app.name}` }
]
},
{
role: 'fileMenu'
label: appMenu.file,
submenu: [{ role: 'close', label: appMenu.close }]
},
{
role: 'editMenu'
label: appMenu.edit,
submenu: [
{ role: 'undo', label: appMenu.undo },
{ role: 'redo', label: appMenu.redo },
{ type: 'separator' },
{ role: 'cut', label: appMenu.cut },
{ role: 'copy', label: appMenu.copy },
{ role: 'paste', label: appMenu.paste },
{ role: 'delete', label: appMenu.delete },
{ role: 'selectAll', label: appMenu.selectAll }
]
},
{
role: 'viewMenu'
label: appMenu.view,
submenu: [
{ role: 'reload', label: appMenu.reload },
{ role: 'forceReload', label: appMenu.forceReload },
{ role: 'toggleDevTools', label: appMenu.toggleDevTools },
{ type: 'separator' },
{ role: 'resetZoom', label: appMenu.resetZoom },
{ role: 'zoomIn', label: appMenu.zoomIn },
{ role: 'zoomOut', label: appMenu.zoomOut },
{ type: 'separator' },
{ role: 'togglefullscreen', label: appMenu.toggleFullscreen }
]
},
{
role: 'windowMenu'
label: appMenu.window,
submenu: [
{ role: 'minimize', label: appMenu.minimize },
{ role: 'zoom', label: appMenu.zoom },
{ type: 'separator' },
{ role: 'front', label: appMenu.front }
]
},
{
role: 'help',
label: appMenu.help,
submenu: [
{
label: 'Website',
label: appMenu.website,
click: () => {
shell.openExternal('https://cherry-ai.com')
}
},
{
label: 'Documentation',
label: appMenu.documentation,
click: () => {
shell.openExternal('https://cherry-ai.com/docs')
}
},
{
label: 'Feedback',
label: appMenu.feedback,
click: () => {
shell.openExternal('https://github.com/CherryHQ/cherry-studio/issues/new/choose')
}
},
{
label: 'Releases',
label: appMenu.releases,
click: () => {
shell.openExternal('https://github.com/CherryHQ/cherry-studio/releases')
}

View File

@@ -339,6 +339,41 @@
},
"title": "API Server"
},
"appMenu": {
"about": "About",
"close": "Close Window",
"copy": "Copy",
"cut": "Cut",
"delete": "Delete",
"documentation": "Documentation",
"edit": "Edit",
"feedback": "Feedback",
"file": "File",
"forceReload": "Force Reload",
"front": "Bring All to Front",
"help": "Help",
"hide": "Hide",
"hideOthers": "Hide Others",
"minimize": "Minimize",
"paste": "Paste",
"quit": "Quit",
"redo": "Redo",
"releases": "Releases",
"reload": "Reload",
"resetZoom": "Actual Size",
"selectAll": "Select All",
"services": "Services",
"toggleDevTools": "Toggle Developer Tools",
"toggleFullscreen": "Toggle Fullscreen",
"undo": "Undo",
"unhide": "Show All",
"view": "View",
"website": "Website",
"window": "Window",
"zoom": "Zoom",
"zoomIn": "Zoom In",
"zoomOut": "Zoom Out"
},
"assistants": {
"abbr": "Assistants",
"clear": {

View File

@@ -339,6 +339,41 @@
},
"title": "API 服务器"
},
"appMenu": {
"about": "关于",
"close": "关闭窗口",
"copy": "复制",
"cut": "剪切",
"delete": "删除",
"documentation": "文档",
"edit": "编辑",
"feedback": "反馈",
"file": "文件",
"forceReload": "强制重新加载",
"front": "全部置于顶层",
"help": "帮助",
"hide": "隐藏",
"hideOthers": "隐藏其他",
"minimize": "最小化",
"paste": "粘贴",
"quit": "退出",
"redo": "重做",
"releases": "版本发布",
"reload": "重新加载",
"resetZoom": "实际大小",
"selectAll": "全选",
"services": "服务",
"toggleDevTools": "切换开发者工具",
"toggleFullscreen": "切换全屏",
"undo": "撤销",
"unhide": "全部显示",
"view": "视图",
"website": "网站",
"window": "窗口",
"zoom": "缩放",
"zoomIn": "放大",
"zoomOut": "缩小"
},
"assistants": {
"abbr": "助手",
"clear": {

View File

@@ -339,6 +339,41 @@
},
"title": "API 伺服器"
},
"appMenu": {
"about": "關於",
"close": "關閉視窗",
"copy": "複製",
"cut": "剪下",
"delete": "刪除",
"documentation": "文件",
"edit": "編輯",
"feedback": "回饋",
"file": "檔案",
"forceReload": "強制重新載入",
"front": "全部置於頂層",
"help": "幫助",
"hide": "隱藏",
"hideOthers": "隱藏其他",
"minimize": "最小化",
"paste": "貼上",
"quit": "結束",
"redo": "重做",
"releases": "版本發布",
"reload": "重新載入",
"resetZoom": "實際大小",
"selectAll": "全選",
"services": "服務",
"toggleDevTools": "切換開發者工具",
"toggleFullscreen": "切換全螢幕",
"undo": "復原",
"unhide": "全部顯示",
"view": "檢視",
"website": "網站",
"window": "視窗",
"zoom": "縮放",
"zoomIn": "放大",
"zoomOut": "縮小"
},
"assistants": {
"abbr": "助手",
"clear": {