aboutsummaryrefslogtreecommitdiffstats
path: root/apps/web
diff options
context:
space:
mode:
authorAhmad Mujahid <55625580+AhmadMuj@users.noreply.github.com>2025-02-17 13:25:16 +0400
committerGitHub <noreply@github.com>2025-02-17 09:25:16 +0000
commite5cb9aa848009ea22c1385e4d33b7edf372979fb (patch)
tree89470d8da8aab10f30bbfccea8d1b0cea08a1408 /apps/web
parenta14be108736133535e2828b6bbdc8d0a69accd63 (diff)
downloadkarakeep-e5cb9aa848009ea22c1385e4d33b7edf372979fb.tar.zst
feat: Add PDF screenshot generation and display (#995)
* Updated pdf2json to 3.1.5 * Extract and store a screenshot from PDF files using pdf2pic * Installing graphicsmagick and ghostscript * Generate Missing PDF screenshot with tidyAssets worker for backward support * Display PDF screenshot instead of the PDF in web if it exists. * Display PDF screenshot in mobile app if exists. * Updated pnpm-lock.yaml * Removed console.log * Revert the unnecessary changes in package.json * Revert pnpm-lock changes * Prevent rendering PDF files if the screenshot is not generated * refactor: replace useEffect with useMemo for section initialization * feat: show PDF file download button and handle large PDFs by defaulting to screenshot view * feat: add file size to openapi spec * feature: Add Assets preprocessing in fix mode to admin actions * i18n: add reprocess_assets_fix_mode translation * i18n: Add missing ar translations * A bunch of fixes * Fix openspec schema --------- Co-authored-by: Mohamed Bassem <me@mbassem.com>
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/components/admin/AdminActions.tsx22
-rw-r--r--apps/web/components/dashboard/bookmarks/AssetCard.tsx28
-rw-r--r--apps/web/components/dashboard/preview/AssetContentSection.tsx131
-rw-r--r--apps/web/components/dashboard/preview/AttachmentBox.tsx1
-rw-r--r--apps/web/lib/i18n/locales/ar/translation.json33
-rw-r--r--apps/web/lib/i18n/locales/da/translation.json3
-rw-r--r--apps/web/lib/i18n/locales/de/translation.json3
-rw-r--r--apps/web/lib/i18n/locales/en/translation.json3
-rw-r--r--apps/web/lib/i18n/locales/es/translation.json3
-rw-r--r--apps/web/lib/i18n/locales/fr/translation.json3
-rw-r--r--apps/web/lib/i18n/locales/gl/translation.json3
-rw-r--r--apps/web/lib/i18n/locales/hr/translation.json3
-rw-r--r--apps/web/lib/i18n/locales/hu/translation.json3
-rw-r--r--apps/web/lib/i18n/locales/it/translation.json3
-rw-r--r--apps/web/lib/i18n/locales/pl/translation.json3
-rw-r--r--apps/web/lib/i18n/locales/ru/translation.json3
-rw-r--r--apps/web/lib/i18n/locales/tr/translation.json3
-rw-r--r--apps/web/lib/i18n/locales/zh/translation.json3
-rw-r--r--apps/web/lib/i18n/locales/zhtw/translation.json3
19 files changed, 204 insertions, 53 deletions
diff --git a/apps/web/components/admin/AdminActions.tsx b/apps/web/components/admin/AdminActions.tsx
index 34b3d63a..fb151ac8 100644
--- a/apps/web/components/admin/AdminActions.tsx
+++ b/apps/web/components/admin/AdminActions.tsx
@@ -37,6 +37,21 @@ export default function AdminActions() {
},
});
+ const { mutate: reprocessAssetsFixMode, isPending: isReprocessingPending } =
+ api.admin.reprocessAssetsFixMode.useMutation({
+ onSuccess: () => {
+ toast({
+ description: "Reprocessing enqueued",
+ });
+ },
+ onError: (e) => {
+ toast({
+ variant: "destructive",
+ description: e.message,
+ });
+ },
+ });
+
const {
mutate: reRunInferenceOnAllBookmarks,
isPending: isInferencePending,
@@ -126,6 +141,13 @@ export default function AdminActions() {
</ActionButton>
<ActionButton
variant="destructive"
+ loading={isReprocessingPending}
+ onClick={() => reprocessAssetsFixMode()}
+ >
+ {t("admin.actions.reprocess_assets_fix_mode")}
+ </ActionButton>
+ <ActionButton
+ variant="destructive"
loading={isTidyAssetsPending}
onClick={() => tidyAssets()}
>
diff --git a/apps/web/components/dashboard/bookmarks/AssetCard.tsx b/apps/web/components/dashboard/bookmarks/AssetCard.tsx
index 61b3bc8d..0cb75b3f 100644
--- a/apps/web/components/dashboard/bookmarks/AssetCard.tsx
+++ b/apps/web/components/dashboard/bookmarks/AssetCard.tsx
@@ -2,6 +2,8 @@
import Image from "next/image";
import Link from "next/link";
+import { cn } from "@/lib/utils";
+import { FileText } from "lucide-react";
import type { ZBookmarkTypeAsset } from "@hoarder/shared/types/bookmarks";
import { getAssetUrl } from "@hoarder/shared-react/utils/assetUtils";
@@ -32,12 +34,28 @@ function AssetImage({
);
}
case "pdf": {
+ const screenshotAssetId = bookmark.assets.find(
+ (r) => r.assetType === "assetScreenshot",
+ )?.id;
+ if (!screenshotAssetId) {
+ return (
+ <div
+ className={cn(className, "flex items-center justify-center")}
+ title="PDF screenshot not available. Run asset preprocessing job to generate one screenshot"
+ >
+ <FileText size={80} />
+ </div>
+ );
+ }
return (
- <iframe
- title={bookmarkedAsset.assetId}
- className={className}
- src={getAssetUrl(bookmarkedAsset.assetId)}
- />
+ <Link href={`/dashboard/preview/${bookmark.id}`}>
+ <Image
+ alt="asset"
+ src={getAssetUrl(screenshotAssetId)}
+ fill={true}
+ className={className}
+ />
+ </Link>
);
}
default: {
diff --git a/apps/web/components/dashboard/preview/AssetContentSection.tsx b/apps/web/components/dashboard/preview/AssetContentSection.tsx
index 03ab8a43..8590d2ad 100644
--- a/apps/web/components/dashboard/preview/AssetContentSection.tsx
+++ b/apps/web/components/dashboard/preview/AssetContentSection.tsx
@@ -1,42 +1,117 @@
+import { useMemo, useState } from "react";
import Image from "next/image";
import Link from "next/link";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { useTranslation } from "@/lib/i18n/client";
+import { getAssetUrl } from "@hoarder/shared-react/utils/assetUtils";
import { BookmarkTypes, ZBookmark } from "@hoarder/shared/types/bookmarks";
-export function AssetContentSection({ bookmark }: { bookmark: ZBookmark }) {
+// 20 MB
+const BIG_FILE_SIZE = 20 * 1024 * 1024;
+
+function PDFContentSection({ bookmark }: { bookmark: ZBookmark }) {
if (bookmark.content.type != BookmarkTypes.ASSET) {
throw new Error("Invalid content type");
}
+ const { t } = useTranslation();
- switch (bookmark.content.assetType) {
- case "image": {
- return (
- <div className="relative h-full min-w-full">
- <Link
- href={`/api/assets/${bookmark.content.assetId}`}
- target="_blank"
- >
- <Image
- alt="asset"
- fill={true}
- className="object-contain"
- src={`/api/assets/${bookmark.content.assetId}`}
- />
- </Link>
- </div>
- );
+ const initialSection = useMemo(() => {
+ if (bookmark.content.type != BookmarkTypes.ASSET) {
+ throw new Error("Invalid content type");
}
- case "pdf": {
- return (
- <iframe
- title={bookmark.content.assetId}
- className="h-full w-full"
- src={`/api/assets/${bookmark.content.assetId}`}
- />
- );
+
+ const screenshot = bookmark.assets.find(
+ (item) => item.assetType === "assetScreenshot",
+ );
+ const bigSize =
+ bookmark.content.size && bookmark.content.size > BIG_FILE_SIZE;
+ if (bigSize && screenshot) {
+ return "screenshot";
}
- default: {
+ return "pdf";
+ }, [bookmark]);
+ const [section, setSection] = useState(initialSection);
+
+ const screenshot = bookmark.assets.find(
+ (r) => r.assetType === "assetScreenshot",
+ )?.id;
+
+ const content =
+ section === "screenshot" && screenshot ? (
+ <div className="relative h-full min-w-full">
+ <Image
+ alt="screenshot"
+ src={getAssetUrl(screenshot)}
+ fill={true}
+ className="object-contain"
+ />
+ </div>
+ ) : (
+ <iframe
+ title={bookmark.content.assetId}
+ className="h-full w-full"
+ src={getAssetUrl(bookmark.content.assetId)}
+ />
+ );
+
+ return (
+ <div className="flex h-full flex-col items-center gap-2">
+ <div className="flex w-full items-center justify-center gap-4">
+ <Select onValueChange={setSection} value={section}>
+ <SelectTrigger className="w-fit">
+ <SelectValue />
+ </SelectTrigger>
+ <SelectContent>
+ <SelectGroup>
+ <SelectItem value="screenshot" disabled={!screenshot}>
+ {t("common.screenshot")}
+ </SelectItem>
+ <SelectItem value="pdf">PDF</SelectItem>
+ </SelectGroup>
+ </SelectContent>
+ </Select>
+ </div>
+ {content}
+ </div>
+ );
+}
+
+function ImageContentSection({ bookmark }: { bookmark: ZBookmark }) {
+ if (bookmark.content.type != BookmarkTypes.ASSET) {
+ throw new Error("Invalid content type");
+ }
+ return (
+ <div className="relative h-full min-w-full">
+ <Link href={getAssetUrl(bookmark.content.assetId)} target="_blank">
+ <Image
+ alt="asset"
+ fill={true}
+ className="object-contain"
+ src={getAssetUrl(bookmark.content.assetId)}
+ />
+ </Link>
+ </div>
+ );
+}
+
+export function AssetContentSection({ bookmark }: { bookmark: ZBookmark }) {
+ if (bookmark.content.type != BookmarkTypes.ASSET) {
+ throw new Error("Invalid content type");
+ }
+ switch (bookmark.content.assetType) {
+ case "image":
+ return <ImageContentSection bookmark={bookmark} />;
+ case "pdf":
+ return <PDFContentSection bookmark={bookmark} />;
+ default:
return <div>Unsupported asset type</div>;
- }
}
}
diff --git a/apps/web/components/dashboard/preview/AttachmentBox.tsx b/apps/web/components/dashboard/preview/AttachmentBox.tsx
index 6547ae51..32939cb0 100644
--- a/apps/web/components/dashboard/preview/AttachmentBox.tsx
+++ b/apps/web/components/dashboard/preview/AttachmentBox.tsx
@@ -45,6 +45,7 @@ export default function AttachmentBox({ bookmark }: { bookmark: ZBookmark }) {
const { t } = useTranslation();
const typeToIcon: Record<ZAssetType, React.ReactNode> = {
screenshot: <Camera className="size-4" />,
+ assetScreenshot: <Camera className="size-4" />,
fullPageArchive: <Archive className="size-4" />,
precrawledArchive: <Archive className="size-4" />,
bannerImage: <Image className="size-4" />,
diff --git a/apps/web/lib/i18n/locales/ar/translation.json b/apps/web/lib/i18n/locales/ar/translation.json
index 7bd0bcad..e9239e70 100644
--- a/apps/web/lib/i18n/locales/ar/translation.json
+++ b/apps/web/lib/i18n/locales/ar/translation.json
@@ -153,7 +153,7 @@
}
},
"admin": {
- "admin_settings": "إعدادات المدير",
+ "admin_settings": "إعدادات المشرف",
"server_stats": {
"server_stats": "إحصائيات الخادم",
"total_users": "إجمالي المستخدمين",
@@ -161,15 +161,36 @@
"server_version": "إصدار الخادم"
},
"background_jobs": {
- "background_jobs": "المهام التلقائية",
+ "background_jobs": "المهام الخلفية",
"crawler_jobs": "مهام الاستكشاف",
"indexing_jobs": "مهام الفهرسة",
- "inference_jobs": "مهام التحليل الذكي",
- "tidy_assets_jobs": "مهام تنظيم الملفات",
+ "inference_jobs": "مهام الاستدلال",
+ "tidy_assets_jobs": "مهام تنظيم الوسائط",
"job": "مهمة",
"queued": "في قائمة الانتظار",
- "pending": "معلق",
- "failed": "فشل"
+ "pending": "قيد الانتظار",
+ "failed": "فشلت"
+ },
+ "actions": {
+ "recrawl_failed_links_only": "إعادة استكشاف الروابط الفاشلة فقط",
+ "recrawl_all_links": "إعادة استكشاف جميع الروابط",
+ "without_inference": "بدون استدلال",
+ "regenerate_ai_tags_for_failed_bookmarks_only": "إعادة إنشاء علامات الذكاء الاصطناعي للإشارات المرجعية الفاشلة فقط",
+ "regenerate_ai_tags_for_all_bookmarks": "إعادة إنشاء علامات الذكاء الاصطناعي لجميع الإشارات المرجعية",
+ "reindex_all_bookmarks": "إعادة فهرسة جميع الإشارات المرجعية",
+ "compact_assets": "ضغط الوسائط",
+ "reprocess_assets_fix_mode": "إعادة معالجة الوسائط (وضع الإصلاح)"
+ },
+ "users_list": {
+ "users_list": "قائمة المستخدمين",
+ "create_user": "إنشاء مستخدم",
+ "change_role": "تغيير الدور",
+ "reset_password": "إعادة تعيين كلمة المرور",
+ "delete_user": "حذف المستخدم",
+ "num_bookmarks": "عدد الإشارات المرجعية",
+ "asset_sizes": "أحجام الوسائط",
+ "local_user": "مستخدم محلي",
+ "confirm_password": "تأكيد كلمة المرور"
}
},
"options": {
diff --git a/apps/web/lib/i18n/locales/da/translation.json b/apps/web/lib/i18n/locales/da/translation.json
index 3822d5c6..4fe69650 100644
--- a/apps/web/lib/i18n/locales/da/translation.json
+++ b/apps/web/lib/i18n/locales/da/translation.json
@@ -94,7 +94,8 @@
"recrawl_all_links": "Gennemsøg alle links",
"without_inference": "Uden inferens",
"regenerate_ai_tags_for_all_bookmarks": "Genopret AI-tags for alle bogmærker",
- "reindex_all_bookmarks": "Genindeksér alle bogmærker"
+ "reindex_all_bookmarks": "Genindeksér alle bogmærker",
+ "reprocess_assets_fix_mode": "Genbehandling af aktiver (Fix Mode)"
},
"background_jobs": {
"inference_jobs": "Inferensopgaver",
diff --git a/apps/web/lib/i18n/locales/de/translation.json b/apps/web/lib/i18n/locales/de/translation.json
index c20a2273..ccebf1f1 100644
--- a/apps/web/lib/i18n/locales/de/translation.json
+++ b/apps/web/lib/i18n/locales/de/translation.json
@@ -175,7 +175,8 @@
"regenerate_ai_tags_for_failed_bookmarks_only": "KI-Tags nur für fehlgeschlagene Lesezeichen neu generieren",
"regenerate_ai_tags_for_all_bookmarks": "KI-Tags für alle Lesezeichen neu generieren",
"reindex_all_bookmarks": "Alle Lesezeichen neu indizieren",
- "compact_assets": "Assets komprimieren"
+ "compact_assets": "Assets komprimieren",
+ "reprocess_assets_fix_mode": "Assets neu verarbeiten (Fix-Modus)"
},
"users_list": {
"users_list": "Benutzerliste",
diff --git a/apps/web/lib/i18n/locales/en/translation.json b/apps/web/lib/i18n/locales/en/translation.json
index 2e80f2f4..81ef942f 100644
--- a/apps/web/lib/i18n/locales/en/translation.json
+++ b/apps/web/lib/i18n/locales/en/translation.json
@@ -178,7 +178,8 @@
"regenerate_ai_tags_for_failed_bookmarks_only": "Regenerate AI Tags for Failed Bookmarks Only",
"regenerate_ai_tags_for_all_bookmarks": "Regenerate AI Tags for All Bookmarks",
"reindex_all_bookmarks": "Reindex All Bookmarks",
- "compact_assets": "Compact Assets"
+ "compact_assets": "Compact Assets",
+ "reprocess_assets_fix_mode": "Reprocess Assets (Fix Mode)"
},
"users_list": {
"users_list": "Users List",
diff --git a/apps/web/lib/i18n/locales/es/translation.json b/apps/web/lib/i18n/locales/es/translation.json
index 40c6cb01..3a1a7e3c 100644
--- a/apps/web/lib/i18n/locales/es/translation.json
+++ b/apps/web/lib/i18n/locales/es/translation.json
@@ -146,7 +146,8 @@
"compact_assets": "Optimizar multimedia",
"without_inference": "Sin inferencia",
"recrawl_failed_links_only": "Recrawlear solo los enlaces fallidos",
- "recrawl_all_links": "Recrawlear todos los enlaces"
+ "recrawl_all_links": "Recrawlear todos los enlaces",
+ "reprocess_assets_fix_mode": "Reprocesar assets (modo fijo)"
},
"users_list": {
"users_list": "Lista de usuarios",
diff --git a/apps/web/lib/i18n/locales/fr/translation.json b/apps/web/lib/i18n/locales/fr/translation.json
index b7834a7b..1772c2ff 100644
--- a/apps/web/lib/i18n/locales/fr/translation.json
+++ b/apps/web/lib/i18n/locales/fr/translation.json
@@ -146,7 +146,8 @@
"regenerate_ai_tags_for_failed_bookmarks_only": "Régénérer les tags AI uniquement pour les favoris échoués",
"regenerate_ai_tags_for_all_bookmarks": "Régénérer les tags AI pour tous les favoris",
"reindex_all_bookmarks": "Réindexer tous les favoris",
- "compact_assets": "Compacter les assets"
+ "compact_assets": "Compacter les assets",
+ "reprocess_assets_fix_mode": "Reprocesser les assets (mode fix)"
},
"users_list": {
"users_list": "Liste des utilisateurs",
diff --git a/apps/web/lib/i18n/locales/gl/translation.json b/apps/web/lib/i18n/locales/gl/translation.json
index eb65ca64..363ffac8 100644
--- a/apps/web/lib/i18n/locales/gl/translation.json
+++ b/apps/web/lib/i18n/locales/gl/translation.json
@@ -178,7 +178,8 @@
"regenerate_ai_tags_for_failed_bookmarks_only": "Rexenerar etiquetas IA so en marcadores errados",
"regenerate_ai_tags_for_all_bookmarks": "Rexenerar etiquetas IA para todos os marcadores",
"reindex_all_bookmarks": "Reindexar marcadores",
- "compact_assets": "Optimizar multimedia"
+ "compact_assets": "Optimizar multimedia",
+ "reprocess_assets_fix_mode": "Reprocesar assets (modo fixo)"
},
"users_list": {
"users_list": "Listado de usuarios",
diff --git a/apps/web/lib/i18n/locales/hr/translation.json b/apps/web/lib/i18n/locales/hr/translation.json
index 6e250924..7a72d295 100644
--- a/apps/web/lib/i18n/locales/hr/translation.json
+++ b/apps/web/lib/i18n/locales/hr/translation.json
@@ -36,7 +36,8 @@
"recrawl_all_links": "Ponovno pregledavanje svih veza",
"regenerate_ai_tags_for_all_bookmarks": "Ponovno generiranje AI oznaka za sve oznake",
"without_inference": "Bez zaključivanja",
- "compact_assets": "Kompaktiranje resursa"
+ "compact_assets": "Kompaktiranje resursa",
+ "reprocess_assets_fix_mode": "Ponovno postupanje s resursima (fiksni mod)"
}
},
"layouts": {
diff --git a/apps/web/lib/i18n/locales/hu/translation.json b/apps/web/lib/i18n/locales/hu/translation.json
index 38ef96b4..439212f4 100644
--- a/apps/web/lib/i18n/locales/hu/translation.json
+++ b/apps/web/lib/i18n/locales/hu/translation.json
@@ -258,7 +258,8 @@
"regenerate_ai_tags_for_all_bookmarks": "Minden könyvjelző MI címkéjének lecserélése",
"regenerate_ai_tags_for_failed_bookmarks_only": "Hibás könyvjelzők MI címkéjének lecserélése",
"reindex_all_bookmarks": "Minden könyvjelző újraindexelése",
- "compact_assets": "Kompakt tulajdonok"
+ "compact_assets": "Kompakt tulajdonok",
+ "reprocess_assets_fix_mode": "Tulajdonok függvényezése (Fix Mod)"
},
"users_list": {
"asset_sizes": "Tulajdon méretek",
diff --git a/apps/web/lib/i18n/locales/it/translation.json b/apps/web/lib/i18n/locales/it/translation.json
index e24b6b7f..4b093b72 100644
--- a/apps/web/lib/i18n/locales/it/translation.json
+++ b/apps/web/lib/i18n/locales/it/translation.json
@@ -201,7 +201,8 @@
"regenerate_ai_tags_for_failed_bookmarks_only": "Rigenera tag AI solo per i segnalibri falliti",
"regenerate_ai_tags_for_all_bookmarks": "Rigenera tag AI per tutti i segnalibri",
"compact_assets": "Compatta asset",
- "reindex_all_bookmarks": "Reindicizza tutti i segnalibri"
+ "reindex_all_bookmarks": "Reindicizza tutti i segnalibri",
+ "reprocess_assets_fix_mode": "Riprocessa asset (modalità fissa)"
},
"users_list": {
"users_list": "Lista utenti",
diff --git a/apps/web/lib/i18n/locales/pl/translation.json b/apps/web/lib/i18n/locales/pl/translation.json
index 0d026542..66921560 100644
--- a/apps/web/lib/i18n/locales/pl/translation.json
+++ b/apps/web/lib/i18n/locales/pl/translation.json
@@ -148,7 +148,8 @@
"regenerate_ai_tags_for_failed_bookmarks_only": "Regeneruj tagi AI tylko dla nieudanych zakładek",
"regenerate_ai_tags_for_all_bookmarks": "Regeneruj tagi AI dla wszystkich zakładek",
"reindex_all_bookmarks": "Ponowne indeksowanie wszystkich zakładek",
- "compact_assets": "Kompaktuj zasoby"
+ "compact_assets": "Kompaktuj zasoby",
+ "reprocess_assets_fix_mode": "Ponowne przetwarzanie zasobów (tryb fiksny)"
}
},
"tags": {
diff --git a/apps/web/lib/i18n/locales/ru/translation.json b/apps/web/lib/i18n/locales/ru/translation.json
index 4a8cdd52..1d4c50bd 100644
--- a/apps/web/lib/i18n/locales/ru/translation.json
+++ b/apps/web/lib/i18n/locales/ru/translation.json
@@ -211,7 +211,8 @@
"compact_assets": "Сжать ресурсы",
"regenerate_ai_tags_for_failed_bookmarks_only": "Перегенерировать ИИ метки только для неудачных закладок",
"reindex_all_bookmarks": "Переиндексировать все закладки",
- "recrawl_all_links": "Пересканировать все ссылки"
+ "recrawl_all_links": "Пересканировать все ссылки",
+ "reprocess_assets_fix_mode": "Перепроцессировать ресурсы (фиксный режим)"
},
"admin_settings": "Настройки администратора"
},
diff --git a/apps/web/lib/i18n/locales/tr/translation.json b/apps/web/lib/i18n/locales/tr/translation.json
index 9840c6f0..227f6dac 100644
--- a/apps/web/lib/i18n/locales/tr/translation.json
+++ b/apps/web/lib/i18n/locales/tr/translation.json
@@ -148,7 +148,8 @@
"regenerate_ai_tags_for_failed_bookmarks_only": "Yalnızca Başarısız Yer İşaretleri için Yapay Zeka Etiketlerini Yeniden Oluştur",
"regenerate_ai_tags_for_all_bookmarks": "Tüm Yer İşaretleri için Yapay Zeka Etiketlerini Yeniden Oluştur",
"reindex_all_bookmarks": "Tüm Yer İşaretlerini Yeniden Dizine Al",
- "compact_assets": "Varlıkları Sıkıştır"
+ "compact_assets": "Varlıkları Sıkıştır",
+ "reprocess_assets_fix_mode": "Varlıkları Yeniden İşle (Fix Mod)"
},
"users_list": {
"users_list": "Kullanıcı Listesi",
diff --git a/apps/web/lib/i18n/locales/zh/translation.json b/apps/web/lib/i18n/locales/zh/translation.json
index 84a9e17a..d798b716 100644
--- a/apps/web/lib/i18n/locales/zh/translation.json
+++ b/apps/web/lib/i18n/locales/zh/translation.json
@@ -175,7 +175,8 @@
"regenerate_ai_tags_for_failed_bookmarks_only": "仅为失败书签重新生成AI标签",
"regenerate_ai_tags_for_all_bookmarks": "为所有书签重新生成AI标签",
"reindex_all_bookmarks": "重新索引所有书签",
- "compact_assets": "压缩资产"
+ "compact_assets": "压缩资产",
+ "reprocess_assets_fix_mode": "重新处理资产(固定模式)"
},
"users_list": {
"users_list": "用户列表",
diff --git a/apps/web/lib/i18n/locales/zhtw/translation.json b/apps/web/lib/i18n/locales/zhtw/translation.json
index aada5492..284b5de2 100644
--- a/apps/web/lib/i18n/locales/zhtw/translation.json
+++ b/apps/web/lib/i18n/locales/zhtw/translation.json
@@ -156,7 +156,8 @@
"regenerate_ai_tags_for_failed_bookmarks_only": "僅重新產生失敗書籤的 AI 標籤",
"regenerate_ai_tags_for_all_bookmarks": "重新產生所有書籤的 AI 標籤",
"reindex_all_bookmarks": "重新索引所有書籤",
- "compact_assets": "壓縮資源"
+ "compact_assets": "壓縮資源",
+ "reprocess_assets_fix_mode": "重新處理資源(固定模式)"
},
"users_list": {
"users_list": "使用者清單",