From fd269348b46242eb215b640def20fb3ba99093b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=A8=E9=9C=96=E9=93=83?= Date: Fri, 5 Jun 2026 19:03:35 +0800 Subject: [PATCH] fix: add missing Toaster component to enable toast display The project was calling toast.success/error/warning from sonner in 7 files, but the component was never rendered in the React tree. This meant sonner stored toast data internally but no UI was ever displayed to users. Changes: - Create src/components/ui/sonner.tsx with shadcn/ui-style Toaster wrapper that integrates with ThemeModeProvider for light/dark theme support - Add to AppRoot.tsx so it works across all entry points (popup, sidepanel, browser-tab) fix: always convert dots to underscores in i18n t() key lookup The t() function only converted dots to underscores when the key contained a colon (namespace:key format). Keys using dot notation like 'messages.copySuccess' were passed directly to chrome.i18n.getMessage() without dot-to-underscore conversion, causing lookups to fail silently and return empty strings - resulting in toast notifications with no text. Now all separators (both ':' and '.') are consistently converted to '_' regardless of format, matching the messages.json key convention. fix: position toast at bottom-center instead of bottom-right --- src/components/ui/sonner.tsx | 26 ++++++++++++++++++++++++++ src/providers/AppRoot.tsx | 2 ++ src/utils/chromeI18n.ts | 8 ++------ 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 src/components/ui/sonner.tsx diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx new file mode 100644 index 0000000..ed76aab --- /dev/null +++ b/src/components/ui/sonner.tsx @@ -0,0 +1,26 @@ +import { Toaster as Sonner } from 'sonner'; +import { useThemeMode } from '@/providers/ThemeModeProvider'; + +type ToasterProps = React.ComponentProps; + +export function Toaster(props: ToasterProps) { + const { resolvedMode } = useThemeMode(); + + return ( + + ); +} diff --git a/src/providers/AppRoot.tsx b/src/providers/AppRoot.tsx index ac52fad..d46fcaa 100644 --- a/src/providers/AppRoot.tsx +++ b/src/providers/AppRoot.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { ThemeModeProvider } from './ThemeModeProvider'; import { RouterProvider } from './RouterProvider'; +import { Toaster } from '@/components/ui/sonner'; interface AppRootProps { children: React.ReactNode; @@ -11,6 +12,7 @@ export default function AppRoot({ children }: AppRootProps) { {children} + ); diff --git a/src/utils/chromeI18n.ts b/src/utils/chromeI18n.ts index f7182f3..e172fd5 100644 --- a/src/utils/chromeI18n.ts +++ b/src/utils/chromeI18n.ts @@ -26,12 +26,8 @@ export function useI18n(namespace?: string | string[]) { const namespaces = Array.isArray(namespace) ? namespace : namespace ? [namespace] : []; const t = (key: string, options?: Record): string => { - let msgId = key; - - // 处理 namespace:key 格式(兼容原 i18next 用法) - if (key.includes(':')) { - msgId = key.replace(':', '_').replace(/\./g, '_'); - } + // 统一将分隔符转换为下划线,兼容 'namespace:key.path' 和 'key.path' 两种写法 + const msgId = key.replace(':', '_').replace(/\./g, '_'); // 先尝试直接查找 key let message = getMessage(msgId);