chore(messages): update MCP server configurations in multiple languages

This commit is contained in:
Fatih Kadir Akın
2025-12-14 21:36:23 +03:00
parent 74dc1eccf3
commit 18256aadc5
16 changed files with 464 additions and 44 deletions

View File

@@ -717,5 +717,18 @@
"reportFailed": "فشل في إرسال البلاغ",
"reasonRequired": "يرجى اختيار سبب",
"alreadyReported": "لقد أبلغت عن هذا الأمر بالفعل"
},
"mcp": {
"button": "خادم MCP",
"title": "إعدادات خادم MCP",
"copy": "نسخ",
"copied": "تم النسخ!",
"customizeFilters": "تخصيص الفلاتر لتضييق نطاق الأوامر:",
"users": "المستخدمون",
"userPlaceholder": "إضافة اسم مستخدم...",
"categories": "الفئات",
"categoryPlaceholder": "إضافة رمز الفئة...",
"tags": "العلامات",
"tagPlaceholder": "إضافة رمز العلامة..."
}
}

View File

@@ -717,5 +717,18 @@
"reportFailed": "Meldung konnte nicht gesendet werden",
"reasonRequired": "Bitte wählen Sie einen Grund",
"alreadyReported": "Sie haben diesen Prompt bereits gemeldet"
},
"mcp": {
"button": "MCP-Server",
"title": "MCP-Server-Konfiguration",
"copy": "Kopieren",
"copied": "Kopiert!",
"customizeFilters": "Filter anpassen, um Prompts einzugrenzen:",
"users": "Benutzer",
"userPlaceholder": "Benutzername hinzufügen...",
"categories": "Kategorien",
"categoryPlaceholder": "Kategorie-Slug hinzufügen...",
"tags": "Tags",
"tagPlaceholder": "Tag-Slug hinzufügen..."
}
}

View File

@@ -717,5 +717,18 @@
"reportFailed": "Failed to submit report",
"reasonRequired": "Please select a reason",
"alreadyReported": "You have already reported this prompt"
},
"mcp": {
"button": "MCP Server",
"title": "MCP Server Configuration",
"copy": "Copy",
"copied": "Copied!",
"customizeFilters": "Customize filters to narrow down prompts:",
"users": "Users",
"userPlaceholder": "Add username...",
"categories": "Categories",
"categoryPlaceholder": "Add category slug...",
"tags": "Tags",
"tagPlaceholder": "Add tag slug..."
}
}

View File

@@ -717,5 +717,18 @@
"reportFailed": "Error al enviar el reporte",
"reasonRequired": "Por favor selecciona un motivo",
"alreadyReported": "Ya has reportado este prompt"
},
"mcp": {
"button": "Servidor MCP",
"title": "Configuración del Servidor MCP",
"copy": "Copiar",
"copied": "¡Copiado!",
"customizeFilters": "Personaliza los filtros para reducir los prompts:",
"users": "Usuarios",
"userPlaceholder": "Añadir nombre de usuario...",
"categories": "Categorías",
"categoryPlaceholder": "Añadir slug de categoría...",
"tags": "Etiquetas",
"tagPlaceholder": "Añadir slug de etiqueta..."
}
}

View File

@@ -717,5 +717,18 @@
"reportFailed": "Échec de l'envoi du signalement",
"reasonRequired": "Veuillez sélectionner une raison",
"alreadyReported": "Vous avez déjà signalé ce prompt"
},
"mcp": {
"button": "Serveur MCP",
"title": "Configuration du Serveur MCP",
"copy": "Copier",
"copied": "Copié !",
"customizeFilters": "Personnalisez les filtres pour affiner les prompts :",
"users": "Utilisateurs",
"userPlaceholder": "Ajouter un nom d'utilisateur...",
"categories": "Catégories",
"categoryPlaceholder": "Ajouter un slug de catégorie...",
"tags": "Tags",
"tagPlaceholder": "Ajouter un slug de tag..."
}
}

View File

@@ -717,5 +717,18 @@
"reportFailed": "Impossibile inviare la segnalazione",
"reasonRequired": "Seleziona un motivo",
"alreadyReported": "Hai già segnalato questo prompt"
},
"mcp": {
"button": "Server MCP",
"title": "Configurazione Server MCP",
"copy": "Copia",
"copied": "Copiato!",
"customizeFilters": "Personalizza i filtri per restringere i prompt:",
"users": "Utenti",
"userPlaceholder": "Aggiungi nome utente...",
"categories": "Categorie",
"categoryPlaceholder": "Aggiungi slug categoria...",
"tags": "Tag",
"tagPlaceholder": "Aggiungi slug tag..."
}
}

View File

@@ -717,5 +717,18 @@
"reportFailed": "報告の送信に失敗しました",
"reasonRequired": "理由を選択してください",
"alreadyReported": "このプロンプトは既に報告済みです"
},
"mcp": {
"button": "MCPサーバー",
"title": "MCPサーバー設定",
"copy": "コピー",
"copied": "コピーしました!",
"customizeFilters": "フィルターをカスタマイズしてプロンプトを絞り込む:",
"users": "ユーザー",
"userPlaceholder": "ユーザー名を追加...",
"categories": "カテゴリー",
"categoryPlaceholder": "カテゴリースラッグを追加...",
"tags": "タグ",
"tagPlaceholder": "タグスラッグを追加..."
}
}

View File

@@ -717,5 +717,18 @@
"reportFailed": "신고 제출에 실패했습니다",
"reasonRequired": "사유를 선택하세요",
"alreadyReported": "이미 이 프롬프트를 신고하셨습니다"
},
"mcp": {
"button": "MCP 서버",
"title": "MCP 서버 설정",
"copy": "복사",
"copied": "복사됨!",
"customizeFilters": "필터를 사용자 정의하여 프롬프트 범위를 좁히세요:",
"users": "사용자",
"userPlaceholder": "사용자 이름 추가...",
"categories": "카테고리",
"categoryPlaceholder": "카테고리 슬러그 추가...",
"tags": "태그",
"tagPlaceholder": "태그 슬러그 추가..."
}
}

View File

@@ -717,5 +717,18 @@
"reportFailed": "Falha ao enviar denúncia",
"reasonRequired": "Por favor, selecione um motivo",
"alreadyReported": "Você já denunciou este prompt"
},
"mcp": {
"button": "Servidor MCP",
"title": "Configuração do Servidor MCP",
"copy": "Copiar",
"copied": "Copiado!",
"customizeFilters": "Personalize os filtros para restringir os prompts:",
"users": "Usuários",
"userPlaceholder": "Adicionar nome de usuário...",
"categories": "Categorias",
"categoryPlaceholder": "Adicionar slug da categoria...",
"tags": "Tags",
"tagPlaceholder": "Adicionar slug da tag..."
}
}

View File

@@ -717,5 +717,18 @@
"reportFailed": "Şikayet gönderilemedi",
"reasonRequired": "Lütfen bir sebep seçin",
"alreadyReported": "Bu promptu zaten şikayet ettiniz"
},
"mcp": {
"button": "MCP Sunucusu",
"title": "MCP Sunucu Yapılandırması",
"copy": "Kopyala",
"copied": "Kopyalandı!",
"customizeFilters": "Promptları daraltmak için filtreleri özelleştirin:",
"users": "Kullanıcılar",
"userPlaceholder": "Kullanıcı adı ekle...",
"categories": "Kategoriler",
"categoryPlaceholder": "Kategori slug ekle...",
"tags": "Etiketler",
"tagPlaceholder": "Etiket slug ekle..."
}
}

View File

@@ -717,5 +717,18 @@
"reportFailed": "举报提交失败",
"reasonRequired": "请选择举报原因",
"alreadyReported": "您已经举报过此提示词"
},
"mcp": {
"button": "MCP 服务器",
"title": "MCP 服务器配置",
"copy": "复制",
"copied": "已复制!",
"customizeFilters": "自定义过滤器以缩小提示词范围:",
"users": "用户",
"userPlaceholder": "添加用户名...",
"categories": "分类",
"categoryPlaceholder": "添加分类标识...",
"tags": "标签",
"tagPlaceholder": "添加标签标识..."
}
}

6
public/mcp.svg Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200" fill="none">
<path d="M25 97.8528L92.8823 29.9706C102.255 20.598 117.451 20.598 126.823 29.9706V29.9706C136.196 39.3431 136.196 54.5391 126.823 63.9117L75.5581 115.177" stroke="black" stroke-width="12" stroke-linecap="round"/>
<path d="M76.2653 114.47L126.823 63.9117C136.196 54.5391 151.392 54.5391 160.765 63.9117L161.118 64.2652C170.491 73.6378 170.491 88.8338 161.118 98.2063L99.7248 159.6C96.6006 162.724 96.6006 167.789 99.7248 170.913L112.331 183.52" stroke="black" stroke-width="12" stroke-linecap="round"/>
<path d="M109.853 46.9411L59.6482 97.1457C50.2757 106.518 50.2757 121.714 59.6482 131.087V131.087C69.0208 140.459 84.2168 140.459 93.5894 131.087L143.794 80.8822" stroke="black" stroke-width="12" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 869 B

View File

@@ -13,6 +13,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Card, CardContent } from "@/components/ui/card";
import { PromptList } from "@/components/prompts/prompt-list";
import { PromptCard, type PromptCardProps } from "@/components/prompts/prompt-card";
import { McpServerPopup } from "@/components/mcp/mcp-server-popup";
interface UserProfilePageProps {
params: Promise<{ username: string }>;
@@ -320,15 +321,18 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
)}
</p>
</div>
{/* Edit button - desktop only */}
{isOwner && (
<Button variant="outline" size="sm" asChild className="hidden md:inline-flex shrink-0">
<Link href="/settings">
<Settings className="h-4 w-4 mr-1.5" />
{t("editProfile")}
</Link>
</Button>
)}
{/* Actions - desktop only */}
<div className="hidden md:flex items-center gap-2 shrink-0">
<McpServerPopup initialUsers={[user.username]} />
{isOwner && (
<Button variant="outline" size="sm" asChild>
<Link href="/settings">
<Settings className="h-4 w-4 mr-1.5" />
{t("editProfile")}
</Link>
</Button>
)}
</div>
</div>
{/* Stats - stacked on mobile, inline on desktop */}
@@ -354,17 +358,18 @@ export default async function UserProfilePage({ params, searchParams }: UserProf
</div>
</div>
{/* Edit button - below stats on mobile */}
{isOwner && (
<div className="md:hidden">
<Button variant="outline" size="sm" asChild className="w-full">
{/* Actions - mobile only */}
<div className="md:hidden flex gap-2">
<McpServerPopup initialUsers={[user.username]} />
{isOwner && (
<Button variant="outline" size="sm" asChild className="flex-1">
<Link href="/settings">
<Settings className="h-4 w-4 mr-1.5" />
{t("editProfile")}
</Link>
</Button>
</div>
)}
)}
</div>
</div>
{/* Tabs for Prompts and Change Requests */}

View File

@@ -8,6 +8,7 @@ import { db } from "@/lib/db";
import { Button } from "@/components/ui/button";
import { PromptList } from "@/components/prompts/prompt-list";
import { SubscribeButton } from "@/components/categories/subscribe-button";
import { McpServerPopup } from "@/components/mcp/mcp-server-popup";
interface CategoryPageProps {
params: Promise<{ slug: string }>;
@@ -114,28 +115,31 @@ export default async function CategoryPage({ params }: CategoryPageProps) {
</Link>
</Button>
<div>
<div className="flex items-center gap-2">
<h1 className="text-xl font-semibold">{category.name}</h1>
{session?.user && (
<SubscribeButton
categoryId={category.id}
categoryName={category.name}
initialSubscribed={!!isSubscribed}
pill
/>
<div className="flex items-start justify-between gap-4">
<div>
<div className="flex items-center gap-2">
<h1 className="text-xl font-semibold">{category.name}</h1>
{session?.user && (
<SubscribeButton
categoryId={category.id}
categoryName={category.name}
initialSubscribed={!!isSubscribed}
pill
/>
)}
</div>
{category.description && (
<p className="text-sm text-muted-foreground mt-1">
{category.description}
</p>
)}
<div className="flex items-center gap-3 mt-2 text-sm text-muted-foreground">
<span>{category._count.prompts} prompts</span>
<span></span>
<span>{category._count.subscribers} subscribers</span>
</div>
</div>
{category.description && (
<p className="text-sm text-muted-foreground mt-1">
{category.description}
</p>
)}
<div className="flex items-center gap-3 mt-2 text-sm text-muted-foreground">
<span>{category._count.prompts} prompts</span>
<span></span>
<span>{category._count.subscribers} subscribers</span>
</div>
<McpServerPopup initialCategories={[slug]} />
</div>
</div>

View File

@@ -6,6 +6,7 @@ import { db } from "@/lib/db";
import { auth } from "@/lib/auth";
import { Button } from "@/components/ui/button";
import { PromptCard } from "@/components/prompts/prompt-card";
import { McpServerPopup } from "@/components/mcp/mcp-server-popup";
interface TagPageProps {
params: Promise<{ slug: string }>;
@@ -112,15 +113,18 @@ export default async function TagPage({ params, searchParams }: TagPageProps) {
</Link>
</Button>
<div className="flex items-center gap-3">
<div
className="w-4 h-4 rounded-full"
style={{ backgroundColor: tag.color }}
/>
<h1 className="text-xl font-semibold">{tag.name}</h1>
<span className="text-sm text-muted-foreground">
{total} {t("prompts")}
</span>
<div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div
className="w-4 h-4 rounded-full"
style={{ backgroundColor: tag.color }}
/>
<h1 className="text-xl font-semibold">{tag.name}</h1>
<span className="text-sm text-muted-foreground">
{total} {t("prompts")}
</span>
</div>
<McpServerPopup initialTags={[slug]} />
</div>
</div>

View File

@@ -0,0 +1,258 @@
"use client";
import { useState, useMemo } from "react";
import { useTranslations } from "next-intl";
import { Check, Copy, Plus, X } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
// MCP Logo component
function McpIcon({ className }: { className?: string }) {
return (
<img
src="/mcp.svg"
alt="MCP"
className={className}
/>
);
}
export { McpIcon };
interface McpServerPopupProps {
/** Pre-filled users (usernames) */
initialUsers?: string[];
/** Pre-filled categories (slugs) */
initialCategories?: string[];
/** Pre-filled tags (slugs) */
initialTags?: string[];
/** Base URL override */
baseUrl?: string;
}
export function McpServerPopup({
initialUsers = [],
initialCategories = [],
initialTags = [],
baseUrl,
}: McpServerPopupProps) {
const t = useTranslations("mcp");
const [copied, setCopied] = useState(false);
const [users, setUsers] = useState<string[]>(initialUsers);
const [categories, setCategories] = useState<string[]>(initialCategories);
const [tags, setTags] = useState<string[]>(initialTags);
const [userInput, setUserInput] = useState("");
const [categoryInput, setCategoryInput] = useState("");
const [tagInput, setTagInput] = useState("");
// Build the MCP URL
const mcpUrl = useMemo(() => {
const base = baseUrl || (typeof window !== "undefined" ? window.location.origin : "https://prompts.chat");
const params = new URLSearchParams();
if (users.length > 0) {
params.set("users", users.join(","));
}
if (categories.length > 0) {
params.set("categories", categories.join(","));
}
if (tags.length > 0) {
params.set("tags", tags.join(","));
}
const queryString = params.toString();
return `${base}/api/mcp${queryString ? `?${queryString}` : ""}`;
}, [baseUrl, users, categories, tags]);
// Generate the JSON config
const configJson = useMemo(() => {
return JSON.stringify(
{
mcpServers: {
"prompts-chat": {
url: mcpUrl,
},
},
},
null,
2
);
}, [mcpUrl]);
const handleCopy = async () => {
await navigator.clipboard.writeText(configJson);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const addUser = () => {
const value = userInput.trim().replace(/^@/, "");
if (value && !users.includes(value)) {
setUsers([...users, value]);
setUserInput("");
}
};
const addCategory = () => {
const value = categoryInput.trim().toLowerCase();
if (value && !categories.includes(value)) {
setCategories([...categories, value]);
setCategoryInput("");
}
};
const addTag = () => {
const value = tagInput.trim().toLowerCase();
if (value && !tags.includes(value)) {
setTags([...tags, value]);
setTagInput("");
}
};
const removeUser = (user: string) => setUsers(users.filter((u) => u !== user));
const removeCategory = (cat: string) => setCategories(categories.filter((c) => c !== cat));
const removeTag = (tag: string) => setTags(tags.filter((t) => t !== tag));
return (
<Popover modal>
<PopoverTrigger asChild>
<Button variant="outline" size="sm" className="h-8 gap-1.5">
<McpIcon className="h-4 w-4" />
<span className="hidden sm:inline">{t("button")}</span>
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="w-[calc(100vw-2rem)] sm:w-[420px] p-4" sideOffset={8} collisionPadding={16}>
<div className="space-y-4">
{/* Header */}
<div className="flex items-center justify-between">
<h3 className="font-semibold text-sm flex items-center gap-2">
<McpIcon className="h-4 w-4" />
{t("title")}
</h3>
<Button
variant="ghost"
size="sm"
className="h-7 text-xs gap-1"
onClick={handleCopy}
>
{copied ? (
<>
<Check className="h-3 w-3" />
{t("copied")}
</>
) : (
<>
<Copy className="h-3 w-3" />
{t("copy")}
</>
)}
</Button>
</div>
{/* Config JSON */}
<div className="bg-muted rounded-md p-3 font-mono text-xs overflow-x-auto">
<pre className="whitespace-nowrap">{configJson}</pre>
</div>
{/* Filters */}
<div className="space-y-3 border-t pt-3">
<p className="text-xs text-muted-foreground">{t("customizeFilters")}</p>
{/* Users */}
<div className="space-y-1.5">
<label className="text-xs font-medium">{t("users")}</label>
<div className="flex gap-1.5">
<Input
value={userInput}
onChange={(e) => setUserInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && addUser()}
placeholder={t("userPlaceholder")}
className="h-7 text-xs flex-1"
/>
<Button size="sm" variant="outline" className="h-7 px-2" onClick={addUser}>
<Plus className="h-3 w-3" />
</Button>
</div>
{users.length > 0 && (
<div className="flex flex-wrap gap-1">
{users.map((user) => (
<Badge key={user} variant="secondary" className="text-xs gap-1 pr-1">
@{user}
<button onClick={() => removeUser(user)} className="hover:text-destructive">
<X className="h-3 w-3" />
</button>
</Badge>
))}
</div>
)}
</div>
{/* Categories */}
<div className="space-y-1.5">
<label className="text-xs font-medium">{t("categories")}</label>
<div className="flex gap-1.5">
<Input
value={categoryInput}
onChange={(e) => setCategoryInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && addCategory()}
placeholder={t("categoryPlaceholder")}
className="h-7 text-xs flex-1"
/>
<Button size="sm" variant="outline" className="h-7 px-2" onClick={addCategory}>
<Plus className="h-3 w-3" />
</Button>
</div>
{categories.length > 0 && (
<div className="flex flex-wrap gap-1">
{categories.map((cat) => (
<Badge key={cat} variant="secondary" className="text-xs gap-1 pr-1">
{cat}
<button onClick={() => removeCategory(cat)} className="hover:text-destructive">
<X className="h-3 w-3" />
</button>
</Badge>
))}
</div>
)}
</div>
{/* Tags */}
<div className="space-y-1.5">
<label className="text-xs font-medium">{t("tags")}</label>
<div className="flex gap-1.5">
<Input
value={tagInput}
onChange={(e) => setTagInput(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && addTag()}
placeholder={t("tagPlaceholder")}
className="h-7 text-xs flex-1"
/>
<Button size="sm" variant="outline" className="h-7 px-2" onClick={addTag}>
<Plus className="h-3 w-3" />
</Button>
</div>
{tags.length > 0 && (
<div className="flex flex-wrap gap-1">
{tags.map((tag) => (
<Badge key={tag} variant="secondary" className="text-xs gap-1 pr-1">
{tag}
<button onClick={() => removeTag(tag)} className="hover:text-destructive">
<X className="h-3 w-3" />
</button>
</Badge>
))}
</div>
)}
</div>
</div>
</div>
</PopoverContent>
</Popover>
);
}