mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-21 05:14:45 +08:00
feat: add i18n support and add Chinese (#372)
* feat: add i18n support and add Chinese * fix: resolve conflicts * Update en.json with cancle settings * Update zh.json with settngs cancle --------- Co-authored-by: johnny0120 <15564476+johnny0120@users.noreply.github.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com> Co-authored-by: Willem Jiang <143703838+willem-bd@users.noreply.github.com>
This commit is contained in:
70
web/src/components/deer-flow/language-switcher.tsx
Normal file
70
web/src/components/deer-flow/language-switcher.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
"use client";
|
||||
|
||||
import { useLocale } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTransition } from "react";
|
||||
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "~/components/ui/dropdown-menu";
|
||||
|
||||
type LanguageOption = {
|
||||
code: string;
|
||||
name: string;
|
||||
flag: string;
|
||||
};
|
||||
|
||||
const languages: Array<LanguageOption> = [
|
||||
{ code: "en", name: "English", flag: "🇺🇸" },
|
||||
{ code: "zh", name: "中文", flag: "🇨🇳" },
|
||||
];
|
||||
|
||||
export function LanguageSwitcher() {
|
||||
const locale = useLocale();
|
||||
const router = useRouter();
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const currentLanguage =
|
||||
languages.find((lang) => lang.code === locale) ??
|
||||
(languages[0] as LanguageOption);
|
||||
|
||||
const handleLanguageChange = (newLocale: string) => {
|
||||
startTransition(() => {
|
||||
console.log(`updateing locale to ${newLocale}`)
|
||||
// Set locale in cookie
|
||||
document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000; SameSite=lax`;
|
||||
// Reload the page to apply the new locale
|
||||
window.location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="sm" disabled={isPending}>
|
||||
<span className="mr-2">{currentLanguage.flag}</span>
|
||||
{currentLanguage.name}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{languages.map((language) => (
|
||||
<DropdownMenuItem
|
||||
key={language.code}
|
||||
onClick={() => handleLanguageChange(language.code)}
|
||||
className={locale === language.code ? "bg-accent" : ""}
|
||||
>
|
||||
<span className="mr-2">{language.flag}</span>
|
||||
{language.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { useMemo } from "react";
|
||||
import { useStore, useToolCalls } from "~/core/store";
|
||||
import { Tooltip } from "./tooltip";
|
||||
import { WarningFilled } from "@ant-design/icons";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export const Link = ({
|
||||
href,
|
||||
@@ -38,16 +39,14 @@ export const Link = ({
|
||||
: true;
|
||||
}, [credibleLinks, href, responding, checkLinkCredibility]);
|
||||
|
||||
const t = useTranslations("common");
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<a href={href} target="_blank" rel="noopener noreferrer">
|
||||
{children}
|
||||
</a>
|
||||
{!isCredible && (
|
||||
<Tooltip
|
||||
title="This link might be a hallucination from AI model and may not be reliable."
|
||||
delayDuration={300}
|
||||
>
|
||||
<Tooltip title={t("linkNotReliable")} delayDuration={300}>
|
||||
<WarningFilled className="text-sx transition-colors hover:!text-yellow-500" />
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "novel";
|
||||
import { Markdown } from "tiptap-markdown";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import "~/styles/prosemirror.css";
|
||||
import { resourceSuggestion } from "./resource-suggestion";
|
||||
@@ -82,6 +83,7 @@ const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
{ className, loading, config, onChange, onEnter }: MessageInputProps,
|
||||
ref,
|
||||
) => {
|
||||
const t = useTranslations("messageInput");
|
||||
const editorRef = useRef<Editor>(null);
|
||||
const handleEnterRef = useRef<
|
||||
((message: string, resources: Array<Resource>) => void) | undefined
|
||||
@@ -136,9 +138,7 @@ const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
}),
|
||||
Placeholder.configure({
|
||||
showOnlyCurrent: false,
|
||||
placeholder: config?.rag.provider
|
||||
? "What can I do for you? \nYou may refer to RAG resources by using @."
|
||||
: "What can I do for you?",
|
||||
placeholder: config?.rag.provider ? t("placeholderWithRag") : t("placeholder"),
|
||||
emptyEditorClass: "placeholder",
|
||||
}),
|
||||
Extension.create({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import { Check, FileText, Newspaper, Users, GraduationCap } from "lucide-react";
|
||||
|
||||
@@ -21,31 +22,32 @@ import { Tooltip } from "./tooltip";
|
||||
const REPORT_STYLES = [
|
||||
{
|
||||
value: "academic" as const,
|
||||
label: "Academic",
|
||||
description: "Formal, objective, and analytical with precise terminology",
|
||||
labelKey: "academic",
|
||||
descriptionKey: "academicDesc",
|
||||
icon: GraduationCap,
|
||||
},
|
||||
{
|
||||
value: "popular_science" as const,
|
||||
label: "Popular Science",
|
||||
description: "Engaging and accessible for general audience",
|
||||
labelKey: "popularScience",
|
||||
descriptionKey: "popularScienceDesc",
|
||||
icon: FileText,
|
||||
},
|
||||
{
|
||||
value: "news" as const,
|
||||
label: "News",
|
||||
description: "Factual, concise, and impartial journalistic style",
|
||||
labelKey: "news",
|
||||
descriptionKey: "newsDesc",
|
||||
icon: Newspaper,
|
||||
},
|
||||
{
|
||||
value: "social_media" as const,
|
||||
label: "Social Media",
|
||||
description: "Concise, attention-grabbing, and shareable",
|
||||
labelKey: "socialMedia",
|
||||
descriptionKey: "socialMediaDesc",
|
||||
icon: Users,
|
||||
},
|
||||
];
|
||||
|
||||
export function ReportStyleDialog() {
|
||||
const t = useTranslations("settings.reportStyle");
|
||||
const [open, setOpen] = useState(false);
|
||||
const currentStyle = useSettingsStore((state) => state.general.reportStyle);
|
||||
|
||||
@@ -68,12 +70,9 @@ export function ReportStyleDialog() {
|
||||
title={
|
||||
<div>
|
||||
<h3 className="mb-2 font-bold">
|
||||
Writing Style: {currentStyleConfig.label}
|
||||
{t("writingStyle")}: {t(currentStyleConfig.labelKey)}
|
||||
</h3>
|
||||
<p>
|
||||
Choose the writing style for your research reports. Different
|
||||
styles are optimized for different audiences and purposes.
|
||||
</p>
|
||||
<p>{t("chooseDesc")}</p>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
@@ -82,17 +81,14 @@ export function ReportStyleDialog() {
|
||||
className="!border-brand !text-brand rounded-2xl"
|
||||
variant="outline"
|
||||
>
|
||||
<CurrentIcon className="h-4 w-4" /> {currentStyleConfig.label}
|
||||
<CurrentIcon className="h-4 w-4" /> {t(currentStyleConfig.labelKey)}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
</Tooltip>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Choose Writing Style</DialogTitle>
|
||||
<DialogDescription>
|
||||
Select the writing style for your research reports. Each style is
|
||||
optimized for different audiences and purposes.
|
||||
</DialogDescription>
|
||||
<DialogTitle>{t("chooseTitle")}</DialogTitle>
|
||||
<DialogDescription>{t("chooseDesc")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-3 py-4">
|
||||
{REPORT_STYLES.map((style) => {
|
||||
@@ -111,11 +107,11 @@ export function ReportStyleDialog() {
|
||||
<Icon className="mt-0.5 h-5 w-5 shrink-0" />
|
||||
<div className="flex-1 space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="font-medium">{style.label}</h4>
|
||||
<h4 className="font-medium">{t(style.labelKey)}</h4>
|
||||
{isSelected && <Check className="text-primary h-4 w-4" />}
|
||||
</div>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{style.description}
|
||||
{t(style.descriptionKey)}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { useTranslations } from "next-intl";
|
||||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||||
import type { Resource } from "~/core/messages";
|
||||
import { cn } from "~/lib/utils";
|
||||
@@ -14,6 +15,7 @@ export const ResourceMentions = forwardRef<
|
||||
{ onKeyDown: (args: { event: KeyboardEvent }) => boolean },
|
||||
ResourceMentionsProps
|
||||
>((props, ref) => {
|
||||
const t = useTranslations("common")
|
||||
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||
|
||||
const selectItem = (index: number) => {
|
||||
@@ -79,7 +81,7 @@ export const ResourceMentions = forwardRef<
|
||||
))
|
||||
) : (
|
||||
<div className="items-center justify-center text-gray-500">
|
||||
No result
|
||||
{t("noResult")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user