diff --git a/apps/admin/src/components/MediaLibraryModal.tsx b/apps/admin/src/components/MediaLibraryModal.tsx index cac56f9..76705c5 100644 --- a/apps/admin/src/components/MediaLibraryModal.tsx +++ b/apps/admin/src/components/MediaLibraryModal.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { API_BASE } from '@homaroma/types'; import { X, Upload, Loader2, Image as ImageIcon, Video } from 'lucide-react'; @@ -16,6 +17,7 @@ interface MediaItem { } export default function MediaLibraryModal({ isOpen, onClose, onSelect }: MediaLibraryModalProps) { + const { t } = useTranslation(); const [media, setMedia] = useState([]); const [loading, setLoading] = useState(false); const [uploading, setUploading] = useState(false); @@ -63,11 +65,11 @@ export default function MediaLibraryModal({ isOpen, onClose, onSelect }: MediaLi } } else { const err = await res.json(); - alert(`Upload failed: ${err.error || 'Unknown error'}`); + alert(t('mediaLibrary.alertUploadFailed', { error: err.error || 'Unknown error' })); } } catch (error) { console.error('Failed to upload', error); - alert('Failed to upload media file.'); + alert(t('mediaLibrary.alertUploadGeneric')); } finally { setUploading(false); e.target.value = ''; // Reset input @@ -90,9 +92,9 @@ export default function MediaLibraryModal({ isOpen, onClose, onSelect }: MediaLi

- Media Library + {t('mediaLibrary.modalTitle')}

-

Select or upload images and videos (.mp4, .webm) for your content.

+

{t('mediaLibrary.modalSubtitle')}

))} @@ -665,7 +667,7 @@ export default function AddSectionModal({ isOpen, onClose, onSelectBlock }: AddS {filteredBlocks.length === 0 ? (
-

No blocks found matching "{searchQuery}".

+

{t('pageBuilderGallery.noBlocksFound', { search: searchQuery })}

) : ( filteredBlocks.map((block) => ( @@ -708,14 +710,14 @@ export default function AddSectionModal({ isOpen, onClose, onSelectBlock }: AddS {/* Hover Selection Footer */}
- Category: {block.category} + {t('pageBuilderGallery.categoryLabel', { cat: block.category })}
diff --git a/apps/admin/src/locales/en/translation.json b/apps/admin/src/locales/en/translation.json index 944fd83..7fe624f 100644 --- a/apps/admin/src/locales/en/translation.json +++ b/apps/admin/src/locales/en/translation.json @@ -1199,6 +1199,110 @@ "thUnits": "Units", "thRevenueCol": "Revenue", "thPercentTotal": "% Total" + }, + "pageBuilder": { + "title": "Page Builder (CMS)", + "createPageBtn": "Create Page", + "orderSavedSuccess": "Homepage section ordering updated successfully.", + "orderSaveError": "Error saving layout order. Please try again.", + "pageTitleLabel": "Page Title", + "pageTitlePlaceholder": "Enter page title...", + "pageUrlLabel": "Page URL", + "copyUrlSuccess": "URL Copied", + "copyUrlTooltip": "Copy page URL", + "publishToWebsite": "Publish to Website", + "badgePublished": "Published", + "badgeDraft": "Draft", + "btnSaveSettings": "Save Settings", + "btnPreviewLive": "Preview Live", + "btnDeletePage": "Delete this page", + "confirmDeletePage": "Are you sure you want to delete the page \"{{title}}\"? This will delete all of its section blocks permanently!", + "sectionsTitle": "Page Sections", + "sectionsSubtitle": "Manage the content blocks for this page.", + "btnReorderLayout": "Reorder Layout", + "btnAddSection": "Add Section", + "selectBlockLabel": "Select Block to Edit", + "chooseBlockPlaceholder": "-- Choose a block --", + "hiddenBadge": "(Hidden)", + "editBlockTitle": "Edit Block Settings", + "createBlockTitle": "Create Layout Block", + "badgeType": "Type:", + "badgeBlockId": "Block ID:", + "badgePage": "Page:", + "badgePageId": "Page ID:", + "blockTypeLabel": "Block Type", + "browseVisualGallery": "Browse Visual Gallery", + "btnSaveBlock": "Save Block Settings", + "btnCreateBlock": "Create Block", + "btnCancel": "Cancel", + "emptyBlockPrompt": "Select a block from the left to edit its content", + "reorderModalTitle": "Reorder Layout", + "noSectionsInPage": "No content sections in this page yet.", + "hiddenTag": "Hidden", + "tooltipHideSection": "Hide Section", + "tooltipShowSection": "Show Section", + "tooltipEditBlock": "Edit Block Content", + "tooltipDeleteBlock": "Delete Block", + "confirmDeleteSection": "Are you sure you want to delete this section?", + "btnSaveOrder": "Save Order", + "btnClose": "Close", + "createPageModalTitle": "Create New Page", + "pageSlugField": "Page Slug (URL Path)", + "pageSlugPlaceholder": "e.g. about", + "slugHint": "Will be converted to lowercase, slugified format (e.g. /pages/about-us).", + "btnCreating": "Creating...", + "btnCreateSubmit": "Create Page", + "errCreatePage": "Failed to create page", + "errSaveSettings": "Failed to save page settings", + "errDeletePage": "Failed to delete page", + "errSaveBlock": "Failed to save: {{error}}" + }, + "pageBuilderGallery": { + "galleryTitle": "Add Block Section", + "gallerySubtitle": "Select a visual block component to insert into your page layout.", + "searchPlaceholder": "Search blocks...", + "categoryLabel": "Category: {{cat}}", + "btnSelectBlock": "Select Block", + "noBlocksFound": "No blocks found matching \"{{search}}\".", + "categories": { + "all": "All Blocks", + "hero": "Banner & Hero", + "products": "Products & Shop", + "editorial": "Story & Editorial", + "articles": "Article Elements", + "forms": "Forms & Info", + "custom": "Product & Custom" + } + }, + "mediaLibrary": { + "title": "Media Library", + "subtitle": "Manage your uploaded images", + "btnUploading": "Uploading...", + "btnUploadNew": "Upload New Image", + "btnUploadNewFile": "Upload New File", + "pasteHintPrefix": "Paste", + "pasteHint": "an image anywhere on this page (Ctrl+V / ⌘+V) to upload instantly", + "loading": "Loading Media...", + "clickToCopy": "Click to copy URL", + "copyUrl": "Copy URL", + "deleteTooltip": "Delete Image", + "noImages": "No images found", + "noImagesSub": "Upload your first image to get started.", + "noMediaFound": "No media found", + "noMediaSub": "Upload your first image or video to get started.", + "itemsFound": "{{count}} items found", + "modalTitle": "Media Library", + "modalSubtitle": "Select or upload images and videos (.mp4, .webm) for your content.", + "toastUploaded": "✅ Uploaded: {{name}}", + "toastUploadFailed": "❌ Upload failed", + "toastUploadError": "❌ Upload error", + "toastPasting": "📋 Pasting image...", + "confirmDelete": "Are you sure you want to delete this image?", + "alertDeleteFailed": "Failed to delete image", + "alertDeleteError": "An error occurred while deleting", + "alertUrlCopied": "Image URL copied to clipboard!", + "alertUploadFailed": "Upload failed: {{error}}", + "alertUploadGeneric": "Failed to upload media file." } } diff --git a/apps/admin/src/locales/ja/translation.json b/apps/admin/src/locales/ja/translation.json index f60a088..5a88cfb 100644 --- a/apps/admin/src/locales/ja/translation.json +++ b/apps/admin/src/locales/ja/translation.json @@ -1198,6 +1198,110 @@ "thUnits": "数量", "thRevenueCol": "売上高", "thPercentTotal": "全体比 %" + }, + "pageBuilder": { + "title": "ページビルダー (CMS)", + "createPageBtn": "新規ページ作成", + "orderSavedSuccess": "ページのセクション順序を正常に保存しました。", + "orderSaveError": "レイアウト順序の保存中にエラーが発生しました。もう一度お試しください。", + "pageTitleLabel": "ページタイトル", + "pageTitlePlaceholder": "ページタイトルを入力...", + "pageUrlLabel": "ページ URL", + "copyUrlSuccess": "URLをコピーしました", + "copyUrlTooltip": "ページURLをコピー", + "publishToWebsite": "Webサイトに公開", + "badgePublished": "公開中", + "badgeDraft": "下書き", + "btnSaveSettings": "設定を保存", + "btnPreviewLive": "ライブプレビュー", + "btnDeletePage": "このページを削除", + "confirmDeletePage": "ページ \"{{title}}\" を削除してもよろしいですか?すべてのセクションブロックが完全に削除されます!", + "sectionsTitle": "ページセクション", + "sectionsSubtitle": "このページのコンテンツブロックを管理します。", + "btnReorderLayout": "並び替え", + "btnAddSection": "セクション追加", + "selectBlockLabel": "編集するブロックを選択", + "chooseBlockPlaceholder": "-- ブロックを選択 --", + "hiddenBadge": "(非表示)", + "editBlockTitle": "ブロック設定を編集", + "createBlockTitle": "新規ブロック作成", + "badgeType": "タイプ:", + "badgeBlockId": "ブロックID:", + "badgePage": "ページ:", + "badgePageId": "ページID:", + "blockTypeLabel": "ブロックタイプ", + "browseVisualGallery": "ビジュアルギャラリーを開く", + "btnSaveBlock": "ブロック設定を保存", + "btnCreateBlock": "ブロックを作成", + "btnCancel": "キャンセル", + "emptyBlockPrompt": "左側からブロックを選択してコンテンツを編集してください", + "reorderModalTitle": "レイアウトの並び替え", + "noSectionsInPage": "このページにはまだセクションがありません。", + "hiddenTag": "非表示", + "tooltipHideSection": "セクションを非表示", + "tooltipShowSection": "セクションを表示", + "tooltipEditBlock": "ブロック内容を編集", + "tooltipDeleteBlock": "ブロックを削除", + "confirmDeleteSection": "このセクションを削除してもよろしいですか?", + "btnSaveOrder": "順序を保存", + "btnClose": "閉じる", + "createPageModalTitle": "新規ページを作成", + "pageSlugField": "ページスラッグ (URLパス)", + "pageSlugPlaceholder": "例: about", + "slugHint": "小文字のスラッグ形式に自動変換されます (例: /pages/about-us)", + "btnCreating": "作成中...", + "btnCreateSubmit": "ページを作成", + "errCreatePage": "ページの作成に失敗しました", + "errSaveSettings": "ページ設定の保存に失敗しました", + "errDeletePage": "ページの削除に失敗しました", + "errSaveBlock": "保存に失敗しました: {{error}}" + }, + "pageBuilderGallery": { + "galleryTitle": "ブロックセクションの追加", + "gallerySubtitle": "ページレイアウトに挿入するビジュアルブロックコンポーネントを選択してください。", + "searchPlaceholder": "ブロックを検索...", + "categoryLabel": "カテゴリー: {{cat}}", + "btnSelectBlock": "このブロックを選択", + "noBlocksFound": "\"{{search}}\" に一致するブロックは見つかりませんでした。", + "categories": { + "all": "すべてのブロック", + "hero": "バナー & ヒーロー", + "products": "商品 & ショップ", + "editorial": "ストーリー & エディトリアル", + "articles": "記事コンポーネント", + "forms": "フォーム & お問い合わせ", + "custom": "商品 & カスタム" + } + }, + "mediaLibrary": { + "title": "メディアライブラリ", + "subtitle": "アップロードした画像とメディアファイルを管理します", + "btnUploading": "アップロード中...", + "btnUploadNew": "新しい画像をアップロード", + "btnUploadNewFile": "新しいファイルをアップロード", + "pasteHintPrefix": "貼り付け", + "pasteHint": "このページの任意の場所に画像を貼り付け(Ctrl+V / ⌘+V)して即時アップロード", + "loading": "メディアを読み込み中...", + "clickToCopy": "クリックしてURLをコピー", + "copyUrl": "URLをコピー", + "deleteTooltip": "画像を削除", + "noImages": "画像が見つかりません", + "noImagesSub": "最初の画像をアップロードして開始してください。", + "noMediaFound": "メディアが見つかりません", + "noMediaSub": "最初の画像または動画をアップロードして開始してください。", + "itemsFound": "{{count}} 件見つかりました", + "modalTitle": "メディアライブラリ", + "modalSubtitle": "コンテンツ用の画像や動画(.mp4、.webm)を選択またはアップロードします。", + "toastUploaded": "✅ アップロード完了: {{name}}", + "toastUploadFailed": "❌ アップロードに失敗しました", + "toastUploadError": "❌ アップロードエラーが発生しました", + "toastPasting": "📋 画像を貼り付け中...", + "confirmDelete": "この画像を削除してもよろしいですか?", + "alertDeleteFailed": "画像の削除に失敗しました", + "alertDeleteError": "削除中にエラーが発生しました", + "alertUrlCopied": "画像URLをクリップボードにコピーしました!", + "alertUploadFailed": "アップロードに失敗しました: {{error}}", + "alertUploadGeneric": "メディアファイルのアップロードに失敗しました。" } } diff --git a/apps/admin/src/locales/th/translation.json b/apps/admin/src/locales/th/translation.json index d821971..f547b13 100644 --- a/apps/admin/src/locales/th/translation.json +++ b/apps/admin/src/locales/th/translation.json @@ -1198,6 +1198,110 @@ "thUnits": "ชิ้น", "thRevenueCol": "ยอดรายได้", "thPercentTotal": "% รวม" + }, + "pageBuilder": { + "title": "ระบบสร้างหน้าเว็บ (Page Builder)", + "createPageBtn": "สร้างหน้าใหม่", + "orderSavedSuccess": "บันทึกลำดับ Section หน้าเว็บเรียบร้อยแล้ว", + "orderSaveError": "เกิดข้อผิดพลาดในการบันทึกลำดับ Layout กรุณาลองใหม่อีกครั้ง", + "pageTitleLabel": "ชื่อหน้า (Page Title)", + "pageTitlePlaceholder": "กรอกชื่อหน้า...", + "pageUrlLabel": "URL หน้าเว็บ", + "copyUrlSuccess": "คัดลอก URL เรียบร้อย", + "copyUrlTooltip": "คัดลอก URL ของหน้า", + "publishToWebsite": "เผยแพร่ขึ้นหน้าเว็บไซต์", + "badgePublished": "เผยแพร่แล้ว", + "badgeDraft": "ฉบับร่าง", + "btnSaveSettings": "บันทึกการตั้งค่า", + "btnPreviewLive": "เปิดดูหน้าจริง", + "btnDeletePage": "ลบหน้านี้", + "confirmDeletePage": "คุณแน่ใจหรือไม่ว่าต้องการลบหน้า \"{{title}}\"? การกระทำนี้จะลบบล็อกและเนื้อหาทั้งหมดอย่างถาวร!", + "sectionsTitle": "Sections ของหน้า", + "sectionsSubtitle": "จัดการบล็อกเนื้อหาทั้งหมดสำหรับหน้านี้", + "btnReorderLayout": "จัดเรียงลำดับบล็อก", + "btnAddSection": "เพิ่ม Section", + "selectBlockLabel": "เลือกบล็อกที่ต้องการแก้ไข", + "chooseBlockPlaceholder": "-- เลือกบล็อก --", + "hiddenBadge": "(ซ่อนอยู่)", + "editBlockTitle": "แก้ไขการตั้งค่าบล็อก", + "createBlockTitle": "สร้างบล็อกใหม่", + "badgeType": "ประเภท:", + "badgeBlockId": "Block ID:", + "badgePage": "หน้า:", + "badgePageId": "Page ID:", + "blockTypeLabel": "ประเภทบล็อก", + "browseVisualGallery": "เปิดดูแกลเลอรีบล็อก", + "btnSaveBlock": "บันทึกการตั้งค่าบล็อก", + "btnCreateBlock": "สร้างบล็อก", + "btnCancel": "ยกเลิก", + "emptyBlockPrompt": "เลือกบล็อกทางด้านซ้ายเพื่อแก้ไขเนื้อหา", + "reorderModalTitle": "จัดเรียงลำดับบล็อก", + "noSectionsInPage": "ยังไม่มี Section เนื้อหาในหน้านี้", + "hiddenTag": "ซ่อนอยู่", + "tooltipHideSection": "ซ่อน Section", + "tooltipShowSection": "แสดง Section", + "tooltipEditBlock": "แก้ไขเนื้อหาบล็อก", + "tooltipDeleteBlock": "ลบบล็อก", + "confirmDeleteSection": "คุณแน่ใจหรือไม่ว่าต้องการลบ Section นี้?", + "btnSaveOrder": "บันทึกลำดับ", + "btnClose": "ปิด", + "createPageModalTitle": "สร้างหน้าเว็บใหม่", + "pageSlugField": "Page Slug (เส้นทาง URL)", + "pageSlugPlaceholder": "เช่น about", + "slugHint": "ระบบจะแปลงเป็นตัวพิมพ์เล็กและคั่นด้วยขีด เช่น /pages/about-us", + "btnCreating": "กำลังสร้าง...", + "btnCreateSubmit": "สร้างหน้าใหม่", + "errCreatePage": "ไม่สามารถสร้างหน้าเว็บได้", + "errSaveSettings": "ไม่สามารถบันทึกการตั้งค่าหน้าได้", + "errDeletePage": "ไม่สามารถลบหน้าได้", + "errSaveBlock": "บันทึกไม่สำเร็จ: {{error}}" + }, + "pageBuilderGallery": { + "galleryTitle": "เพิ่มบล็อกเนื้อหา (Block Section)", + "gallerySubtitle": "เลือกคอมโพเนนต์บล็อกเพื่อแทรกลงในโครงสร้างหน้าเว็บของคุณ", + "searchPlaceholder": "ค้นหาประเภทบล็อก...", + "categoryLabel": "หมวดหมู่: {{cat}}", + "btnSelectBlock": "เลือกบล็อกนี้", + "noBlocksFound": "ไม่พบบล็อกที่ตรงกับ \"{{search}}\"", + "categories": { + "all": "ทุกบล็อก", + "hero": "แบนเนอร์ & Hero", + "products": "สินค้า & ร้านค้า", + "editorial": "เรื่องราว & บทความ", + "articles": "องค์ประกอบบทความ", + "forms": "ฟอร์ม & ข้อมูลติดต่อ", + "custom": "สินค้า & กำหนดเอง" + } + }, + "mediaLibrary": { + "title": "คลังสื่อมีเดีย", + "subtitle": "จัดการรูปภาพและไฟล์สื่อที่อัปโหลดของคุณ", + "btnUploading": "กำลังอัปโหลด...", + "btnUploadNew": "อัปโหลดรูปภาพใหม่", + "btnUploadNewFile": "อัปโหลดไฟล์ใหม่", + "pasteHintPrefix": "วางรูปภาพ (Paste)", + "pasteHint": "ที่ใดก็ได้ในหน้านี้ (Ctrl+V / ⌘+V) เพื่ออัปโหลดทันที", + "loading": "กำลังโหลดไฟล์สื่อ...", + "clickToCopy": "คลิกเพื่อคัดลอก URL", + "copyUrl": "คัดลอก URL", + "deleteTooltip": "ลบรูปภาพ", + "noImages": "ไม่พบรูปภาพ", + "noImagesSub": "อัปโหลดรูปภาพแรกของคุณเพื่อเริ่มต้นใช้งาน", + "noMediaFound": "ไม่พบไฟล์สื่อ", + "noMediaSub": "อัปโหลดรูปภาพหรือวิดีโอแรกของคุณเพื่อเริ่มต้นใช้งาน", + "itemsFound": "พบ {{count}} รายการ", + "modalTitle": "คลังสื่อมีเดีย", + "modalSubtitle": "เลือกหรืออัปโหลดรูปภาพและวิดีโอ (.mp4, .webm) สำหรับเนื้อหาของคุณ", + "toastUploaded": "✅ อัปโหลดสำเร็จ: {{name}}", + "toastUploadFailed": "❌ อัปโหลดไม่สำเร็จ", + "toastUploadError": "❌ เกิดข้อผิดพลาดในการอัปโหลด", + "toastPasting": "📋 กำลังวางรูปภาพ...", + "confirmDelete": "คุณแน่ใจหรือไม่ว่าต้องการลบรูปภาพนี้?", + "alertDeleteFailed": "ลบรูปภาพไม่สำเร็จ", + "alertDeleteError": "เกิดข้อผิดพลาดขณะลบรูปภาพ", + "alertUrlCopied": "คัดลอก URL รูปภาพลงคลิปบอร์ดแล้ว!", + "alertUploadFailed": "อัปโหลดไม่สำเร็จ: {{error}}", + "alertUploadGeneric": "ไม่สามารถอัปโหลดไฟล์สื่อได้" } } diff --git a/apps/admin/src/locales/zh/translation.json b/apps/admin/src/locales/zh/translation.json index eea7388..0a851e2 100644 --- a/apps/admin/src/locales/zh/translation.json +++ b/apps/admin/src/locales/zh/translation.json @@ -1199,6 +1199,110 @@ "thUnits": "销量(件)", "thRevenueCol": "销售额", "thPercentTotal": "占总销售额 %" + }, + "pageBuilder": { + "title": "页面构建器 (CMS)", + "createPageBtn": "创建新页面", + "orderSavedSuccess": "页面模块布局顺序保存成功。", + "orderSaveError": "保存布局顺序时出错,请重试。", + "pageTitleLabel": "页面标题", + "pageTitlePlaceholder": "输入页面标题...", + "pageUrlLabel": "页面 URL", + "copyUrlSuccess": "已复制 URL", + "copyUrlTooltip": "复制页面 URL", + "publishToWebsite": "发布到网站", + "badgePublished": "已发布", + "badgeDraft": "草稿", + "btnSaveSettings": "保存设置", + "btnPreviewLive": "实时预览", + "btnDeletePage": "删除此页面", + "confirmDeletePage": "确认删除页面 \"{{title}}\" 吗?此页面的所有内容块都将被永久删除!", + "sectionsTitle": "页面内容模块 (Sections)", + "sectionsSubtitle": "管理此页面的所有内容组件与模块。", + "btnReorderLayout": "调整模块顺序", + "btnAddSection": "添加 Section", + "selectBlockLabel": "选择要编辑的模块", + "chooseBlockPlaceholder": "-- 请选择模块 --", + "hiddenBadge": "(已隐藏)", + "editBlockTitle": "编辑模块设置", + "createBlockTitle": "创建新模块", + "badgeType": "类型:", + "badgeBlockId": "模块ID:", + "badgePage": "所属页面:", + "badgePageId": "页面ID:", + "blockTypeLabel": "模块类型", + "browseVisualGallery": "打开可视化画廊", + "btnSaveBlock": "保存模块设置", + "btnCreateBlock": "创建模块", + "btnCancel": "取消", + "emptyBlockPrompt": "请从左侧选择一个模块以编辑其内容", + "reorderModalTitle": "调整模块布局顺序", + "noSectionsInPage": "此页面暂无任何内容模块。", + "hiddenTag": "已隐藏", + "tooltipHideSection": "隐藏模块", + "tooltipShowSection": "显示模块", + "tooltipEditBlock": "编辑模块内容", + "tooltipDeleteBlock": "删除模块", + "confirmDeleteSection": "确认删除此模块吗?", + "btnSaveOrder": "保存顺序", + "btnClose": "关闭", + "createPageModalTitle": "创建新页面", + "pageSlugField": "页面 Slug (URL 路径)", + "pageSlugPlaceholder": "例如: about", + "slugHint": "将自动转换为小写 slug 格式 (例如: /pages/about-us)", + "btnCreating": "正在创建...", + "btnCreateSubmit": "创建页面", + "errCreatePage": "创建页面失败", + "errSaveSettings": "保存页面设置失败", + "errDeletePage": "删除页面失败", + "errSaveBlock": "保存失败: {{error}}" + }, + "pageBuilderGallery": { + "galleryTitle": "添加内容模块 (Block Section)", + "gallerySubtitle": "选择要插入页面布局的可视化组件模块。", + "searchPlaceholder": "搜索模块类型...", + "categoryLabel": "分类: {{cat}}", + "btnSelectBlock": "选择此模块", + "noBlocksFound": "未找到与 \"{{search}}\" 匹配的模块。", + "categories": { + "all": "全部模块", + "hero": "横幅 & 首屏 (Hero)", + "products": "商品 & 商店", + "editorial": "品牌故事 & 专栏", + "articles": "文章元素", + "forms": "表单 & 联系信息", + "custom": "商品 & 自定义" + } + }, + "mediaLibrary": { + "title": "媒体库", + "subtitle": "管理您上传的图片与媒体文件", + "btnUploading": "上传中...", + "btnUploadNew": "上传新图片", + "btnUploadNewFile": "上传新文件", + "pasteHintPrefix": "粘贴", + "pasteHint": "在此页面任意位置粘贴图片(Ctrl+V / ⌘+V)即可快速上传", + "loading": "正在加载媒体文件...", + "clickToCopy": "点击复制 URL", + "copyUrl": "复制 URL", + "deleteTooltip": "删除图片", + "noImages": "未找到图片", + "noImagesSub": "上传您的第一张图片以开始使用。", + "noMediaFound": "未找到媒体文件", + "noMediaSub": "上传您的第一张图片或视频以开始使用。", + "itemsFound": "共找到 {{count}} 项", + "modalTitle": "媒体库", + "modalSubtitle": "为您的内容选择或上传图片和视频(.mp4、.webm)。", + "toastUploaded": "✅ 上传成功:{{name}}", + "toastUploadFailed": "❌ 上传失败", + "toastUploadError": "❌ 上传发生错误", + "toastPasting": "📋 正在粘贴图片...", + "confirmDelete": "您确定要删除此图片吗?", + "alertDeleteFailed": "删除图片失败", + "alertDeleteError": "删除过程中发生错误", + "alertUrlCopied": "图片 URL 已复制到剪贴板!", + "alertUploadFailed": "上传失败:{{error}}", + "alertUploadGeneric": "上传媒体文件失败。" } } diff --git a/apps/admin/src/pages/MediaLibraryPage.tsx b/apps/admin/src/pages/MediaLibraryPage.tsx index 126d0d5..07f05b1 100644 --- a/apps/admin/src/pages/MediaLibraryPage.tsx +++ b/apps/admin/src/pages/MediaLibraryPage.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; import { API_BASE } from '@homaroma/types'; import { Upload, Loader2, Image as ImageIcon, Trash2, ClipboardPaste } from 'lucide-react'; @@ -9,6 +10,7 @@ interface MediaItem { } export default function MediaLibraryPage() { + const { t } = useTranslation(); const [media, setMedia] = useState([]); const [loading, setLoading] = useState(false); const [uploading, setUploading] = useState(false); @@ -52,17 +54,17 @@ export default function MediaLibraryPage() { }); if (res.ok) { await fetchMedia(); - showToast(`✅ Uploaded: ${file.name || 'image'}`); + showToast(t('mediaLibrary.toastUploaded', { name: file.name || 'image' })); } else { - showToast('❌ Upload failed'); + showToast(t('mediaLibrary.toastUploadFailed')); } } catch (error) { console.error('Failed to upload', error); - showToast('❌ Upload error'); + showToast(t('mediaLibrary.toastUploadError')); } finally { setUploading(false); } - }, []); + }, [t]); const handleUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; @@ -83,13 +85,13 @@ export default function MediaLibraryPage() { // Give the pasted file a sensible name const ext = item.type.split('/')[1] || 'png'; const namedFile = new File([file], `paste-${Date.now()}.${ext}`, { type: item.type }); - showToast('📋 Pasting image...'); + showToast(t('mediaLibrary.toastPasting')); await uploadFile(namedFile); break; } } }, - [uploading, uploadFile] + [uploading, uploadFile, t] ); useEffect(() => { @@ -105,7 +107,7 @@ export default function MediaLibraryPage() { e.stopPropagation(); if (!id) return; - if (!window.confirm('Are you sure you want to delete this image?')) return; + if (!window.confirm(t('mediaLibrary.confirmDelete'))) return; try { setDeletingId(id); @@ -115,11 +117,11 @@ export default function MediaLibraryPage() { if (res.ok) { await fetchMedia(); } else { - alert('Failed to delete image'); + alert(t('mediaLibrary.alertDeleteFailed')); } } catch (error) { console.error('Failed to delete', error); - alert('An error occurred while deleting'); + alert(t('mediaLibrary.alertDeleteError')); } finally { setDeletingId(null); } @@ -128,7 +130,7 @@ export default function MediaLibraryPage() { const copyToClipboard = (url: string, e: React.MouseEvent) => { e.stopPropagation(); navigator.clipboard.writeText(url); - alert('Image URL copied to clipboard!'); + alert(t('mediaLibrary.alertUrlCopied')); }; return ( @@ -160,16 +162,16 @@ export default function MediaLibraryPage() {

- Media Library + {t('mediaLibrary.title')}

-

Manage your uploaded images

+

{t('mediaLibrary.subtitle')}

@@ -193,7 +195,7 @@ export default function MediaLibraryPage() { > - Paste an image anywhere on this page (Ctrl+V / ⌘+V) to upload instantly + {t('mediaLibrary.pasteHintPrefix')} {t('mediaLibrary.pasteHint')} @@ -201,7 +203,7 @@ export default function MediaLibraryPage() { {loading ? (
-

Loading Media...

+

{t('mediaLibrary.loading')}

) : (
@@ -211,7 +213,7 @@ export default function MediaLibraryPage() { className="group relative border-2 border-white/5 bg-night-950 rounded-xl overflow-hidden hover:border-cyan-glow transition-all hover:shadow-[0_0_15px_rgba(34,211,238,0.2)] aspect-square flex flex-col" onClick={(e) => copyToClipboard(item.url, e)} > -
+
{item.filename {/* Hover Overlay */}
- Copy URL + {t('mediaLibrary.copyUrl')}
{item.filename && ( @@ -230,7 +232,7 @@ export default function MediaLibraryPage() { {item.id && (
diff --git a/apps/admin/src/pages/PageBuilderPage.tsx b/apps/admin/src/pages/PageBuilderPage.tsx index 7570775..2a7d891 100644 --- a/apps/admin/src/pages/PageBuilderPage.tsx +++ b/apps/admin/src/pages/PageBuilderPage.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { Layers, Plus, @@ -72,6 +73,7 @@ interface Brand { } export default function PageBuilderPage() { + const { t } = useTranslation(); const frontendBase = ( import.meta.env.DEV ? window.location.origin @@ -388,11 +390,11 @@ export default function PageBuilderPage() { await fetchPagesList(); setSelectedPageId(data.id); } else { - alert(data.error || 'Failed to create page'); + alert(data.error || t('pageBuilder.errCreatePage', 'Failed to create page')); } } catch (err) { console.error('Create page error:', err); - alert('Failed to create page due to network error'); + alert(t('pageBuilder.errCreatePage', 'Failed to create page')); } finally { setIsSavingPage(false); } @@ -418,11 +420,11 @@ export default function PageBuilderPage() { setPage((prev) => (prev ? { ...prev, isPublished: data.isPublished ?? prev.isPublished } : prev)); await fetchPagesList(); } else { - alert(data.error || 'Failed to save page settings'); + alert(data.error || t('pageBuilder.errSaveSettings', 'Failed to save page settings')); } } catch (err) { console.error('Save page settings error:', err); - alert('Failed to save page settings due to network error'); + alert(t('pageBuilder.errSaveSettings', 'Failed to save page settings')); } finally { setIsSavingPageSettings(false); } @@ -430,7 +432,7 @@ export default function PageBuilderPage() { const handleDeletePage = async () => { if (!page || page.slug === 'home') return; - if (!confirm(`Are you sure you want to delete the page "${page.title}"? This will delete all of its section blocks permanently!`)) return; + if (!confirm(t('pageBuilder.confirmDeletePage', { title: page.title, defaultValue: `Are you sure you want to delete the page "${page.title}"? This will delete all of its section blocks permanently!` }))) return; try { const res = await fetch(`${API_BASE}/pages/${page.id}`, { @@ -443,7 +445,7 @@ export default function PageBuilderPage() { setSections([]); await fetchPagesList(); } else { - alert('Failed to delete page'); + alert(t('pageBuilder.errDeletePage', 'Failed to delete page')); } } catch (err) { console.error('Delete page error:', err); @@ -518,7 +520,7 @@ export default function PageBuilderPage() { }; const handleDeleteSection = async (id: number) => { - if (!confirm('Are you sure you want to delete this section?')) return; + if (!confirm(t('pageBuilder.confirmDeleteSection', 'Are you sure you want to delete this section?'))) return; try { const res = await fetch(`${API_BASE}/pages/sections/${id}`, { method: 'DELETE' @@ -929,7 +931,7 @@ export default function PageBuilderPage() { if (selectedPageId) fetchPageDetails(selectedPageId); } else { const errData = await res.json(); - alert(`Failed to save: ${errData.error || 'Unknown error'}`); + alert(t('pageBuilder.errSaveBlock', { error: errData.error || 'Unknown error', defaultValue: `Failed to save: ${errData.error || 'Unknown error'}` })); } } catch (err) { console.error('Save section error:', err); @@ -941,7 +943,7 @@ export default function PageBuilderPage() { {/* Global Header */}
-

Page Builder (CMS)

+

{t('pageBuilder.title', 'Page Builder (CMS)')}

setPage({ ...page, title: e.target.value })} className="bg-slate-900/50 border border-white/10 text-white rounded-xl px-4 py-2 text-sm focus:outline-none focus:border-cyan-glow/50 transition-colors w-full" - placeholder="Enter page title..." + placeholder={t('pageBuilder.pageTitlePlaceholder', 'Enter page title...')} />
{isCopied ? : } @@ -1044,7 +1046,7 @@ export default function PageBuilderPage() { className="h-4 w-4 rounded border-white/20 bg-slate-900 text-cyan-glow focus:ring-cyan-glow focus:ring-offset-slate-900" /> - Publish to Website + {t('pageBuilder.publishToWebsite', 'Publish to Website')} - {page.isPublished ? 'Published' : 'Draft'} + {page.isPublished ? t('pageBuilder.badgePublished', 'Published') : t('pageBuilder.badgeDraft', 'Draft')}
@@ -1069,7 +1071,7 @@ export default function PageBuilderPage() { ) : ( )} - Save Settings + {t('pageBuilder.btnSaveSettings', 'Save Settings')} - Preview Live + {t('pageBuilder.btnPreviewLive', 'Preview Live')}
{page.slug !== 'home' && ( @@ -1087,7 +1089,7 @@ export default function PageBuilderPage() { onClick={handleDeletePage} className="text-xs font-bold text-rose-500 hover:text-rose-400 hover:underline uppercase tracking-wider transition-all mt-2 w-fit mx-auto" > - Delete this page + {t('pageBuilder.btnDeletePage', 'Delete this page')} )}
@@ -1097,8 +1099,8 @@ export default function PageBuilderPage() { {/* Section Management Header */}
-

Page Sections

-

Manage the content blocks for this page.

+

{t('pageBuilder.sectionsTitle', 'Page Sections')}

+

{t('pageBuilder.sectionsSubtitle', 'Manage the content blocks for this page.')}

@@ -1121,7 +1123,7 @@ export default function PageBuilderPage() { {/* Block Selection Dropdown */}
- {editingSection.id ? 'Save Block Settings' : 'Create Block'} + {editingSection.id ? t('pageBuilder.btnSaveBlock', 'Save Block Settings') : t('pageBuilder.btnCreateBlock', 'Create Block')}
@@ -1645,7 +1647,7 @@ export default function PageBuilderPage() { ) : (
-

Select a block from the left to edit its content

+

{t('pageBuilder.emptyBlockPrompt', 'Select a block from the left to edit its content')}

)}
@@ -1659,7 +1661,7 @@ export default function PageBuilderPage() {

- Reorder Layout + {t('pageBuilder.reorderModalTitle', 'Reorder Layout')}

@@ -1745,14 +1747,14 @@ export default function PageBuilderPage() { openEditModal(section); }} className="p-1.5 bg-white/5 border border-white/5 rounded-lg text-slate-300 hover:bg-white/10 hover:text-white transition-all" - title="Edit Block Content" + title={t('pageBuilder.tooltipEditBlock', 'Edit Block Content')} > @@ -1774,7 +1776,7 @@ export default function PageBuilderPage() { ) : ( )} - Save Order + {t('pageBuilder.btnSaveOrder', 'Save Order')} )}
@@ -1796,7 +1798,7 @@ export default function PageBuilderPage() {

- Create New Page + {t('pageBuilder.createPageModalTitle', 'Create New Page')}