Compare commits

...

1 Commits

Author SHA1 Message Date
雨霖铃 fd269348b4 fix: add missing Toaster component to enable toast display
The project was calling toast.success/error/warning from sonner in 7 files,
but the <Toaster> 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 <Toaster /> 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
2026-06-05 19:24:03 +08:00
3 changed files with 30 additions and 6 deletions
+26
View File
@@ -0,0 +1,26 @@
import { Toaster as Sonner } from 'sonner';
import { useThemeMode } from '@/providers/ThemeModeProvider';
type ToasterProps = React.ComponentProps<typeof Sonner>;
export function Toaster(props: ToasterProps) {
const { resolvedMode } = useThemeMode();
return (
<Sonner
theme={resolvedMode}
className="toaster group"
position="bottom-center"
toastOptions={{
classNames: {
toast:
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
description: 'group-[.toast]:text-muted-foreground',
actionButton: 'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
cancelButton: 'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
},
}}
{...props}
/>
);
}
+2
View File
@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { ThemeModeProvider } from './ThemeModeProvider'; import { ThemeModeProvider } from './ThemeModeProvider';
import { RouterProvider } from './RouterProvider'; import { RouterProvider } from './RouterProvider';
import { Toaster } from '@/components/ui/sonner';
interface AppRootProps { interface AppRootProps {
children: React.ReactNode; children: React.ReactNode;
@@ -11,6 +12,7 @@ export default function AppRoot({ children }: AppRootProps) {
<React.StrictMode> <React.StrictMode>
<ThemeModeProvider> <ThemeModeProvider>
<RouterProvider>{children}</RouterProvider> <RouterProvider>{children}</RouterProvider>
<Toaster />
</ThemeModeProvider> </ThemeModeProvider>
</React.StrictMode> </React.StrictMode>
); );
+2 -6
View File
@@ -26,12 +26,8 @@ export function useI18n(namespace?: string | string[]) {
const namespaces = Array.isArray(namespace) ? namespace : namespace ? [namespace] : []; const namespaces = Array.isArray(namespace) ? namespace : namespace ? [namespace] : [];
const t = (key: string, options?: Record<string, unknown>): string => { const t = (key: string, options?: Record<string, unknown>): string => {
let msgId = key; // 统一将分隔符转换为下划线,兼容 'namespace:key.path' 和 'key.path' 两种写法
const msgId = key.replace(':', '_').replace(/\./g, '_');
// 处理 namespace:key 格式(兼容原 i18next 用法)
if (key.includes(':')) {
msgId = key.replace(':', '_').replace(/\./g, '_');
}
// 先尝试直接查找 key // 先尝试直接查找 key
let message = getMessage(msgId); let message = getMessage(msgId);