@@ -10,6 +10,7 @@ import { Button } from "@/app/components/ui/button";
1010import { useCallback , useRef , useState } from "react" ;
1111import { useRouter } from "next/navigation" ;
1212import Link from "next/link" ;
13+ import { useTranslations } from "next-intl" ;
1314import type { UserView } from "@/lib/use-auth" ;
1415import type { PostRequest , ApiResponse , PostView } from "@/app/types/post" ;
1516import {
@@ -38,6 +39,7 @@ function titleToSlug(title: string): string {
3839
3940export function EditorPageClient ( { user } : EditorPageClientProps ) {
4041 const router = useRouter ( ) ;
42+ const t = useTranslations ( "editor" ) ;
4143 const [ isPublishing , setIsPublishing ] = useState ( false ) ;
4244 const [ imageCount , setImageCount ] = useState ( 0 ) ;
4345 const editorRef = useRef < MarkdownEditorHandle | null > ( null ) ;
@@ -61,9 +63,7 @@ export function EditorPageClient({ user }: EditorPageClientProps) {
6163 // 否则 R2 返 403 SignatureDoesNotMatch。
6264 const primaryMime = file . type . split ( ";" ) [ 0 ] ! . trim ( ) . toLowerCase ( ) ;
6365 if ( ! primaryMime ) {
64- throw new Error (
65- `无法识别图片类型:${ file . name } (浏览器未给出 MIME),请另存为 PNG/JPG/WebP 后重试` ,
66- ) ;
66+ throw new Error ( t ( "errors.imageType" , { filename : file . name } ) ) ;
6767 }
6868
6969 const token = localStorage . getItem ( "satoken" ) ?? "" ;
@@ -83,7 +83,7 @@ export function EditorPageClient({ user }: EditorPageClientProps) {
8383
8484 if ( ! response . ok ) {
8585 const error = await response . json ( ) ;
86- throw new Error ( error . error || "获取上传链接失败" ) ;
86+ throw new Error ( error . error || t ( "errors.uploadLink" ) ) ;
8787 }
8888
8989 const { uploadUrl, publicUrl } = await response . json ( ) ;
@@ -96,7 +96,9 @@ export function EditorPageClient({ user }: EditorPageClientProps) {
9696 } ) ;
9797
9898 if ( ! uploadResponse . ok ) {
99- throw new Error ( `上传图片失败: ${ uploadResponse . statusText } ` ) ;
99+ throw new Error (
100+ t ( "errors.imageUpload" , { statusText : uploadResponse . statusText } ) ,
101+ ) ;
100102 }
101103
102104 return { blobUrl, publicUrl } ;
@@ -107,7 +109,7 @@ export function EditorPageClient({ user }: EditorPageClientProps) {
107109
108110 try {
109111 if ( ! title . trim ( ) ) {
110- alert ( "请输入文章标题" ) ;
112+ alert ( t ( "errors.titleRequired" ) ) ;
111113 return ;
112114 }
113115
@@ -117,9 +119,7 @@ export function EditorPageClient({ user }: EditorPageClientProps) {
117119 : titleToSlug ( title ) ;
118120
119121 if ( rawSlug && ! FILENAME_PATTERN . test ( rawSlug ) ) {
120- alert (
121- "文件名仅支持字母、数字、连字符或下划线,并需以字母或数字开头(已自动清洗空格和特殊符号)。" ,
122- ) ;
122+ alert ( t ( "errors.invalidFilename" ) ) ;
123123 return ;
124124 }
125125
@@ -133,7 +133,7 @@ export function EditorPageClient({ user }: EditorPageClientProps) {
133133
134134 const editorHandle = editorRef . current ;
135135 if ( ! editorHandle ) {
136- throw new Error ( "编辑器尚未就绪,无法上传图片" ) ;
136+ throw new Error ( t ( "errors.editorNotReady" ) ) ;
137137 }
138138
139139 // 清理编辑器中未被 Markdown 正文引用的孤儿图片
@@ -158,7 +158,7 @@ export function EditorPageClient({ user }: EditorPageClientProps) {
158158
159159 const token = localStorage . getItem ( "satoken" ) ?? "" ;
160160 if ( ! token ) {
161- throw new Error ( "请先登录后再发布" ) ;
161+ throw new Error ( t ( "errors.loginRequired" ) ) ;
162162 }
163163
164164 const postRequest : PostRequest = {
@@ -186,20 +186,25 @@ export function EditorPageClient({ user }: EditorPageClientProps) {
186186 const body = await res . json ( ) . catch ( ( ) => ( { } ) ) ;
187187 throw new Error (
188188 ( body as { message ?: string } ) . message ??
189- `发布失败(HTTP ${ res . status } )` ,
189+ t ( "errors.publishFailedHttp" , { status : res . status } ) ,
190190 ) ;
191191 }
192192
193193 const body = ( await res . json ( ) ) as ApiResponse < PostView > ;
194194 if ( ! body . success || ! body . data ) {
195- throw new Error ( body . message ?? "发布失败,请重试" ) ;
195+ throw new Error ( body . message ?? t ( "errors.publishFailedRetry" ) ) ;
196196 }
197197
198198 const { slug : finalSlug , authorUsername } = body . data ;
199199 router . push ( `/u/${ authorUsername } /posts/${ finalSlug } ` ) ;
200200 } catch ( error ) {
201201 console . error ( "发布失败:" , error ) ;
202- alert ( `发布失败:${ error instanceof Error ? error . message : "未知错误" } ` ) ;
202+ alert (
203+ t ( "errors.publishFailed" , {
204+ message :
205+ error instanceof Error ? error . message : t ( "errors.unknownError" ) ,
206+ } ) ,
207+ ) ;
203208 } finally {
204209 setIsPublishing ( false ) ;
205210 }
@@ -212,13 +217,11 @@ export function EditorPageClient({ user }: EditorPageClientProps) {
212217 { /* 头部 */ }
213218 < header className = "mb-8 flex items-center justify-between" >
214219 < div >
215- < h1 className = "text-3xl font-bold" > 写篇文章</ h1 >
216- < p className = "text-muted-foreground mt-1" >
217- 写完直接发布,想进知识库再一键投稿。
218- </ p >
220+ < h1 className = "text-3xl font-bold" > { t ( "pageTitle" ) } </ h1 >
221+ < p className = "text-muted-foreground mt-1" > { t ( "pageSubtitle" ) } </ p >
219222 </ div >
220223 < Link href = "/" >
221- < Button variant = "outline" > 返回首页 </ Button >
224+ < Button variant = "outline" > { t ( "backHome" ) } </ Button >
222225 </ Link >
223226 </ header >
224227
@@ -230,60 +233,61 @@ export function EditorPageClient({ user }: EditorPageClientProps) {
230233 { /* Markdown 编辑器 */ }
231234 < div >
232235 < div className = "mb-2 flex items-center justify-between" >
233- < h2 className = "text-lg font-semibold" > 文章内容 </ h2 >
236+ < h2 className = "text-lg font-semibold" > { t ( "contentHeading" ) } </ h2 >
234237 < div className = "text-sm text-muted-foreground" >
235- { markdown . length } 字符 · { imageCount } 张图片
238+ { t ( "stats" , { characters : markdown . length , images : imageCount } ) }
236239 </ div >
237240 </ div >
238241 < MarkdownEditor
239242 ref = { editorRef }
240243 onImagesChange = { handleImageCountChange }
244+ defaultMarkdown = { t ( "defaultMarkdown" ) }
241245 />
242246 </ div >
243247
244248 { /* 操作区 */ }
245249 < div className = "flex items-center justify-between rounded-lg border border-border bg-card p-4" >
246250 < div className = "text-sm text-muted-foreground" >
247251 { ! title . trim ( ) ? (
248- < span className = "text-destructive" > 请填写标题 </ span >
252+ < span className = "text-destructive" > { t ( "titleRequired" ) } </ span >
249253 ) : previewSlug ? (
250254 < span >
251- 将发布到 { " " }
255+ { t ( "publishTo" ) } { " " }
252256 < code className = "font-mono text-foreground" >
253257 /u/{ user . username } /posts/{ previewSlug }
254258 </ code >
255259 </ span >
256260 ) : (
257- < span > 发布后 slug 由标题自动生成 </ span >
261+ < span > { t ( "autoSlug" ) } </ span >
258262 ) }
259263 </ div >
260264
261265 < div className = "flex gap-2" >
262266 < Button
263267 variant = "outline"
264268 onClick = { ( ) => {
265- if ( confirm ( "确定要清空所有内容吗?" ) ) {
269+ if ( confirm ( t ( "clearConfirm" ) ) ) {
266270 useEditorStore . getState ( ) . reset ( ) ;
267271 window . location . reload ( ) ;
268272 }
269273 } }
270274 >
271- 清空
275+ { t ( "clear" ) }
272276 </ Button >
273277
274278 < Button onClick = { handlePublish } disabled = { ! canPublish } >
275- { isPublishing ? "发布中..." : "发布文章" }
279+ { isPublishing ? t ( "publishing" ) : t ( "publish" ) }
276280 </ Button >
277281 </ div >
278282 </ div >
279283
280284 { /* 流程提示 */ }
281285 < div className = "rounded-lg border border-green-200 bg-green-50 p-4 text-sm dark:border-green-900 dark:bg-green-950" >
282- < h3 className = "font-medium mb-2" > 写完直接发 </ h3 >
286+ < h3 className = "font-medium mb-2" > { t ( "directPublish" ) } </ h3 >
283287 < ul className = "space-y-1 text-muted-foreground list-disc list-inside" >
284- < li > 图片粘贴后自动上传到 CDN,发布时无需额外处理 </ li >
285- < li > 发布即可见,链接可直接分享,不等 review </ li >
286- < li > 想进知识库?发布后点「收录进知识库」一键投稿 </ li >
288+ < li > { t ( "tips.imageUpload" ) } </ li >
289+ < li > { t ( "tips.publishImmediately" ) } </ li >
290+ < li > { t ( "tips.promoteToDocs" ) } </ li >
287291 </ ul >
288292 </ div >
289293 </ div >
0 commit comments