mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
fix(ui): complete blog dropdown localization and address review comments
This commit is contained in:
parent
180d1650fb
commit
eb999e069b
6 changed files with 87 additions and 11 deletions
|
|
@ -20,9 +20,7 @@ export default function LanguageSwitcher() {
|
|||
void i18n.changeLanguage(language);
|
||||
try {
|
||||
window.localStorage.setItem(LANGUAGE_STORAGE_KEY, language);
|
||||
} catch {
|
||||
// Storage can be disabled by the browser; keep the current selection usable
|
||||
}
|
||||
} catch {}
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
import { act } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createDashboardI18n } from "@/i18n";
|
||||
import { renderWithProviders, screen } from "../../../../tests/test-utils";
|
||||
import { BlogDropdown } from "./BlogDropdown";
|
||||
|
||||
const mockRefetch = vi.fn();
|
||||
let mockIsError = false;
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/useDisableBlogPosts", () => ({
|
||||
useDisableBlogPosts: () => false,
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/blogPosts/useBlogPosts", () => ({
|
||||
useBlogPosts: () => ({
|
||||
data: { posts: [{ title: "Post One", date: "2026-02-01", description: "Description one", url: "https://example.com/1" }] },
|
||||
isLoading: false,
|
||||
isError: mockIsError,
|
||||
refetch: mockRefetch,
|
||||
}),
|
||||
}));
|
||||
|
||||
async function openDropdown() {
|
||||
await userEvent.setup().hover(screen.getByRole("button", { name: "Blog" }));
|
||||
}
|
||||
|
||||
describe("blog dropdown language changes", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockIsError = false;
|
||||
});
|
||||
|
||||
it("should translate Retry after switching languages and still refetch posts", async () => {
|
||||
mockIsError = true;
|
||||
const user = userEvent.setup();
|
||||
const i18n = createDashboardI18n();
|
||||
renderWithProviders(
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<BlogDropdown />
|
||||
</I18nextProvider>,
|
||||
);
|
||||
|
||||
await openDropdown();
|
||||
expect(await screen.findByRole("button", { name: "Retry" })).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
await i18n.changeLanguage("zh-CN");
|
||||
});
|
||||
expect(screen.getByText("加载文章失败")).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Retry" })).not.toBeInTheDocument();
|
||||
await user.click(screen.getByRole("button", { name: "重试" }));
|
||||
expect(mockRefetch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should update visible dates when switching between English and Chinese", async () => {
|
||||
const i18n = createDashboardI18n();
|
||||
renderWithProviders(
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<BlogDropdown />
|
||||
</I18nextProvider>,
|
||||
);
|
||||
|
||||
await openDropdown();
|
||||
expect(await screen.findByText("Feb 1, 2026")).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
await i18n.changeLanguage("zh-CN");
|
||||
});
|
||||
expect(screen.getByText("2026年2月1日")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Feb 1, 2026")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Post One")).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
await i18n.changeLanguage("en");
|
||||
});
|
||||
expect(screen.getByText("Feb 1, 2026")).toBeInTheDocument();
|
||||
expect(screen.queryByText("2026年2月1日")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -13,9 +13,9 @@ import {
|
|||
import { ChevronDown, LoaderCircle } from "lucide-react";
|
||||
import React from "react";
|
||||
|
||||
function formatDate(dateStr: string): string {
|
||||
function formatDate(dateStr: string, language: string): string {
|
||||
const date = new Date(dateStr + "T00:00:00");
|
||||
return date.toLocaleDateString("en-US", {
|
||||
return date.toLocaleDateString(language === "zh-CN" ? "zh-CN" : "en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
|
|
@ -23,7 +23,7 @@ function formatDate(dateStr: string): string {
|
|||
}
|
||||
|
||||
export const BlogDropdown: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
const disableBlogPosts = useDisableBlogPosts();
|
||||
|
||||
const { data, isLoading, isError, refetch } = useBlogPosts();
|
||||
|
|
@ -46,7 +46,7 @@ export const BlogDropdown: React.FC = () => {
|
|||
<div className="flex items-center gap-2 px-2 py-1.5 text-sm">
|
||||
<span className="text-destructive">{t("Failed to load posts")}</span>
|
||||
<Button variant="outline" size="sm" onClick={() => refetch()}>
|
||||
Retry
|
||||
{t("Retry")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -65,7 +65,7 @@ export const BlogDropdown: React.FC = () => {
|
|||
{post.title}
|
||||
</h5>
|
||||
<span className="text-muted-foreground" style={{ fontSize: 11 }}>
|
||||
{formatDate(post.date)}
|
||||
{formatDate(post.date, i18n.language)}
|
||||
</span>
|
||||
<p className="line-clamp-2">{post.description}</p>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ export default function LanguageProvider({ children }: { children: ReactNode })
|
|||
try {
|
||||
const stored = window.localStorage.getItem(LANGUAGE_STORAGE_KEY);
|
||||
if (isLanguage(stored)) void instance.changeLanguage(stored);
|
||||
} catch {
|
||||
// Storage can be disabled by the browser; language selection still works in memory
|
||||
}
|
||||
} catch {}
|
||||
updateDocumentLanguage(instance.language);
|
||||
return () => {
|
||||
instance.off("languageChanged", updateDocumentLanguage);
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@
|
|||
"Prompts": "Prompts",
|
||||
"Read the docs": "Read the docs",
|
||||
"Response Cache": "Response Cache",
|
||||
"Retry": "Retry",
|
||||
"Role": "Role",
|
||||
"Route every request to the cheapest model that can handle it, no prompt changes needed.": "Route every request to the cheapest model that can handle it, no prompt changes needed.",
|
||||
"Router Settings": "Router Settings",
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@
|
|||
"Prompts": "提示词",
|
||||
"Read the docs": "查看文档",
|
||||
"Response Cache": "响应缓存",
|
||||
"Retry": "重试",
|
||||
"Role": "角色",
|
||||
"Route every request to the cheapest model that can handle it, no prompt changes needed.": "自动将每个请求分配给能够处理它的最低成本模型,无需修改提示词。",
|
||||
"Router Settings": "路由设置",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue