diff --git a/src/renderer/src/components/Icons/MinAppIcon.tsx b/src/renderer/src/components/Icons/MinAppIcon.tsx index 58da46a723..a55d598dde 100644 --- a/src/renderer/src/components/Icons/MinAppIcon.tsx +++ b/src/renderer/src/components/Icons/MinAppIcon.tsx @@ -1,4 +1,4 @@ -import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' +import { allMinApps } from '@renderer/config/minapps' import type { MinAppType } from '@renderer/types' import type { FC } from 'react' @@ -10,10 +10,10 @@ interface Props { } const MinAppIcon: FC = ({ app, size = 48, style, sidebar = false }) => { - // First try to find in DEFAULT_MIN_APPS for predefined styling - const _app = DEFAULT_MIN_APPS.find((item) => item.id === app.id) + // First try to find in allMinApps for predefined styling + const _app = allMinApps.find((item) => item.id === app.id) - // If found in DEFAULT_MIN_APPS, use predefined styling + // If found in allMinApps, use predefined styling if (_app) { return ( = ({ app, size = 48, style, sidebar = false }) => { ) } - // If not found in DEFAULT_MIN_APPS but app has logo, use it (for temporary apps) + // If not found in allMinApps but app has logo, use it (for temporary apps) if (app.logo) { return ( ({ - DEFAULT_MIN_APPS: [ + allMinApps: [ { id: 'test-app-1', name: 'Test App 1', @@ -52,7 +52,7 @@ describe('MinAppIcon', () => { }) }) - it('should return null when app is not found in DEFAULT_MIN_APPS', () => { + it('should return null when app is not found in allMinApps', () => { const unknownApp = { id: 'unknown-app', name: 'Unknown App', diff --git a/src/renderer/src/components/MinApp/MinApp.tsx b/src/renderer/src/components/MinApp/MinApp.tsx index b6a623d309..896592ef16 100644 --- a/src/renderer/src/components/MinApp/MinApp.tsx +++ b/src/renderer/src/components/MinApp/MinApp.tsx @@ -1,7 +1,7 @@ import { loggerService } from '@logger' import MinAppIcon from '@renderer/components/Icons/MinAppIcon' import IndicatorLight from '@renderer/components/IndicatorLight' -import { loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateDefaultMinApps } from '@renderer/config/minapps' +import { loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateAllMinApps } from '@renderer/config/minapps' import { useMinappPopup } from '@renderer/hooks/useMinappPopup' import { useMinapps } from '@renderer/hooks/useMinapps' import { useRuntime } from '@renderer/hooks/useRuntime' @@ -93,7 +93,7 @@ const MinApp: FC = ({ app, onClick, size = 60, isLast }) => { await window.api.file.writeWithId('custom-minapps.json', JSON.stringify(updatedApps, null, 2)) window.toast.success(t('settings.miniapps.custom.remove_success')) const reloadedApps = [...ORIGIN_DEFAULT_MIN_APPS, ...(await loadCustomMiniApp())] - updateDefaultMinApps(reloadedApps) + updateAllMinApps(reloadedApps) updateMinapps(minapps.filter((item) => item.id !== app.id)) updatePinnedMinapps(pinned.filter((item) => item.id !== app.id)) updateDisabledMinapps(disabled.filter((item) => item.id !== app.id)) @@ -122,7 +122,7 @@ const MinApp: FC = ({ app, onClick, size = 60, isLast }) => { )} - {isLast ? t('settings.miniapps.custom.title') : app.name} + {isLast ? t('settings.miniapps.custom.title') : app.nameKey ? t(app.nameKey) : app.name} ) diff --git a/src/renderer/src/components/MinApp/MinappPopupContainer.tsx b/src/renderer/src/components/MinApp/MinappPopupContainer.tsx index 8e361f0bc7..f4eaaaf4da 100644 --- a/src/renderer/src/components/MinApp/MinappPopupContainer.tsx +++ b/src/renderer/src/components/MinApp/MinappPopupContainer.tsx @@ -13,7 +13,7 @@ import { import { loggerService } from '@logger' import WindowControls from '@renderer/components/WindowControls' import { isDev, isLinux, isMac, isWin } from '@renderer/config/constant' -import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' +import { allMinApps } from '@renderer/config/minapps' import { useBridge } from '@renderer/hooks/useBridge' import { useMinappPopup } from '@renderer/hooks/useMinappPopup' import { useMinapps } from '@renderer/hooks/useMinapps' @@ -246,7 +246,7 @@ const MinappPopupContainer: React.FC = () => { (acc, app) => ({ ...acc, [app.id]: { - canPinned: DEFAULT_MIN_APPS.some((item) => item.id === app.id), + canPinned: allMinApps.some((item) => item.id === app.id), isPinned: pinned.some((item) => item.id === app.id), canOpenExternalLink: app.url.startsWith('http://') || app.url.startsWith('https://') } diff --git a/src/renderer/src/components/Tab/TabContainer.tsx b/src/renderer/src/components/Tab/TabContainer.tsx index 2d4d34a249..0b75b67e44 100644 --- a/src/renderer/src/components/Tab/TabContainer.tsx +++ b/src/renderer/src/components/Tab/TabContainer.tsx @@ -3,7 +3,7 @@ import { loggerService } from '@logger' import { Sortable, useDndReorder } from '@renderer/components/dnd' import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer' import { isLinux, isMac } from '@renderer/config/constant' -import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' +import { allMinApps } from '@renderer/config/minapps' import { useTheme } from '@renderer/context/ThemeProvider' import { useFullscreen } from '@renderer/hooks/useFullscreen' import { useMinappPopup } from '@renderer/hooks/useMinappPopup' @@ -58,7 +58,7 @@ const getTabIcon = ( // Check if it's a minapp tab (format: apps:appId) if (tabId.startsWith('apps:')) { const appId = tabId.replace('apps:', '') - let app = [...DEFAULT_MIN_APPS, ...minapps].find((app) => app.id === appId) + let app = [...allMinApps, ...minapps].find((app) => app.id === appId) // If not found in permanent apps, search in temporary apps cache // The cache stores apps opened via openSmartMinapp() for top navbar mode @@ -140,7 +140,7 @@ const TabsContainer: React.FC = ({ children }) => { // Check if it's a minapp tab if (tabId.startsWith('apps:')) { const appId = tabId.replace('apps:', '') - let app = [...DEFAULT_MIN_APPS, ...minapps].find((app) => app.id === appId) + let app = [...allMinApps, ...minapps].find((app) => app.id === appId) // If not found in permanent apps, search in temporary apps cache // This ensures temporary MinApps display proper titles while being used diff --git a/src/renderer/src/config/minapps.ts b/src/renderer/src/config/minapps.ts index 2653768979..bbdc8f8222 100644 --- a/src/renderer/src/config/minapps.ts +++ b/src/renderer/src/config/minapps.ts @@ -56,7 +56,6 @@ import GroqProviderLogo from '@renderer/assets/images/providers/groq.png?url' import OpenAiProviderLogo from '@renderer/assets/images/providers/openai.png?url' import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png?url' import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png?url' -import i18n from '@renderer/i18n' import type { MinAppType } from '@renderer/types' const logger = loggerService.withContext('Config:minapps') @@ -118,14 +117,18 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ }, { id: 'yi', - name: i18n.t('minapps.wanzhi'), + name: 'Wanzhi', + nameKey: 'minapps.wanzhi', + locales: ['zh-CN', 'zh-TW'], url: 'https://www.wanzhi.com/', logo: WanZhiAppLogo, bodered: true }, { id: 'zhipu', - name: i18n.t('minapps.chatglm'), + name: 'ChatGLM', + nameKey: 'minapps.chatglm', + locales: ['zh-CN', 'zh-TW'], url: 'https://chatglm.cn/main/alltoolsdetail', logo: ZhipuProviderLogo, bodered: true @@ -133,31 +136,40 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ { id: 'moonshot', name: 'Kimi', + locales: ['zh-CN', 'zh-TW'], url: 'https://kimi.moonshot.cn/', logo: KimiAppLogo }, { id: 'baichuan', - name: i18n.t('minapps.baichuan'), + name: 'Baichuan', + nameKey: 'minapps.baichuan', + locales: ['zh-CN', 'zh-TW'], url: 'https://ying.baichuan-ai.com/chat', logo: BaicuanAppLogo }, { id: 'dashscope', - name: i18n.t('minapps.qwen'), + name: 'Qwen', + nameKey: 'minapps.qwen', + locales: ['zh-CN', 'zh-TW'], url: 'https://www.qianwen.com', logo: QwenModelLogo }, { id: 'stepfun', - name: i18n.t('minapps.stepfun'), + name: 'Stepfun', + nameKey: 'minapps.stepfun', + locales: ['zh-CN', 'zh-TW'], url: 'https://stepfun.com', logo: StepfunAppLogo, bodered: true }, { id: 'doubao', - name: i18n.t('minapps.doubao'), + name: 'Doubao', + nameKey: 'minapps.doubao', + locales: ['zh-CN', 'zh-TW'], url: 'https://www.doubao.com/chat/', logo: DoubaoAppLogo }, @@ -169,7 +181,9 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ }, { id: 'minimax', - name: i18n.t('minapps.hailuo'), + name: 'Hailuo', + nameKey: 'minapps.hailuo', + locales: ['zh-CN', 'zh-TW'], url: 'https://chat.minimaxi.com/', logo: HailuoModelLogo, bodered: true @@ -198,13 +212,17 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ }, { id: 'baidu-ai-chat', - name: i18n.t('minapps.wenxin'), + name: 'Wenxin', + nameKey: 'minapps.wenxin', + locales: ['zh-CN', 'zh-TW'], logo: BaiduAiAppLogo, url: 'https://yiyan.baidu.com/' }, { id: 'baidu-ai-search', - name: i18n.t('minapps.baidu-ai-search'), + name: 'Baidu AI Search', + nameKey: 'minapps.baidu-ai-search', + locales: ['zh-CN', 'zh-TW'], logo: BaiduAiSearchLogo, url: 'https://chat.baidu.com/', bodered: true, @@ -214,14 +232,18 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ }, { id: 'tencent-yuanbao', - name: i18n.t('minapps.tencent-yuanbao'), + name: 'Tencent Yuanbao', + nameKey: 'minapps.tencent-yuanbao', + locales: ['zh-CN', 'zh-TW'], logo: TencentYuanbaoAppLogo, url: 'https://yuanbao.tencent.com/chat', bodered: true }, { id: 'sensetime-chat', - name: i18n.t('minapps.sensechat'), + name: 'Sensechat', + nameKey: 'minapps.sensechat', + locales: ['zh-CN', 'zh-TW'], logo: SensetimeAppLogo, url: 'https://chat.sensetime.com/wb/chat', bodered: true @@ -229,12 +251,15 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ { id: 'spark-desk', name: 'SparkDesk', + locales: ['zh-CN', 'zh-TW'], logo: SparkDeskAppLogo, url: 'https://xinghuo.xfyun.cn/desk' }, { id: 'metaso', - name: i18n.t('minapps.metaso'), + name: 'Metaso', + nameKey: 'minapps.metaso', + locales: ['zh-CN', 'zh-TW'], logo: MetasoAppLogo, url: 'https://metaso.cn/' }, @@ -258,7 +283,9 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ }, { id: 'tiangong-ai', - name: i18n.t('minapps.tiangong-ai'), + name: 'Tiangong AI', + nameKey: 'minapps.tiangong-ai', + locales: ['zh-CN', 'zh-TW'], logo: TiangongAiLogo, url: 'https://www.tiangong.cn/', bodered: true @@ -285,7 +312,9 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ }, { id: 'nm', - name: i18n.t('minapps.nami-ai'), + name: 'Nami AI', + nameKey: 'minapps.nami-ai', + locales: ['zh-CN', 'zh-TW'], logo: NamiAiLogo, url: 'https://bot.n.cn/', bodered: true @@ -329,6 +358,7 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ { id: 'qwenlm', name: 'QwenChat', + locales: ['zh-CN', 'zh-TW'], logo: QwenlmAppLogo, url: 'https://chat.qwen.ai' }, @@ -354,7 +384,9 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ }, { id: 'xiaoyi', - name: i18n.t('minapps.xiaoyi'), + name: 'Xiaoyi', + nameKey: 'minapps.xiaoyi', + locales: ['zh-CN', 'zh-TW'], logo: XiaoYiAppLogo, url: 'https://xiaoyi.huawei.com/chat/', bodered: true @@ -384,7 +416,9 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ }, { id: 'wpslingxi', - name: i18n.t('minapps.wps-copilot'), + name: 'WPS AI', + nameKey: 'minapps.wps-copilot', + locales: ['zh-CN', 'zh-TW'], logo: WPSLingXiLogo, url: 'https://copilot.wps.cn/', bodered: true @@ -425,14 +459,18 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ }, { id: 'zhihu', - name: i18n.t('minapps.zhihu'), + name: 'Zhihu Zhida', + nameKey: 'minapps.zhihu', + locales: ['zh-CN', 'zh-TW'], logo: ZhihuAppLogo, url: 'https://zhida.zhihu.com/', bodered: true }, { id: 'dangbei', - name: i18n.t('minapps.dangbei'), + name: 'Dangbei AI', + nameKey: 'minapps.dangbei', + locales: ['zh-CN', 'zh-TW'], logo: DangbeiLogo, url: 'https://ai.dangbei.com/', bodered: true @@ -460,13 +498,16 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ { id: 'longcat', name: 'LongCat', + locales: ['zh-CN', 'zh-TW'], logo: LongCatAppLogo, url: 'https://longcat.chat/', bodered: true }, { id: 'ling', - name: i18n.t('minapps.ant-ling'), + name: 'Ant Ling', + nameKey: 'minapps.ant-ling', + locales: ['zh-CN', 'zh-TW'], url: 'https://ling.tbox.cn/chat', logo: LingAppLogo, bodered: true, @@ -486,11 +527,11 @@ const ORIGIN_DEFAULT_MIN_APPS: MinAppType[] = [ } ] -// 加载自定义小应用并合并到默认应用中 -let DEFAULT_MIN_APPS = [...ORIGIN_DEFAULT_MIN_APPS, ...(await loadCustomMiniApp())] +// All mini apps: built-in defaults + custom apps loaded from user config +let allMinApps = [...ORIGIN_DEFAULT_MIN_APPS, ...(await loadCustomMiniApp())] -function updateDefaultMinApps(param) { - DEFAULT_MIN_APPS = param +function updateAllMinApps(apps: MinAppType[]) { + allMinApps = apps } -export { DEFAULT_MIN_APPS, loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateDefaultMinApps } +export { allMinApps, loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateAllMinApps } diff --git a/src/renderer/src/hooks/useMinappPopup.ts b/src/renderer/src/hooks/useMinappPopup.ts index 9d372a6b3c..6c0734e047 100644 --- a/src/renderer/src/hooks/useMinappPopup.ts +++ b/src/renderer/src/hooks/useMinappPopup.ts @@ -1,4 +1,4 @@ -import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' +import { allMinApps } from '@renderer/config/minapps' import { useRuntime } from '@renderer/hooks/useRuntime' import { useSettings } from '@renderer/hooks/useSettings' // 使用设置中的值 import NavigationService from '@renderer/services/NavigationService' @@ -120,10 +120,10 @@ export const useMinappPopup = () => { [openMinapp] ) - /** Open a minapp by id (look up the minapp in DEFAULT_MIN_APPS) */ + /** Open a minapp by id (look up the minapp in allMinApps) */ const openMinappById = useCallback( (id: string, keepAlive: boolean = false) => { - const app = DEFAULT_MIN_APPS.find((app) => app?.id === id) + const app = allMinApps.find((app) => app?.id === id) if (app) { openMinapp(app, keepAlive) } diff --git a/src/renderer/src/hooks/useMinapps.ts b/src/renderer/src/hooks/useMinapps.ts index 77ea3cb89e..75262f2b8d 100644 --- a/src/renderer/src/hooks/useMinapps.ts +++ b/src/renderer/src/hooks/useMinapps.ts @@ -1,25 +1,129 @@ -import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' +import { allMinApps } from '@renderer/config/minapps' import type { RootState } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store' import { setDisabledMinApps, setMinApps, setPinnedMinApps } from '@renderer/store/minapps' -import type { MinAppType } from '@renderer/types' +import type { LanguageVarious, MinAppType } from '@renderer/types' +import { useCallback, useMemo } from 'react' + +/** + * Data Flow Design: + * + * PRINCIPLE: Locale filtering is a VIEW concern, not a DATA concern. + * + * - Redux stores ALL apps (including locale-restricted ones) to preserve user preferences + * - allMinApps is the template data source containing locale definitions + * - This hook applies locale filtering only when READING for UI display + * - When WRITING, locale-hidden apps are merged back to prevent data loss + */ + +// Check if app should be visible for the given locale +const isVisibleForLocale = (app: MinAppType, language: LanguageVarious): boolean => { + if (!app.locales) return true + return app.locales.includes(language) +} + +// Filter apps by locale - only show apps that match current language +const filterByLocale = (apps: MinAppType[], language: LanguageVarious): MinAppType[] => { + return apps.filter((app) => isVisibleForLocale(app, language)) +} + +// Get locale-hidden apps from allMinApps for the current language +// This uses allMinApps as source of truth for locale definitions +const getLocaleHiddenApps = (language: LanguageVarious): MinAppType[] => { + return allMinApps.filter((app) => !isVisibleForLocale(app, language)) +} export const useMinapps = () => { const { enabled, disabled, pinned } = useAppSelector((state: RootState) => state.minapps) + const language = useAppSelector((state: RootState) => state.settings.language) const dispatch = useAppDispatch() + const mapApps = useCallback( + (apps: MinAppType[]) => apps.map((app) => allMinApps.find((item) => item.id === app.id) || app), + [] + ) + + const getAllApps = useCallback( + (apps: MinAppType[], disabledApps: MinAppType[]) => { + const mappedApps = mapApps(apps) + const existingIds = new Set(mappedApps.map((app) => app.id)) + const disabledIds = new Set(disabledApps.map((app) => app.id)) + const missingApps = allMinApps.filter((app) => !existingIds.has(app.id) && !disabledIds.has(app.id)) + return [...mappedApps, ...missingApps] + }, + [mapApps] + ) + + // READ: Get apps filtered by locale for UI display + const minapps = useMemo(() => { + const allApps = getAllApps(enabled, disabled) + const disabledIds = new Set(disabled.map((app) => app.id)) + const withoutDisabled = allApps.filter((app) => !disabledIds.has(app.id)) + return filterByLocale(withoutDisabled, language) + }, [enabled, disabled, language, getAllApps]) + + const disabledApps = useMemo(() => filterByLocale(mapApps(disabled), language), [disabled, language, mapApps]) + const pinnedApps = useMemo(() => filterByLocale(mapApps(pinned), language), [pinned, language, mapApps]) + + const updateMinapps = useCallback( + (visibleApps: MinAppType[]) => { + const disabledIds = new Set(disabled.map((app) => app.id)) + + const withoutDisabled = visibleApps.filter((app) => !disabledIds.has(app.id)) + + const localeHiddenApps = getLocaleHiddenApps(language) + + const localeHiddenIds = new Set(localeHiddenApps.map((app) => app.id)) + const preservedLocaleHidden = enabled.filter((app) => localeHiddenIds.has(app.id) && !disabledIds.has(app.id)) + + const visibleIds = new Set(withoutDisabled.map((app) => app.id)) + const toAppend = preservedLocaleHidden.filter((app) => !visibleIds.has(app.id)) + const merged = [...withoutDisabled, ...toAppend] + + const existingIds = new Set(merged.map((app) => app.id)) + const missingApps = allMinApps.filter((app) => !existingIds.has(app.id) && !disabledIds.has(app.id)) + + dispatch(setMinApps([...merged, ...missingApps])) + }, + [dispatch, enabled, disabled, language] + ) + + // WRITE: Update disabled apps, preserving locale-hidden disabled apps + const updateDisabledMinapps = useCallback( + (visibleDisabledApps: MinAppType[]) => { + const localeHiddenApps = getLocaleHiddenApps(language) + const localeHiddenIds = new Set(localeHiddenApps.map((app) => app.id)) + const preservedLocaleHidden = disabled.filter((app) => localeHiddenIds.has(app.id)) + + const visibleIds = new Set(visibleDisabledApps.map((app) => app.id)) + const toAppend = preservedLocaleHidden.filter((app) => !visibleIds.has(app.id)) + + dispatch(setDisabledMinApps([...visibleDisabledApps, ...toAppend])) + }, + [dispatch, disabled, language] + ) + + // WRITE: Update pinned apps, preserving locale-hidden pinned apps + const updatePinnedMinapps = useCallback( + (visiblePinnedApps: MinAppType[]) => { + const localeHiddenApps = getLocaleHiddenApps(language) + const localeHiddenIds = new Set(localeHiddenApps.map((app) => app.id)) + const preservedLocaleHidden = pinned.filter((app) => localeHiddenIds.has(app.id)) + + const visibleIds = new Set(visiblePinnedApps.map((app) => app.id)) + const toAppend = preservedLocaleHidden.filter((app) => !visibleIds.has(app.id)) + + dispatch(setPinnedMinApps([...visiblePinnedApps, ...toAppend])) + }, + [dispatch, pinned, language] + ) + return { - minapps: enabled.map((app) => DEFAULT_MIN_APPS.find((item) => item.id === app.id) || app), - disabled: disabled.map((app) => DEFAULT_MIN_APPS.find((item) => item.id === app.id) || app), - pinned: pinned.map((app) => DEFAULT_MIN_APPS.find((item) => item.id === app.id) || app), - updateMinapps: (minapps: MinAppType[]) => { - dispatch(setMinApps(minapps)) - }, - updateDisabledMinapps: (minapps: MinAppType[]) => { - dispatch(setDisabledMinApps(minapps)) - }, - updatePinnedMinapps: (minapps: MinAppType[]) => { - dispatch(setPinnedMinApps(minapps)) - } + minapps, + disabled: disabledApps, + pinned: pinnedApps, + updateMinapps, + updateDisabledMinapps, + updatePinnedMinapps } } diff --git a/src/renderer/src/pages/minapps/MinAppPage.tsx b/src/renderer/src/pages/minapps/MinAppPage.tsx index e13a1059ce..4dc74a999f 100644 --- a/src/renderer/src/pages/minapps/MinAppPage.tsx +++ b/src/renderer/src/pages/minapps/MinAppPage.tsx @@ -1,5 +1,5 @@ import { loggerService } from '@logger' -import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' +import { allMinApps } from '@renderer/config/minapps' import { useMinappPopup } from '@renderer/hooks/useMinappPopup' import { useMinapps } from '@renderer/hooks/useMinapps' import { useNavbarPosition } from '@renderer/hooks/useSettings' @@ -51,7 +51,7 @@ const MinAppPage: FC = () => { if (!appId) return null // First try to find in default and custom mini-apps - let foundApp = [...DEFAULT_MIN_APPS, ...minapps].find((app) => app.id === appId) + let foundApp = [...allMinApps, ...minapps].find((app) => app.id === appId) // If not found and we have cache, try to find in cache (for temporary apps) if (!foundApp && minAppsCache) { diff --git a/src/renderer/src/pages/minapps/MiniappSettings/MiniAppIconsManager.tsx b/src/renderer/src/pages/minapps/MiniappSettings/MiniAppIconsManager.tsx index 426d25cf17..0212477dd0 100644 --- a/src/renderer/src/pages/minapps/MiniappSettings/MiniAppIconsManager.tsx +++ b/src/renderer/src/pages/minapps/MiniappSettings/MiniAppIconsManager.tsx @@ -1,7 +1,7 @@ import { CloseOutlined } from '@ant-design/icons' import type { DraggableProvided, DroppableProvided, DropResult } from '@hello-pangea/dnd' import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd' -import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' +import { allMinApps } from '@renderer/config/minapps' import { useMinapps } from '@renderer/hooks/useMinapps' import { getMiniappsStatusLabel } from '@renderer/i18n/label' import type { MinAppType } from '@renderer/types' @@ -91,7 +91,9 @@ const MiniAppIconsManager: FC = ({ ) const renderProgramItem = (program: MinAppType, provided: DraggableProvided, listType: ListType) => { - const { name, logo } = DEFAULT_MIN_APPS.find((app) => app.id === program.id) || { name: program.name, logo: '' } + const appData = allMinApps.find((app) => app.id === program.id) + const name = appData?.nameKey ? t(appData.nameKey) : appData?.name || program.name + const logo = appData?.logo || '' return ( diff --git a/src/renderer/src/pages/minapps/MiniappSettings/MiniAppSettings.tsx b/src/renderer/src/pages/minapps/MiniappSettings/MiniAppSettings.tsx index d8b61603a1..e54d80c369 100644 --- a/src/renderer/src/pages/minapps/MiniappSettings/MiniAppSettings.tsx +++ b/src/renderer/src/pages/minapps/MiniappSettings/MiniAppSettings.tsx @@ -1,5 +1,5 @@ import { UndoOutlined } from '@ant-design/icons' // 导入重置图标 -import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' +import { allMinApps } from '@renderer/config/minapps' import { useMinapps } from '@renderer/hooks/useMinapps' import { useSettings } from '@renderer/hooks/useSettings' import { SettingDescription, SettingDivider, SettingRowTitle, SettingTitle } from '@renderer/pages/settings' @@ -32,9 +32,9 @@ const MiniAppSettings: FC = () => { const debounceTimerRef = useRef(null) const handleResetMinApps = useCallback(() => { - setVisibleMiniApps(DEFAULT_MIN_APPS) + setVisibleMiniApps(allMinApps) setDisabledMiniApps([]) - updateMinapps(DEFAULT_MIN_APPS) + updateMinapps(allMinApps) updateDisabledMinapps([]) }, [updateDisabledMinapps, updateMinapps]) diff --git a/src/renderer/src/pages/minapps/NewAppButton.tsx b/src/renderer/src/pages/minapps/NewAppButton.tsx index 22d9f9345d..7845ae117c 100644 --- a/src/renderer/src/pages/minapps/NewAppButton.tsx +++ b/src/renderer/src/pages/minapps/NewAppButton.tsx @@ -1,6 +1,6 @@ import { PlusOutlined, UploadOutlined } from '@ant-design/icons' import { loggerService } from '@logger' -import { loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateDefaultMinApps } from '@renderer/config/minapps' +import { loadCustomMiniApp, ORIGIN_DEFAULT_MIN_APPS, updateAllMinApps } from '@renderer/config/minapps' import { useMinapps } from '@renderer/hooks/useMinapps' import type { MinAppType } from '@renderer/types' import { Button, Form, Input, Modal, Radio, Upload } from 'antd' @@ -60,7 +60,7 @@ const NewAppButton: FC = ({ size = 60 }) => { form.resetFields() setFileList([]) const reloadedApps = [...ORIGIN_DEFAULT_MIN_APPS, ...(await loadCustomMiniApp())] - updateDefaultMinApps(reloadedApps) + updateAllMinApps(reloadedApps) updateMinapps([...minapps, newApp]) } catch (error) { window.toast.error(t('settings.miniapps.custom.save_error')) diff --git a/src/renderer/src/pages/minapps/components/MinimalToolbar.tsx b/src/renderer/src/pages/minapps/components/MinimalToolbar.tsx index 7755432167..835f5dcf49 100644 --- a/src/renderer/src/pages/minapps/components/MinimalToolbar.tsx +++ b/src/renderer/src/pages/minapps/components/MinimalToolbar.tsx @@ -10,7 +10,7 @@ import { } from '@ant-design/icons' import { loggerService } from '@logger' import { isDev } from '@renderer/config/constant' -import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' +import { allMinApps } from '@renderer/config/minapps' import { useMinapps } from '@renderer/hooks/useMinapps' import { useSettings } from '@renderer/hooks/useSettings' import { useAppDispatch } from '@renderer/store' @@ -50,7 +50,7 @@ const MinimalToolbar: FC = ({ app, webviewRef, currentUrl, onReload, onOp const navigate = useNavigate() const [canGoBack, setCanGoBack] = useState(false) const [canGoForward, setCanGoForward] = useState(false) - const canPinned = DEFAULT_MIN_APPS.some((item) => item.id === app.id) + const canPinned = allMinApps.some((item) => item.id === app.id) const isPinned = pinned.some((item) => item.id === app.id) const canOpenExternalLink = app.url.startsWith('http://') || app.url.startsWith('https://') diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 8719cdb7c1..0594635a8b 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -22,7 +22,7 @@ import { DEFAULT_TEMPERATURE, isMac } from '@renderer/config/constant' -import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' +import { allMinApps } from '@renderer/config/minapps' import { glm45FlashModel, isFunctionCallingModel, @@ -99,7 +99,7 @@ function removeMiniAppFromState(state: RootState, id: string) { function addMiniApp(state: RootState, id: string) { if (state.minapps) { - const app = DEFAULT_MIN_APPS.find((app) => app.id === id) + const app = allMinApps.find((app) => app.id === id) if (app) { if (!state.minapps.enabled.find((app) => app.id === id)) { state.minapps.enabled.push(app) @@ -1076,7 +1076,7 @@ const migrateConfig = { if (state.minapps) { appIds.forEach((id) => { - const app = DEFAULT_MIN_APPS.find((app) => app.id === id) + const app = allMinApps.find((app) => app.id === id) if (app) { state.minapps.enabled.push(app) } diff --git a/src/renderer/src/store/minapps.ts b/src/renderer/src/store/minapps.ts index ac2a83440b..749be9c1d3 100644 --- a/src/renderer/src/store/minapps.ts +++ b/src/renderer/src/store/minapps.ts @@ -16,7 +16,7 @@ */ import type { PayloadAction } from '@reduxjs/toolkit' import { createSlice } from '@reduxjs/toolkit' -import { DEFAULT_MIN_APPS } from '@renderer/config/minapps' +import { allMinApps } from '@renderer/config/minapps' import type { MinAppType } from '@renderer/types' export interface MinAppsState { @@ -26,7 +26,7 @@ export interface MinAppsState { } const initialState: MinAppsState = { - enabled: DEFAULT_MIN_APPS, + enabled: allMinApps, disabled: [], pinned: [] } diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 9719897525..3138a55188 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -459,6 +459,10 @@ export interface PaintingsState { export type MinAppType = { id: string name: string + /** i18n key for translatable names */ + nameKey?: string + /** Locale codes where this app should be visible (e.g., ['zh-CN', 'zh-TW']) */ + locales?: LanguageVarious[] logo?: string url: string // FIXME: It should be `bordered` diff --git a/src/renderer/src/utils/__tests__/export.test.ts b/src/renderer/src/utils/__tests__/export.test.ts index c7a89207e9..1c53d07c4b 100644 --- a/src/renderer/src/utils/__tests__/export.test.ts +++ b/src/renderer/src/utils/__tests__/export.test.ts @@ -9,9 +9,9 @@ import { afterEach, beforeEach, describe, expect, it, test, vi } from 'vitest' vi.mock('@renderer/config/minapps', () => { return { ORIGIN_DEFAULT_MIN_APPS: [], - DEFAULT_MIN_APPS: [], + allMinApps: [], loadCustomMiniApp: async () => [], - updateDefaultMinApps: vi.fn() + updateAllMinApps: vi.fn() } })