mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-22 21:54: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:
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
import { Button } from "~/components/ui/button";
|
||||
@@ -29,6 +30,7 @@ export function AddMCPServerDialog({
|
||||
}: {
|
||||
onAdd?: (servers: MCPServerMetadata[]) => void;
|
||||
}) {
|
||||
const t = useTranslations("settings");
|
||||
const [open, setOpen] = useState(false);
|
||||
const [input, setInput] = useState("");
|
||||
const [validationError, setValidationError] = useState<string | null>("");
|
||||
@@ -50,7 +52,7 @@ export function AddMCPServerDialog({
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
setValidationError("Invalid JSON");
|
||||
setValidationError(t("invalidJson"));
|
||||
return;
|
||||
}
|
||||
const result = MCPConfigSchema.safeParse(JSON.parse(value));
|
||||
@@ -65,17 +67,17 @@ export function AddMCPServerDialog({
|
||||
}
|
||||
}
|
||||
const errorMessage =
|
||||
result.error.errors[0]?.message ?? "Validation failed";
|
||||
result.error.errors[0]?.message ?? t("validationFailed");
|
||||
setValidationError(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const keys = Object.keys(result.data.mcpServers);
|
||||
if (keys.length === 0) {
|
||||
setValidationError("Missing server name in `mcpServers`");
|
||||
setValidationError(t("missingServerName"));
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
}, [t]);
|
||||
|
||||
const handleAdd = useCallback(async () => {
|
||||
abortControllerRef.current = new AbortController();
|
||||
@@ -139,21 +141,21 @@ export function AddMCPServerDialog({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button size="sm">Add Servers</Button>
|
||||
<Button size="sm">{t("addServers")}</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[560px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add New MCP Servers</DialogTitle>
|
||||
<DialogTitle>{t("addNewMCPServers")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
DeerFlow uses the standard JSON MCP config to create a new server.
|
||||
{t("mcpConfigDescription")}
|
||||
<br />
|
||||
Paste your config below and click "Add" to add new servers.
|
||||
{t("pasteConfigBelow")}
|
||||
</DialogDescription>
|
||||
|
||||
<main>
|
||||
<Textarea
|
||||
className="h-[360px] sm:max-w-[510px] break-all"
|
||||
className="h-[360px] break-all sm:max-w-[510px]"
|
||||
placeholder={
|
||||
'Example:\n\n{\n "mcpServers": {\n "My Server": {\n "command": "python",\n "args": [\n "-m", "mcp_server"\n ],\n "env": {\n "API_KEY": "YOUR_API_KEY"\n }\n }\n }\n}'
|
||||
}
|
||||
@@ -169,7 +171,7 @@ export function AddMCPServerDialog({
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
{t("cancel", { defaultValue: "Cancel" })}
|
||||
</Button>
|
||||
<Button
|
||||
className="w-24"
|
||||
@@ -178,7 +180,7 @@ export function AddMCPServerDialog({
|
||||
onClick={handleAdd}
|
||||
>
|
||||
{processing && <Loader2 className="animate-spin" />}
|
||||
Add
|
||||
{t("add")}
|
||||
</Button>
|
||||
{
|
||||
processing && (
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Settings } from "lucide-react";
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { Tooltip } from "~/components/deer-flow/tooltip";
|
||||
@@ -29,6 +30,8 @@ import { cn } from "~/lib/utils";
|
||||
import { SETTINGS_TABS } from "../tabs";
|
||||
|
||||
export function SettingsDialog() {
|
||||
const t = useTranslations('settings');
|
||||
const tCommon = useTranslations('common');
|
||||
const { isReplay } = useReplay();
|
||||
const [activeTabId, setActiveTabId] = useState(SETTINGS_TABS[0]!.id);
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -92,7 +95,7 @@ export function SettingsDialog() {
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<Tooltip title="Settings">
|
||||
<Tooltip title={tCommon('settings')}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" size="icon">
|
||||
<Settings />
|
||||
@@ -101,9 +104,9 @@ export function SettingsDialog() {
|
||||
</Tooltip>
|
||||
<DialogContent className="sm:max-w-[850px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>DeerFlow Settings</DialogTitle>
|
||||
<DialogTitle>{t('title')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Manage your DeerFlow settings here.
|
||||
{t('description')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Tabs value={activeTabId}>
|
||||
@@ -157,10 +160,10 @@ export function SettingsDialog() {
|
||||
</Tabs>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
{tCommon('cancel')}
|
||||
</Button>
|
||||
<Button className="w-24" type="submit" onClick={handleSave}>
|
||||
Save
|
||||
{tCommon('save')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Settings } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
@@ -47,6 +48,7 @@ export const GeneralTab: Tab = ({
|
||||
settings: SettingsState;
|
||||
onChange: (changes: Partial<SettingsState>) => void;
|
||||
}) => {
|
||||
const t = useTranslations("settings.general");
|
||||
const generalSettings = useMemo(() => settings.general, [settings]);
|
||||
const form = useForm<z.infer<typeof generalFormSchema>>({
|
||||
resolver: zodResolver(generalFormSchema, undefined, undefined),
|
||||
@@ -75,7 +77,7 @@ export const GeneralTab: Tab = ({
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<header>
|
||||
<h1 className="text-lg font-medium">General</h1>
|
||||
<h1 className="text-lg font-medium">{t("title")}</h1>
|
||||
</header>
|
||||
<main>
|
||||
<Form {...form}>
|
||||
@@ -93,7 +95,7 @@ export const GeneralTab: Tab = ({
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm" htmlFor="autoAcceptedPlan">
|
||||
Allow automatic acceptance of plans
|
||||
{t("autoAcceptPlan")}
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
@@ -105,7 +107,7 @@ export const GeneralTab: Tab = ({
|
||||
name="maxPlanIterations"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Max plan iterations</FormLabel>
|
||||
<FormLabel>{t("maxPlanIterations")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="w-60"
|
||||
@@ -118,8 +120,7 @@ export const GeneralTab: Tab = ({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Set to 1 for single-step planning. Set to 2 or more to
|
||||
enable re-planning.
|
||||
{t("maxPlanIterationsDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -130,7 +131,7 @@ export const GeneralTab: Tab = ({
|
||||
name="maxStepNum"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Max steps of a research plan</FormLabel>
|
||||
<FormLabel>{t("maxStepsOfPlan")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="w-60"
|
||||
@@ -142,9 +143,7 @@ export const GeneralTab: Tab = ({
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
By default, each research plan has 3 steps.
|
||||
</FormDescription>
|
||||
<FormDescription>{t("maxStepsDescription")}</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -154,7 +153,7 @@ export const GeneralTab: Tab = ({
|
||||
name="maxSearchResults"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Max search results</FormLabel>
|
||||
<FormLabel>{t("maxSearchResults")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="w-60"
|
||||
@@ -167,7 +166,7 @@ export const GeneralTab: Tab = ({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
By default, each search step has 3 results.
|
||||
{t("maxSearchResultsDescription")}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
Reference in New Issue
Block a user