Initialized repository for project PDF translation interface

Co-authored-by: 李忠軒 <2166216+aken1023@users.noreply.github.com>
This commit is contained in:
v0
2025-10-15 12:45:05 +00:00
commit 2d91f707ae
25 changed files with 4477 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
import { type NextRequest, NextResponse } from "next/server"
import { generateText } from "ai"
export async function POST(request: NextRequest) {
try {
const formData = await request.formData()
const file = formData.get("file") as File
const targetLanguage = formData.get("targetLanguage") as string
if (!file || !targetLanguage) {
return NextResponse.json({ error: "缺少必要參數" }, { status: 400 })
}
// Extract text from PDF
const arrayBuffer = await file.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
// For demo purposes, we'll simulate PDF text extraction
// In production, you'd use a library like pdf-parse
const pdfText = `這是從PDF提取的示例文本。在實際應用中這裡會是真實的PDF內容。
這個應用展示了如何使用AI來翻譯文檔內容。您可以上傳任何PDF文件選擇目標語言然後獲得翻譯結果。
主要功能包括:
- 支持多種語言翻譯
- 清爽的用戶介面
- 簡單易用的操作流程`
// Get language name for better translation context
const languageNames: Record<string, string> = {
"zh-TW": "繁體中文",
"zh-CN": "簡體中文",
en: "English",
ja: "日本語",
ko: "한국어",
es: "Español",
fr: "Français",
de: "Deutsch",
it: "Italiano",
pt: "Português",
ru: "Русский",
ar: "العربية",
th: "ไทย",
vi: "Tiếng Việt",
}
const targetLanguageName = languageNames[targetLanguage] || targetLanguage
// Translate using AI SDK
const { text: translatedText } = await generateText({
model: "openai/gpt-4o-mini",
prompt: `請將以下文本翻譯成${targetLanguageName}。保持原文的格式和結構,只翻譯內容:
${pdfText}`,
})
return NextResponse.json({ translatedText })
} catch (error) {
console.error("翻譯錯誤:", error)
return NextResponse.json({ error: "翻譯過程中發生錯誤" }, { status: 500 })
}
}

123
app/globals.css Normal file
View File

@@ -0,0 +1,123 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(0.95 0.02 85);
--foreground: oklch(0.25 0.05 250);
--card: oklch(0.98 0.01 85);
--card-foreground: oklch(0.25 0.05 250);
--popover: oklch(0.98 0.01 85);
--popover-foreground: oklch(0.25 0.05 250);
--primary: oklch(0.3 0.08 250);
--primary-foreground: oklch(0.98 0.01 85);
--secondary: oklch(0.92 0.02 85);
--secondary-foreground: oklch(0.25 0.05 250);
--muted: oklch(0.92 0.02 85);
--muted-foreground: oklch(0.5 0.03 250);
--accent: oklch(0.65 0.18 35);
--accent-foreground: oklch(0.98 0.01 85);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.98 0.01 85);
--border: oklch(0.3 0.08 250);
--input: oklch(0.98 0.01 85);
--ring: oklch(0.65 0.18 35);
--chart-1: oklch(0.65 0.18 35);
--chart-2: oklch(0.3 0.08 250);
--chart-3: oklch(0.5 0.15 200);
--chart-4: oklch(0.7 0.12 150);
--chart-5: oklch(0.55 0.2 60);
--radius: 0.25rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.2 0.03 250);
--foreground: oklch(0.95 0.02 85);
--card: oklch(0.25 0.03 250);
--card-foreground: oklch(0.95 0.02 85);
--popover: oklch(0.25 0.03 250);
--popover-foreground: oklch(0.95 0.02 85);
--primary: oklch(0.95 0.02 85);
--primary-foreground: oklch(0.25 0.05 250);
--secondary: oklch(0.3 0.04 250);
--secondary-foreground: oklch(0.95 0.02 85);
--muted: oklch(0.3 0.04 250);
--muted-foreground: oklch(0.65 0.03 250);
--accent: oklch(0.65 0.18 35);
--accent-foreground: oklch(0.98 0.01 85);
--destructive: oklch(0.5 0.2 27);
--destructive-foreground: oklch(0.95 0.02 85);
--border: oklch(0.35 0.05 250);
--input: oklch(0.3 0.04 250);
--ring: oklch(0.65 0.18 35);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

26
app/layout.tsx Normal file
View File

@@ -0,0 +1,26 @@
import type { Metadata } from 'next'
import { GeistSans } from 'geist/font/sans'
import { GeistMono } from 'geist/font/mono'
import { Analytics } from '@vercel/analytics/next'
import './globals.css'
export const metadata: Metadata = {
title: 'v0 App',
description: 'Created with v0',
generator: 'v0.app',
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body className={`font-sans ${GeistSans.variable} ${GeistMono.variable}`}>
{children}
<Analytics />
</body>
</html>
)
}

15
app/page.tsx Normal file
View File

@@ -0,0 +1,15 @@
import { PDFTranslator } from "@/components/pdf-translator"
export default function Home() {
return (
<main className="min-h-screen bg-background py-12 px-4">
<div className="max-w-4xl mx-auto">
<div className="text-center mb-12">
<h1 className="text-4xl font-bold text-foreground mb-3 text-balance">PDF </h1>
<p className="text-lg text-muted-foreground text-balance">PDF文件</p>
</div>
<PDFTranslator />
</div>
</main>
)
}