@jant/core 0.6.8 → 0.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/commands/uploads/cleanup.js +1 -0
- package/dist/{app-9P4rVCe2.js → app-C-jxWmAV.js} +12324 -12157
- package/dist/app-DqHzOwL5.js +6 -0
- package/dist/client/.vite/manifest.json +3 -3
- package/dist/client/_assets/{client-C6peCkkD.css → client-CGf2m3qp.css} +1 -1
- package/dist/client/_assets/{client-CXnEhyyv.js → client-DWy1LEEk.js} +1 -1
- package/dist/client/_assets/{client-auth-CSItbyU8.js → client-auth-Blg-a5Ep.js} +180 -162
- package/dist/{export-Be082J0n.js → export-C2DIB7mm.js} +2 -2
- package/dist/{github-sync-_kPWM4m9.js → github-sync-7XQ5ZM6z.js} +2 -2
- package/dist/{github-sync-D1Cw8mOY.js → github-sync-BEFCfLKK.js} +1 -1
- package/dist/index.js +3 -3
- package/dist/node.js +4 -4
- package/package.json +1 -1
- package/src/client/components/__tests__/jant-settings-avatar.test.ts +5 -2
- package/src/client/components/__tests__/jant-settings-general.test.ts +55 -8
- package/src/client/components/jant-compose-dialog.ts +12 -0
- package/src/client/components/jant-settings-general.ts +56 -18
- package/src/client/components/settings-types.ts +11 -0
- package/src/client/settings-bridge.ts +3 -0
- package/src/client/tiptap/__tests__/mark-exit.test.ts +99 -0
- package/src/client/tiptap/bubble-menu.ts +37 -4
- package/src/db/migrations/0026_absent_rhodey.sql +14 -0
- package/src/db/migrations/meta/0026_snapshot.json +2511 -0
- package/src/db/migrations/meta/_journal.json +7 -0
- package/src/db/migrations/pg/0024_high_violations.sql +14 -0
- package/src/db/migrations/pg/meta/0024_snapshot.json +3204 -0
- package/src/db/migrations/pg/meta/_journal.json +7 -0
- package/src/db/pg/schema.ts +36 -0
- package/src/db/schema.ts +36 -0
- package/src/i18n/__tests__/middleware.test.ts +46 -0
- package/src/i18n/locales/settings/en.po +25 -10
- package/src/i18n/locales/settings/en.ts +1 -1
- package/src/i18n/locales/settings/zh-Hans.po +25 -10
- package/src/i18n/locales/settings/zh-Hans.ts +1 -1
- package/src/i18n/locales/settings/zh-Hant.po +25 -10
- package/src/i18n/locales/settings/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +17 -8
- package/src/i18n/supported-locales.ts +5 -4
- package/src/lib/ids.ts +1 -0
- package/src/lib/resolve-config.ts +1 -0
- package/src/lib/upload.ts +14 -0
- package/src/routes/api/__tests__/settings.test.ts +1 -4
- package/src/routes/api/__tests__/upload.test.ts +2 -0
- package/src/routes/api/internal/__tests__/uploads.test.ts +19 -1
- package/src/routes/api/settings.ts +2 -1
- package/src/routes/auth/__tests__/setup.test.ts +14 -0
- package/src/routes/dash/__tests__/settings-avatar.test.ts +35 -17
- package/src/routes/dash/settings.tsx +15 -2
- package/src/services/__tests__/media.test.ts +191 -30
- package/src/services/__tests__/settings.test.ts +55 -0
- package/src/services/bootstrap.ts +7 -0
- package/src/services/export-theme/layouts/_default/baseof.html +2 -1
- package/src/services/media.ts +169 -42
- package/src/services/settings.ts +49 -15
- package/src/services/upload-session.ts +13 -3
- package/src/styles/tokens.css +6 -4
- package/src/types/bindings.ts +1 -0
- package/src/types/config.ts +13 -0
- package/src/ui/dash/settings/GeneralContent.tsx +38 -4
- package/src/ui/layouts/BaseLayout.tsx +1 -0
- package/src/ui/layouts/__tests__/BaseLayout.test.tsx +13 -0
- package/dist/app-DaxS_Cz-.js +0 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"+4Z6iP\":[\"先在 GitHub 上建立儲存庫 — 可以是空的。\"],\"+9JI/F\":[\"連線後會將您的網站同步到 \",[\"repo\"],\" 的預設分支,並疊加在其現有歷史之上。Jant 管理路徑外的既有檔案會保留。此操作無法復原。\"],\"+AXdXp\":[\"標籤與 URL 為必填\"],\"+K0AvT\":[\"解除連線\"],\"+zy2Nq\":[\"類型\"],\"/3H2/s\":[\"此託管網站透過 \",[\"providerLabel\"],\" 登入。請在該處管理密碼與託管存取權限。\"],\"/JnyjR\":[\"切換內建導覽項目。它們的順序決定頁首顯示哪些項目,以及首頁會先開啟哪一個 feed。\"],\"/ODeyS\":[\"聲明給讀者的內容語言(HTML lang、RSS),同時驅動後台介面的語言。可填任意 BCP 47 標籤;沒有對應翻譯時後台會回退為英文。\"],\"/zOUxl\":[\"連結至 Telegram 機器人的 QR 碼\"],\"0OGSSc\":[\"頭像顯示已更新.\"],\"0UzCUX\":[\"更新您用來登入的密碼\"],\"0bdA9b\":[\"開啟 Telegram 以連線\"],\"10UtuM\":[\"CJK 字體\"],\"14BEca\":[\"瞭解原因\"],\"1F6Mzc\":[\"目前尚無導覽項目。請新增連結或在下方啟用系統項目。\"],\"1mbBbL\":[\"手動連接\"],\"1njn7W\":[\"淺色\"],\"2B7t+s\":[\"工作階段與密碼\"],\"2DoBvq\":[\"訂閱來源\"],\"2FYpfJ\":[\"更多\"],\"2Ithfh\":[\"傳送任何文字給機器人,該文字會作為筆記已發佈。\"],\"2MXb5X\":[\"關於靜謐設計的實地筆記\"],\"2PTjMB\":[\"我想刪除 \",[\"siteName\"]],\"2cFU6q\":[\"網站頁尾\"],\"2oWZo7\":[\"最近一次提交\"],\"2uuy4H\":[\"已透過個人存取權杖連線\"],\"35x8eZ\":[\"顯示 \",[\"shown\"],\" 共 \",[\"total\"]],\"39QGku\":[\"開啟機器人並傳送綁定碼,之後你傳給它的任何訊息都會成為筆記。\"],\"3Cw1AI\":[\"新增選集\"],\"3VrybB\":[\"重新導向\"],\"3Yvsaz\":[\"302 (暫時)\"],\"3n0zbB\":[\"在示範模式中已停用工作階段管理。請改用共用示範工作階段。\"],\"3sYJi5\":[\"下載與 Hugo 相容的封存 — 以靜態方式託管或移轉到另一個 Jant\"],\"3wKq0C\":[\"無法儲存。請稍後再試。\"],\"49Bsal\":[\"Feed 設定已更新。\"],\"4Jge8E\":[\"目前的工作階段\"],\"4KIa+q\":[\"已下載匯出檔案。\"],\"4cEClj\":[\"工作階段\"],\"4zGJ5E\":[\"永久刪除帳戶\"],\"5QlUIt\":[\"倉庫為空。準備連線。\"],\"5VQnR3\":[\"當你想要一個永遠不會改變的 feed URL 時,請使用這些。\"],\"5dpcN1\":[\"輸入以搜尋全部\"],\"5f1Wo9\":[\"已以 \",[\"account\"],\" 連線\"],\"6ArdBh\":[\"將精選文章用於 /feed.\"],\"6DjeBT\":[\"示範網站會始終對搜尋引擎保持隱藏。\"],\"6E3aK4\":[\"管理託管\"],\"6FFB7q\":[\"使用最新的公開貼文作為 /feed。\"],\"6K1Vef\":[\"確定要永久刪除此部落格嗎?此動作無法復原。\"],\"6NpNLc\":[\"此儲存庫已有內容。\"],\"6V3Ea3\":[\"已複製\"],\"71WIgc\":[\"取得新代碼\"],\"746NHh\":[\"此部落格\"],\"7811AW\":[\"此儲存庫已在備份本網站。\"],\"7FaY4u\":[\"用法\"],\"7G9YLi\":[\"允許搜尋引擎收錄我的網站\"],\"7GISOt\":[\"儲存機器人權杖\"],\"7MZxzw\":[\"密碼已變更.\"],\"7vhWI8\":[\"新密碼\"],\"81nFIS\":[\"密碼不符。請確認兩個欄位相同。\"],\"87a/t/\":[\"標籤\"],\"89Upyo\":[\"該主題目前不可用。請選擇其他主題。\"],\"8BfEpW\":[\"託管帳號\"],\"8N/Mcp\":[\"封存篩選參數 (例如 format=note&view=list)\"],\"8T46pB\":[\"機器人權杖\"],\"8U2Z7f\":[\"新增自訂 URL\"],\"8ZsakT\":[\"密碼\"],\"9+vGLh\":[\"自訂 CSS\"],\"9As8Nu\":[\"在 GitHub 上建立一個\"],\"9Lsvt5\":[\"已於 \",[\"date\"],\" 登入\"],\"9T7Cwm\":[\"重新導向、自訂路徑與網址控制\"],\"9aUyym\":[\"查看您在哪裡已登入,並撤銷舊的工作階段\"],\"A1taO8\":[\"搜尋\"],\"AeXO77\":[\"帳戶\"],\"AnY+O9\":[\"在首頁底部顯示「Build with Jant」\"],\"ApZDMk\":[\"此圖會用於您的 favicon 和 apple-touch-icon。為達最佳效果,請上傳至少 512x512 像素、背景為純色的正方形 PNG。\"],\"B495Gs\":[\"封存\"],\"B4ESok\":[\"API 參考\"],\"BzEFor\":[\"或\"],\"CDAdlf\":[\"移除機器人\"],\"CTAEes\":[\"選擇儲存庫\"],\"CjZZgz\":[\"此儲存庫已有提交紀錄\"],\"D8k2s6\":[\"連接 Telegram\"],\"DCKkhU\":[\"目前密碼\"],\"DKKKeF\":[\"在 \",[\"providerLabel\"],\" 管理密碼與託管存取\"],\"EO3I6h\":[\"上傳未成功. 請稍後再試.\"],\"Enslfm\":[\"目標網址\"],\"F7FKwe\":[\"你在此處貼上的任何內容都能完全存取訪客的瀏覽器。僅使用來自你信任來源的程式碼。\"],\"FkMol5\":[\"精選\"],\"G/1oP+\":[\"移除 webhook 並停止同步。您的儲存庫內容不會被刪除。\"],\"G0qJsQ\":[\"找不到安全權杖。請重新整理頁面後再試一次。\"],\"G39wnK\":[\"將內容備份並與 GitHub 儲存庫同步\"],\"GMMWcy\":[\"名稱, 中繼資料, 語言, 和 搜尋預設值\"],\"GXsAby\":[\"撤銷\"],\"GxkJXS\":[\"上傳中...\"],\"GzKzUa\":[\"試用限制\"],\"HKH+W+\":[\"資料\"],\"Hp1l6f\":[\"目前\"],\"HxlY7t\":[\"變更此設定會更新訂閱者從 /feed 取得的內容。\"],\"HxuOlm\":[\"網站頁首\"],\"I6gXOa\":[\"路徑\"],\"ID38tA\":[\"示範模式下帳號刪除已停用。共用示範會另行重置。\"],\"IF9tPu\":[\"何時使用網站匯出、資料庫備份與復原演練。\"],\"IW5PBo\":[\"複製權杖\"],\"IagCbF\":[\"URL\"],\"IreQBq\":[\"儲存庫\"],\"J6bLeg\":[\"新增自訂連結到任何 URL\"],\"JL7LF5\":[\"可用的 CSS 變數、data 屬性與範例.\"],\"JTviaO\":[\"管理登入安全、匯出,以及不可逆的操作。\"],\"JcD7qf\":[\"更多操作\"],\"JjX0OO\":[\"請立即複製您的權杖 — 它不會再顯示。\"],\"JrFTcr\":[\"連線中…\"],\"JuN5GC\":[\"未選取檔案。請選擇要上傳的檔案。\"],\"KDw4GX\":[\"重試\"],\"KSgo21\":[\"選擇一個儲存庫\"],\"KVVYBh\":[\"新增選集到導覽\"],\"KiJn9B\":[\"筆記\"],\"L3DEwT\":[\"移除這個頭像?您的 favicon 與頁首圖示會回復為預設。\"],\"L4t4/q\":[\"3月14日\"],\"LdyooL\":[\"連結\"],\"M/D8PK\":[\"+ 安裝到其他帳戶\"],\"M/haSd\":[\"永遠顯示主題的淺色版本。\"],\"M2kIWU\":[\"字型主題\"],\"M6CbAU\":[\"切換編輯面板\"],\"MaYYE6\":[\"透過向 Telegram 機器人傳送訊息來發佈筆記\"],\"Me5t5H\":[\"連接 GitHub 倉庫,自動將你的文章備份為 Markdown 檔案。GitHub 上的編輯會同步回你的網站。\"],\"Mr4QPw\":[\"要斷開 Telegram?您可以隨時使用新的綁定代碼重新連接。\"],\"MtENL9\":[\"調整您的網站外觀、可讀性與執行效能。\"],\"N/8NPV\":[\"在刪除前,請先下載網站匯出檔案。刪除後無法恢復此帳號。\"],\"N7UNHY\":[\"精選 RSS 來源\"],\"NHnUHF\":[\"頁首上的網站圖示與個人標記\"],\"NU2Fqi\":[\"儲存 CSS\"],\"Nldjdr\":[\"尚未有自訂 URL。建立一個自訂 URL 以為文章新增重新導向或自訂路徑。\"],\"O7rgs6\":[\"頁首 RSS 指向你的 \",[\"feed\"],\" 訂閱 (/feed)。在「一般」中變更 /feed 回傳的內容。\"],\"OSJXFg\":[\"套用於整個網站, 包括管理頁面. 選擇一個調色盤, 然後決定它是跟隨系統還是維持固定.\"],\"Ox3+3h\":[\"無相符結果。\"],\"PEUV5I\":[\"程式碼注入已更新。\"],\"PXj9lw\":[\"停止接受來自 Telegram 的貼文。您現有的筆記會保持已發佈。\"],\"PZ7HJ8\":[\"部落格大頭貼\"],\"Pwqkdw\":[\"載入中…\"],\"PxJ9W6\":[\"產生權杖\"],\"Q/6Y+2\":[\"需要在目標儲存庫上擁有 Contents(讀/寫)和 Webhooks(讀/寫)權限。\"],\"Q30z/l\":[\"要從導覽移除這個選集嗎?選集本身不會被刪除。\"],\"Q99OtV\":[\"將選集釘選到導覽列。最近 48 小時內更新的選集旁會顯示一個 * 號。\"],\"QZmz0H\":[\"內建連結\"],\"Qnrzvb\":[\"已啟用的權杖\"],\"R6Z4LE\":[\"下載失敗。請再試一次。\"],\"R9Khdg\":[\"自動\"],\"RcdDOS\":[\"在 Telegram 上向 @BotFather 發送訊息以建立機器人,然後貼上它提供給你的權杖\"],\"RxsRD6\":[\"時區\"],\"SJmfuf\":[\"網站名稱\"],\"SKZhW9\":[\"權杖名稱 (API 權杖名稱欄位。)\"],\"SVQQPe\":[\"無法連線。請檢查錯誤並再試一次。\"],\"SchpMp\":[\"Telegram\"],\"TpF3v+\":[\"在 </head> 之前注入。用於分析、自訂 meta 標籤,以及必須提前載入的樣式。\"],\"Tz0i8g\":[\"設定\"],\"UFK415\":[\"用於分析與小工具的網站全域 HTML\"],\"UTvFQq\":[\"開啟 \",[\"linkOpen\"],\"@\",[\"botUsername\"],[\"linkClose\"],\" 並傳送:\"],\"Uj/btJ\":[\"在我的網站頁首顯示大頭貼\"],\"UsODUn\":[\"選擇一個帳戶\"],\"UxKoFf\":[\"導覽\"],\"V+bhUy\":[\"安裝 GitHub App\"],\"V4WsyL\":[\"新增連結\"],\"V5pZwT\":[\"搜尋設定已更新。\"],\"VXUPla\":[\"使用 GitHub App 連線\"],\"VhMDMg\":[\"變更密碼\"],\"Vn3jYy\":[\"導覽項目\"],\"VoZYGU\":[\"這會永久刪除您所有的資料 — 文章、媒體、選集、設定,以及您的帳戶。您的部落格將被重設為初始設定狀態。此操作無法復原。\"],\"Weq9zb\":[\"一般\"],\"Wi9i06\":[\"依照每位訪客的系統偏好。\"],\"Wx1M8N\":[\"安裝 GitHub App,以授予存取權而無需管理個人權杖。權限以每個儲存庫為範圍,並可在 GitHub 上撤銷。\"],\"X+8FMk\":[\"目前的密碼不符。請再試一次。\"],\"X1G9eY\":[\"導覽預覽\"],\"X9Hujr\":[\"手動推送\"],\"XtBJV8\":[\"正在檢查儲存庫…\"],\"Xtc16w\":[\"重新整理儲存庫清單\"],\"Y/F35r\":[\"使用 curl 建立貼文:\"],\"YF6zHf\":[\"網站設定已更新.\"],\"YdG2RF\":[\"匯出網站\"],\"YkgZi7\":[\"連接一個 Telegram 機器人,之後你傳給它的任何訊息都會以筆記形式已發佈。\"],\"YwhjRx\":[\"管理帳戶\"],\"ZDY7Fy\":[\"同步中…\"],\"ZQKLI1\":[\"危險區域\"],\"ZS/CBL\":[\"刪除此導覽連結?訪客將不再在您的網站頁首看到它。\"],\"ZhhOwV\":[\"引用 (Jant 的貼文格式之一。)\"],\"ZiooJI\":[\"API 權杖\"],\"Zm7Qb0\":[\"備份與還原指南\"],\"ZmUkwN\":[\"新增自訂連結到導覽\"],\"a14mj8\":[\"未知裝置\"],\"a3LDKx\":[\"安全性\"],\"aAIQg2\":[\"外觀\"],\"aFkzVF\":[\"目標文章或選集的 slug\"],\"alKG0+\":[\"字型主題\"],\"anibOb\":[\"關於本部落格\"],\"any7NR\":[\"主題指南\"],\"b+/jO6\":[\"301 (永久)\"],\"bHOiy1\":[\"示範模式已停用變更密碼功能。請使用共用示範帳號登入。\"],\"bHYIks\":[\"登出\"],\"bmrL08\":[\"示範模式會隱藏會話、密碼更改與帳號刪除。匯出功能仍可使用。\"],\"c3MN2z\":[\"所有可用的端點與請求格式。\"],\"cS7/bk\":[\"移除已儲存的機器人權杖?其 webhook 會被刪除,任何已連接的帳號將會被斷線。\"],\"cSDy01\":[\"自訂 CSS 已更新.\"],\"clzoNp\":[\"始終顯示深色主題。\"],\"cnGeoo\":[\"刪除\"],\"d3FRkY\":[\"無法複製. 請再試一次.\"],\"d5oGUo\":[\"在 GitHub 建立新儲存庫\"],\"dEgA5A\":[\"取消\"],\"dTXUY+\":[\"確認刪除帳號\"],\"dYKrp3\":[\"從最新中隱藏 (不出現在 Latest 中,但仍可透過固定連結和集合存取的狀態。)\"],\"dk7TCH\":[\"永久刪除所有資料並重設部落格\"],\"drodVV\":[\"目前還沒有選集。請先建立一個,然後將它加入您的導覽。\"],\"dsWkIw\":[\"要與 GitHub 斷開連線嗎? webhook 將會被移除。您的儲存庫內容不會被刪除。\"],\"e/tSI5\":[\"導覽順序已更新。\"],\"ePK91l\":[\"編輯\"],\"ebQKK7\":[\"網站\"],\"egK+Yy\":[\"供腳本與自動化使用的 Bearer 權杖\"],\"ehj/zN\":[\"重新導向類型\"],\"eneWvv\":[\"草稿\"],\"erTMh7\":[\"上次同步\"],\"f+m8jj\":[\"已複製訂閱網址。\"],\"f8fH8W\":[\"設計\"],\"fWYqkz\":[\"程式碼注入\"],\"gOWiTY\":[\"載入為中文、日文或韓文內容最佳化的襯線字型。\"],\"gZ5owP\":[\"搜尋儲存庫\"],\"gbqbh6\":[\"可以放心離開此頁面 — 同步會在背景繼續進行。\"],\"gkFvVN\":[\"在 </body> 之前注入。用於聊天小工具和不應阻塞頁面載入的腳本。\"],\"gtQsRO\":[\"建立自訂網址\"],\"hBO/y4\":[\"安全權杖已過期。請重新整理頁面並再試一次。\"],\"hGmyDl\":[\"權杖讓您從腳本, 捷徑和其他工具存取 API 無需登入\"],\"hIHkRy\":[\"已透過 GitHub 應用程式連線\"],\"hdSi1b\":[\"輸入 \",[\"repo\"],\" 以確認\"],\"he3ygx\":[\"複製\"],\"i0qMbr\":[\"首頁\"],\"iEUzMn\":[\"系統\"],\"iSLIjg\":[\"連接\"],\"iVOMRi\":[\"首頁設定已更新。\"],\"icB4Cv\":[\"將連結拖到此處以顯示於「更多」選單下方\"],\"id3vuh\":[\"已設定 Telegram,但無法連線到機器人。請檢查機器人權杖並再試一次。\"],\"ihn4zD\":[\"搜尋…\"],\"iiDXZc\":[\"顯示於所有文章與頁面的底部。\"],\"j4VrG6\":[\"下載匯出 ZIP\"],\"j5nQL2\":[\"例如 iOS 捷徑\"],\"jUV7CU\":[\"上傳大頭貼\"],\"jVUmOK\":[\"支援 Markdown\"],\"jgBjXJ\":[\"要撤銷這個權杖嗎?任何使用它的腳本都會停止運作。\"],\"jpctdh\":[\"檢視\"],\"k1ifdL\":[\"處理中...\"],\"kMXclu\":[\"下載網站匯出檔案\"],\"kNiQp6\":[\"已釘選\"],\"kRhzWq\":[\"GitHub 同步\"],\"kVQs7s\":[\"細緻的樣式覆寫\"],\"ke1gWS\":[\"自訂 URL\"],\"kfcRb0\":[\"頭像\"],\"kxDZ2i\":[\"此程式碼會在您網站的每個頁面上執行。\"],\"l2Op2p\":[\"查詢參數\"],\"lLW3vJ\":[\"目標 slug\"],\"lYHJih\":[\"撤銷此工作階段?該裝置將需要重新登入。\"],\"mLOk1i\":[\"立即將所有文章推送到 GitHub,而不是等待下一次自動同步。\"],\"mSNmrX\":[\"列出貼文:\"],\"nK07ni\":[\"為您的網站選擇一種排版風格。每個主題會同時改變字體配對與閱讀節奏。\"],\"nbfdhU\":[\"第三方整合\"],\"ntJYyh\":[\"在 \",[\"providerLabel\"],\" 管理網域、方案和計費\"],\"o/vNDE\":[\"讓您覆寫任何主題變數。\"],\"oGC9uP\":[\"owner/repo\"],\"oH2JHg\":[\"我們會預先填入名稱 \",[\"name\"],\"。返回時清單會重新整理。\"],\"oKOOsY\":[\"色彩主題\"],\"oL535e\":[\"尚未同步\"],\"oNA4If\":[\"所有選集已經在您的導覽中。\"],\"pZq3aX\":[\"上傳失敗。請再試一次。\"],\"pgTIrt\":[\"選擇要與此網站同步的 GitHub 帳號和儲存庫.\"],\"psoxDF\":[\"該字型主題無法使用. 請選擇其他主題.\"],\"pvnfJD\":[\"深色\"],\"q+hNag\":[\"選集\"],\"qdcESc\":[\"建立新儲存庫\"],\"r5EW6f\":[\"此儲存庫已在備份另一個 Jant 網站 (\",[\"host\"],\"). 請選擇其他儲存庫.\"],\"rEspiY\":[\"導覽位置已更新。\"],\"rFmBG3\":[\"色彩主題\"],\"rlonmB\":[\"無法刪除。請稍後再試。\"],\"satWc6\":[\"主要 RSS 訂閱\"],\"sgr2wQ\":[\"選集\"],\"sqxcaY\":[\"建立於 \",[\"date\"]],\"sxkWRg\":[\"進階\"],\"t/YqKh\":[\"移除\"],\"t3hvHq\":[\"立即同步\"],\"tJ4H0O\":[\"您的 Telegram 帳號\"],\"tfDRzk\":[\"儲存\"],\"tvgAq5\":[\"尚未授權任何帳戶\"],\"u1VTd3\":[\"調色盤、表面色調與整體氛圍\"],\"u3wRF+\":[\"已發佈\"],\"u6KOjV\":[\"想要更細緻的控制?\"],\"udPwLB\":[\"頁首\"],\"ui6aMF\":[\"這些裝置目前已登入您的帳號。撤銷任何您不認識的工作階段。\"],\"vBEKwo\":[\"在此管理本網站的活動工作階段。密碼與託管存取由 \",[\"providerLabel\"],\" 管理。\"],\"vRldcl\":[\"字體選擇與閱讀質感\"],\"vSYKYI\":[\"主要訂閱來源\"],\"vTuib7\":[\"這會控制 /feed 回傳的內容。\"],\"vXIe7J\":[\"語言\"],\"vmQmHx\":[\"新增自訂 CSS 以覆寫任何樣式。使用像 [data-page]、[data-post]、[data-format] 這類資料屬性來選取特定元素。\"],\"vzX5FB\":[\"刪除帳號\"],\"w8Rv8T\":[\"標籤為必填\"],\"wL3cK8\":[\"最新\"],\"wPmHHc\":[\"低調的介面讓文字成為主角。\"],\"wW6NCp\":[\"上次錯誤\"],\"wc+17X\":[\"/* 在此放入您的自訂 CSS */\"],\"wuLtXn\":[\"目前沒有任何活動中的工作階段。已登入的裝置會顯示在此處。\"],\"xCWek4\":[\"檔案儲存尚未設定。請檢查您的伺服器設定。\"],\"xHt036\":[\"個人存取權杖\"],\"xbN8dp\":[\"柔和的色彩仍應保有清晰的閱讀節奏。\"],\"y28hnO\":[\"文章\"],\"y8Md/V\":[\"語言與時間已更新。\"],\"yNCqOt\":[\"最新 RSS 來源\"],\"yQ3kNF\":[\"請輸入以下短語以確認:\"],\"ydq1k2\":[\"請先選擇一個帳戶\"],\"yjjCV8\":[\"固定的 RSS 檔案網址\"],\"yjkELF\":[\"確認新密碼\"],\"yzF66j\":[\"連結 (Jant 的貼文格式之一。)\"],\"z6wakA\":[\"顯示在您的主頁上的簡短介紹。\"],\"zEizrk\":[\"最後使用於 \",[\"date\"]],\"zSURJW\":[\"沒有符合的儲存庫.\"],\"zXH2jX\":[\"語言與時區\"],\"zlcDd2\":[\"刪除此自訂 URL?使用該 URL 的訪客將不會再被重新導向。\"],\"zwBp5t\":[\"私有\"],\"zxRN6H\":[\"頁首連結、首頁動態與更多選單\"]}")as Messages;
|
|
1
|
+
/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"++YsxG\":[\"讀者和搜尋引擎看到的是\"],\"+4Z6iP\":[\"先在 GitHub 上建立儲存庫 — 可以是空的。\"],\"+9JI/F\":[\"連線後會將您的網站同步到 \",[\"repo\"],\" 的預設分支,並疊加在其現有歷史之上。Jant 管理路徑外的既有檔案會保留。此操作無法復原。\"],\"+AXdXp\":[\"標籤與 URL 為必填\"],\"+K0AvT\":[\"解除連線\"],\"+zy2Nq\":[\"類型\"],\"/3H2/s\":[\"此託管網站透過 \",[\"providerLabel\"],\" 登入。請在該處管理密碼與託管存取權限。\"],\"/JnyjR\":[\"切換內建導覽項目。它們的順序決定頁首顯示哪些項目,以及首頁會先開啟哪一個 feed。\"],\"/zOUxl\":[\"連結至 Telegram 機器人的 QR 碼\"],\"0OGSSc\":[\"頭像顯示已更新.\"],\"0UzCUX\":[\"更新您用來登入的密碼\"],\"0bdA9b\":[\"開啟 Telegram 以連線\"],\"10UtuM\":[\"CJK 字體\"],\"14BEca\":[\"瞭解原因\"],\"1F6Mzc\":[\"目前尚無導覽項目。請新增連結或在下方啟用系統項目。\"],\"1H7gng\":[\"後台語言\"],\"1mbBbL\":[\"手動連接\"],\"1njn7W\":[\"淺色\"],\"2B7t+s\":[\"工作階段與密碼\"],\"2DoBvq\":[\"訂閱來源\"],\"2FYpfJ\":[\"更多\"],\"2Ithfh\":[\"傳送任何文字給機器人,該文字會作為筆記已發佈。\"],\"2MXb5X\":[\"關於靜謐設計的實地筆記\"],\"2PTjMB\":[\"我想刪除 \",[\"siteName\"]],\"2cFU6q\":[\"網站頁尾\"],\"2oWZo7\":[\"最近一次提交\"],\"2uuy4H\":[\"已透過個人存取權杖連線\"],\"35x8eZ\":[\"顯示 \",[\"shown\"],\" 共 \",[\"total\"]],\"39QGku\":[\"開啟機器人並傳送綁定碼,之後你傳給它的任何訊息都會成為筆記。\"],\"3Cw1AI\":[\"新增選集\"],\"3VrybB\":[\"重新導向\"],\"3Yvsaz\":[\"302 (暫時)\"],\"3n0zbB\":[\"在示範模式中已停用工作階段管理。請改用共用示範工作階段。\"],\"3sYJi5\":[\"下載與 Hugo 相容的封存 — 以靜態方式託管或移轉到另一個 Jant\"],\"3wKq0C\":[\"無法儲存。請稍後再試。\"],\"49Bsal\":[\"Feed 設定已更新。\"],\"4Jge8E\":[\"目前的工作階段\"],\"4KIa+q\":[\"已下載匯出檔案。\"],\"4cEClj\":[\"工作階段\"],\"4zGJ5E\":[\"永久刪除帳戶\"],\"5QlUIt\":[\"倉庫為空。準備連線。\"],\"5VQnR3\":[\"當你想要一個永遠不會改變的 feed URL 時,請使用這些。\"],\"5dpcN1\":[\"輸入以搜尋全部\"],\"5f1Wo9\":[\"已以 \",[\"account\"],\" 連線\"],\"6ArdBh\":[\"將精選文章用於 /feed.\"],\"6DjeBT\":[\"示範網站會始終對搜尋引擎保持隱藏。\"],\"6E3aK4\":[\"管理託管\"],\"6FFB7q\":[\"使用最新的公開貼文作為 /feed。\"],\"6K1Vef\":[\"確定要永久刪除此部落格嗎?此動作無法復原。\"],\"6NpNLc\":[\"此儲存庫已有內容。\"],\"6V3Ea3\":[\"已複製\"],\"71WIgc\":[\"取得新代碼\"],\"746NHh\":[\"此部落格\"],\"7811AW\":[\"此儲存庫已在備份本網站。\"],\"7FCZPo\":[\"內容語言\"],\"7FaY4u\":[\"用法\"],\"7G9YLi\":[\"允許搜尋引擎收錄我的網站\"],\"7GISOt\":[\"儲存機器人權杖\"],\"7MZxzw\":[\"密碼已變更.\"],\"7vhWI8\":[\"新密碼\"],\"81nFIS\":[\"密碼不符。請確認兩個欄位相同。\"],\"87a/t/\":[\"標籤\"],\"89Upyo\":[\"該主題目前不可用。請選擇其他主題。\"],\"8BfEpW\":[\"託管帳號\"],\"8N/Mcp\":[\"封存篩選參數 (例如 format=note&view=list)\"],\"8T46pB\":[\"機器人權杖\"],\"8U2Z7f\":[\"新增自訂 URL\"],\"8ZsakT\":[\"密碼\"],\"9+vGLh\":[\"自訂 CSS\"],\"9As8Nu\":[\"在 GitHub 上建立一個\"],\"9Lsvt5\":[\"已於 \",[\"date\"],\" 登入\"],\"9T7Cwm\":[\"重新導向、自訂路徑與網址控制\"],\"9aUyym\":[\"查看您在哪裡已登入,並撤銷舊的工作階段\"],\"A1taO8\":[\"搜尋\"],\"AeXO77\":[\"帳戶\"],\"AnY+O9\":[\"在首頁底部顯示「Build with Jant」\"],\"ApZDMk\":[\"此圖會用於您的 favicon 和 apple-touch-icon。為達最佳效果,請上傳至少 512x512 像素、背景為純色的正方形 PNG。\"],\"B495Gs\":[\"封存\"],\"B4ESok\":[\"API 參考\"],\"BzEFor\":[\"或\"],\"CDAdlf\":[\"移除機器人\"],\"CTAEes\":[\"選擇儲存庫\"],\"CjZZgz\":[\"此儲存庫已有提交紀錄\"],\"D8k2s6\":[\"連接 Telegram\"],\"DCKkhU\":[\"目前密碼\"],\"DKKKeF\":[\"在 \",[\"providerLabel\"],\" 管理密碼與託管存取\"],\"EO3I6h\":[\"上傳未成功. 請稍後再試.\"],\"Enslfm\":[\"目標網址\"],\"F7FKwe\":[\"你在此處貼上的任何內容都能完全存取訪客的瀏覽器。僅使用來自你信任來源的程式碼。\"],\"FkMol5\":[\"精選\"],\"G/1oP+\":[\"移除 webhook 並停止同步。您的儲存庫內容不會被刪除。\"],\"G0qJsQ\":[\"找不到安全權杖。請重新整理頁面後再試一次。\"],\"G39wnK\":[\"將內容備份並與 GitHub 儲存庫同步\"],\"GMMWcy\":[\"名稱, 中繼資料, 語言, 和 搜尋預設值\"],\"GXsAby\":[\"撤銷\"],\"GxkJXS\":[\"上傳中...\"],\"GzKzUa\":[\"試用限制\"],\"H4x7Sk\":[\"後台介面顯示的語言。可選 English、简体中文 和 繁體中文。\"],\"HKH+W+\":[\"資料\"],\"Hp1l6f\":[\"目前\"],\"HxlY7t\":[\"變更此設定會更新訂閱者從 /feed 取得的內容。\"],\"HxuOlm\":[\"網站頁首\"],\"I6gXOa\":[\"路徑\"],\"ID38tA\":[\"示範模式下帳號刪除已停用。共用示範會另行重置。\"],\"IF9tPu\":[\"何時使用網站匯出、資料庫備份與復原演練。\"],\"IW5PBo\":[\"複製權杖\"],\"IagCbF\":[\"URL\"],\"IfB3m6\":[\"你撰寫內容所用的語言。透過 HTML lang 和 RSS 向讀者與搜尋引擎宣告。支援任意 BCP 47 標籤。\"],\"IreQBq\":[\"儲存庫\"],\"J6bLeg\":[\"新增自訂連結到任何 URL\"],\"JL7LF5\":[\"可用的 CSS 變數、data 屬性與範例.\"],\"JTviaO\":[\"管理登入安全、匯出,以及不可逆的操作。\"],\"JcD7qf\":[\"更多操作\"],\"JjX0OO\":[\"請立即複製您的權杖 — 它不會再顯示。\"],\"JrFTcr\":[\"連線中…\"],\"JuN5GC\":[\"未選取檔案。請選擇要上傳的檔案。\"],\"KDw4GX\":[\"重試\"],\"KSgo21\":[\"選擇一個儲存庫\"],\"KVVYBh\":[\"新增選集到導覽\"],\"KiJn9B\":[\"筆記\"],\"L3DEwT\":[\"移除這個頭像?您的 favicon 與頁首圖示會回復為預設。\"],\"L4t4/q\":[\"3月14日\"],\"LdyooL\":[\"連結\"],\"M/D8PK\":[\"+ 安裝到其他帳戶\"],\"M/haSd\":[\"永遠顯示主題的淺色版本。\"],\"M2kIWU\":[\"字型主題\"],\"M6CbAU\":[\"切換編輯面板\"],\"MaYYE6\":[\"透過向 Telegram 機器人傳送訊息來發佈筆記\"],\"Me5t5H\":[\"連接 GitHub 倉庫,自動將你的文章備份為 Markdown 檔案。GitHub 上的編輯會同步回你的網站。\"],\"Mr4QPw\":[\"要斷開 Telegram?您可以隨時使用新的綁定代碼重新連接。\"],\"MtENL9\":[\"調整您的網站外觀、可讀性與執行效能。\"],\"N/8NPV\":[\"在刪除前,請先下載網站匯出檔案。刪除後無法恢復此帳號。\"],\"N7UNHY\":[\"精選 RSS 來源\"],\"NHnUHF\":[\"頁首上的網站圖示與個人標記\"],\"NU2Fqi\":[\"儲存 CSS\"],\"Nldjdr\":[\"尚未有自訂 URL。建立一個自訂 URL 以為文章新增重新導向或自訂路徑。\"],\"O7rgs6\":[\"頁首 RSS 指向你的 \",[\"feed\"],\" 訂閱 (/feed)。在「一般」中變更 /feed 回傳的內容。\"],\"OSJXFg\":[\"套用於整個網站, 包括管理頁面. 選擇一個調色盤, 然後決定它是跟隨系統還是維持固定.\"],\"Ox3+3h\":[\"無相符結果。\"],\"PEUV5I\":[\"程式碼注入已更新。\"],\"PXj9lw\":[\"停止接受來自 Telegram 的貼文。您現有的筆記會保持已發佈。\"],\"PZ7HJ8\":[\"部落格大頭貼\"],\"Pwqkdw\":[\"載入中…\"],\"PxJ9W6\":[\"產生權杖\"],\"Q/6Y+2\":[\"需要在目標儲存庫上擁有 Contents(讀/寫)和 Webhooks(讀/寫)權限。\"],\"Q30z/l\":[\"要從導覽移除這個選集嗎?選集本身不會被刪除。\"],\"Q99OtV\":[\"將選集釘選到導覽列。最近 48 小時內更新的選集旁會顯示一個 * 號。\"],\"QZmz0H\":[\"內建連結\"],\"Qnrzvb\":[\"已啟用的權杖\"],\"R6Z4LE\":[\"下載失敗。請再試一次。\"],\"R9Khdg\":[\"自動\"],\"RcdDOS\":[\"在 Telegram 上向 @BotFather 發送訊息以建立機器人,然後貼上它提供給你的權杖\"],\"RxsRD6\":[\"時區\"],\"SJmfuf\":[\"網站名稱\"],\"SKZhW9\":[\"權杖名稱 (API 權杖名稱欄位。)\"],\"SVQQPe\":[\"無法連線。請檢查錯誤並再試一次。\"],\"SchpMp\":[\"Telegram\"],\"TpF3v+\":[\"在 </head> 之前注入。用於分析、自訂 meta 標籤,以及必須提前載入的樣式。\"],\"Tz0i8g\":[\"設定\"],\"UFK415\":[\"用於分析與小工具的網站全域 HTML\"],\"UTvFQq\":[\"開啟 \",[\"linkOpen\"],\"@\",[\"botUsername\"],[\"linkClose\"],\" 並傳送:\"],\"Uj/btJ\":[\"在我的網站頁首顯示大頭貼\"],\"UsODUn\":[\"選擇一個帳戶\"],\"UxKoFf\":[\"導覽\"],\"V+bhUy\":[\"安裝 GitHub App\"],\"V4WsyL\":[\"新增連結\"],\"V5pZwT\":[\"搜尋設定已更新。\"],\"VXUPla\":[\"使用 GitHub App 連線\"],\"VhMDMg\":[\"變更密碼\"],\"Vn3jYy\":[\"導覽項目\"],\"VoZYGU\":[\"這會永久刪除您所有的資料 — 文章、媒體、選集、設定,以及您的帳戶。您的部落格將被重設為初始設定狀態。此操作無法復原。\"],\"Weq9zb\":[\"一般\"],\"Wi9i06\":[\"依照每位訪客的系統偏好。\"],\"Wx1M8N\":[\"安裝 GitHub App,以授予存取權而無需管理個人權杖。權限以每個儲存庫為範圍,並可在 GitHub 上撤銷。\"],\"X+8FMk\":[\"目前的密碼不符。請再試一次。\"],\"X1G9eY\":[\"導覽預覽\"],\"X9Hujr\":[\"手動推送\"],\"XtBJV8\":[\"正在檢查儲存庫…\"],\"Xtc16w\":[\"重新整理儲存庫清單\"],\"Y/F35r\":[\"使用 curl 建立貼文:\"],\"YF6zHf\":[\"網站設定已更新.\"],\"YdG2RF\":[\"匯出網站\"],\"YkgZi7\":[\"連接一個 Telegram 機器人,之後你傳給它的任何訊息都會以筆記形式已發佈。\"],\"YwhjRx\":[\"管理帳戶\"],\"ZDY7Fy\":[\"同步中…\"],\"ZQKLI1\":[\"危險區域\"],\"ZS/CBL\":[\"刪除此導覽連結?訪客將不再在您的網站頁首看到它。\"],\"ZhhOwV\":[\"引用 (Jant 的貼文格式之一。)\"],\"ZiooJI\":[\"API 權杖\"],\"Zm7Qb0\":[\"備份與還原指南\"],\"ZmUkwN\":[\"新增自訂連結到導覽\"],\"a14mj8\":[\"未知裝置\"],\"a3LDKx\":[\"安全性\"],\"aAIQg2\":[\"外觀\"],\"aFkzVF\":[\"目標文章或選集的 slug\"],\"alKG0+\":[\"字型主題\"],\"anibOb\":[\"關於本部落格\"],\"any7NR\":[\"主題指南\"],\"b+/jO6\":[\"301 (永久)\"],\"bHOiy1\":[\"示範模式已停用變更密碼功能。請使用共用示範帳號登入。\"],\"bHYIks\":[\"登出\"],\"bmrL08\":[\"示範模式會隱藏會話、密碼更改與帳號刪除。匯出功能仍可使用。\"],\"c3MN2z\":[\"所有可用的端點與請求格式。\"],\"cS7/bk\":[\"移除已儲存的機器人權杖?其 webhook 會被刪除,任何已連接的帳號將會被斷線。\"],\"cSDy01\":[\"自訂 CSS 已更新.\"],\"clzoNp\":[\"始終顯示深色主題。\"],\"cnGeoo\":[\"刪除\"],\"d3FRkY\":[\"無法複製. 請再試一次.\"],\"d5oGUo\":[\"在 GitHub 建立新儲存庫\"],\"dEgA5A\":[\"取消\"],\"dTXUY+\":[\"確認刪除帳號\"],\"dYKrp3\":[\"從最新中隱藏 (不出現在 Latest 中,但仍可透過固定連結和集合存取的狀態。)\"],\"dk7TCH\":[\"永久刪除所有資料並重設部落格\"],\"drodVV\":[\"目前還沒有選集。請先建立一個,然後將它加入您的導覽。\"],\"dsWkIw\":[\"要與 GitHub 斷開連線嗎? webhook 將會被移除。您的儲存庫內容不會被刪除。\"],\"e/tSI5\":[\"導覽順序已更新。\"],\"ePK91l\":[\"編輯\"],\"ebQKK7\":[\"網站\"],\"egK+Yy\":[\"供腳本與自動化使用的 Bearer 權杖\"],\"ehj/zN\":[\"重新導向類型\"],\"eneWvv\":[\"草稿\"],\"erTMh7\":[\"上次同步\"],\"f+m8jj\":[\"已複製訂閱網址。\"],\"f8fH8W\":[\"設計\"],\"fWYqkz\":[\"程式碼注入\"],\"gOWiTY\":[\"載入為中文、日文或韓文內容最佳化的襯線字型。\"],\"gZ5owP\":[\"搜尋儲存庫\"],\"gbqbh6\":[\"可以放心離開此頁面 — 同步會在背景繼續進行。\"],\"gkFvVN\":[\"在 </body> 之前注入。用於聊天小工具和不應阻塞頁面載入的腳本。\"],\"gtQsRO\":[\"建立自訂網址\"],\"hBO/y4\":[\"安全權杖已過期。請重新整理頁面並再試一次。\"],\"hGmyDl\":[\"權杖讓您從腳本, 捷徑和其他工具存取 API 無需登入\"],\"hIHkRy\":[\"已透過 GitHub 應用程式連線\"],\"hdSi1b\":[\"輸入 \",[\"repo\"],\" 以確認\"],\"he3ygx\":[\"複製\"],\"i0qMbr\":[\"首頁\"],\"iEUzMn\":[\"系統\"],\"iSLIjg\":[\"連接\"],\"iVOMRi\":[\"首頁設定已更新。\"],\"icB4Cv\":[\"將連結拖到此處以顯示於「更多」選單下方\"],\"id3vuh\":[\"已設定 Telegram,但無法連線到機器人。請檢查機器人權杖並再試一次。\"],\"ihn4zD\":[\"搜尋…\"],\"iiDXZc\":[\"顯示於所有文章與頁面的底部。\"],\"j4VrG6\":[\"下載匯出 ZIP\"],\"j5nQL2\":[\"例如 iOS 捷徑\"],\"jUV7CU\":[\"上傳大頭貼\"],\"jVUmOK\":[\"支援 Markdown\"],\"jgBjXJ\":[\"要撤銷這個權杖嗎?任何使用它的腳本都會停止運作。\"],\"jpctdh\":[\"檢視\"],\"k1ifdL\":[\"處理中...\"],\"kMXclu\":[\"下載網站匯出檔案\"],\"kNiQp6\":[\"已釘選\"],\"kRhzWq\":[\"GitHub 同步\"],\"kVQs7s\":[\"細緻的樣式覆寫\"],\"ke1gWS\":[\"自訂 URL\"],\"kfcRb0\":[\"頭像\"],\"kxDZ2i\":[\"此程式碼會在您網站的每個頁面上執行。\"],\"l2Op2p\":[\"查詢參數\"],\"lLW3vJ\":[\"目標 slug\"],\"lYHJih\":[\"撤銷此工作階段?該裝置將需要重新登入。\"],\"mLOk1i\":[\"立即將所有文章推送到 GitHub,而不是等待下一次自動同步。\"],\"mSNmrX\":[\"列出貼文:\"],\"nK07ni\":[\"為您的網站選擇一種排版風格。每個主題會同時改變字體配對與閱讀節奏。\"],\"nbfdhU\":[\"第三方整合\"],\"ntJYyh\":[\"在 \",[\"providerLabel\"],\" 管理網域、方案和計費\"],\"o/vNDE\":[\"讓您覆寫任何主題變數。\"],\"oGC9uP\":[\"owner/repo\"],\"oH2JHg\":[\"我們會預先填入名稱 \",[\"name\"],\"。返回時清單會重新整理。\"],\"oKOOsY\":[\"色彩主題\"],\"oL535e\":[\"尚未同步\"],\"oNA4If\":[\"所有選集已經在您的導覽中。\"],\"pZq3aX\":[\"上傳失敗。請再試一次。\"],\"pgTIrt\":[\"選擇要與此網站同步的 GitHub 帳號和儲存庫.\"],\"psoxDF\":[\"該字型主題無法使用. 請選擇其他主題.\"],\"pvnfJD\":[\"深色\"],\"q+hNag\":[\"選集\"],\"qdcESc\":[\"建立新儲存庫\"],\"r5EW6f\":[\"此儲存庫已在備份另一個 Jant 網站 (\",[\"host\"],\"). 請選擇其他儲存庫.\"],\"rEspiY\":[\"導覽位置已更新。\"],\"rFmBG3\":[\"色彩主題\"],\"rlonmB\":[\"無法刪除。請稍後再試。\"],\"satWc6\":[\"主要 RSS 訂閱\"],\"sgr2wQ\":[\"選集\"],\"sqxcaY\":[\"建立於 \",[\"date\"]],\"sxkWRg\":[\"進階\"],\"t/YqKh\":[\"移除\"],\"t3hvHq\":[\"立即同步\"],\"tJ4H0O\":[\"您的 Telegram 帳號\"],\"tfDRzk\":[\"儲存\"],\"tvgAq5\":[\"尚未授權任何帳戶\"],\"u1VTd3\":[\"調色盤、表面色調與整體氛圍\"],\"u3wRF+\":[\"已發佈\"],\"u6KOjV\":[\"想要更細緻的控制?\"],\"udPwLB\":[\"頁首\"],\"ui6aMF\":[\"這些裝置目前已登入您的帳號。撤銷任何您不認識的工作階段。\"],\"vBEKwo\":[\"在此管理本網站的活動工作階段。密碼與託管存取由 \",[\"providerLabel\"],\" 管理。\"],\"vRldcl\":[\"字體選擇與閱讀質感\"],\"vSYKYI\":[\"主要訂閱來源\"],\"vTuib7\":[\"這會控制 /feed 回傳的內容。\"],\"vmQmHx\":[\"新增自訂 CSS 以覆寫任何樣式。使用像 [data-page]、[data-post]、[data-format] 這類資料屬性來選取特定元素。\"],\"vzX5FB\":[\"刪除帳號\"],\"w8Rv8T\":[\"標籤為必填\"],\"wL3cK8\":[\"最新\"],\"wPmHHc\":[\"低調的介面讓文字成為主角。\"],\"wW6NCp\":[\"上次錯誤\"],\"wc+17X\":[\"/* 在此放入您的自訂 CSS */\"],\"wuLtXn\":[\"目前沒有任何活動中的工作階段。已登入的裝置會顯示在此處。\"],\"xCWek4\":[\"檔案儲存尚未設定。請檢查您的伺服器設定。\"],\"xHt036\":[\"個人存取權杖\"],\"xbN8dp\":[\"柔和的色彩仍應保有清晰的閱讀節奏。\"],\"y28hnO\":[\"文章\"],\"y8Md/V\":[\"語言與時間已更新。\"],\"yNCqOt\":[\"最新 RSS 來源\"],\"yQ3kNF\":[\"請輸入以下短語以確認:\"],\"ydq1k2\":[\"請先選擇一個帳戶\"],\"yjjCV8\":[\"固定的 RSS 檔案網址\"],\"yjkELF\":[\"確認新密碼\"],\"yzF66j\":[\"連結 (Jant 的貼文格式之一。)\"],\"z6wakA\":[\"顯示在您的主頁上的簡短介紹。\"],\"zEizrk\":[\"最後使用於 \",[\"date\"]],\"zSURJW\":[\"沒有符合的儲存庫.\"],\"zXH2jX\":[\"語言與時區\"],\"zlcDd2\":[\"刪除此自訂 URL?使用該 URL 的訪客將不會再被重新導向。\"],\"zwBp5t\":[\"私有\"],\"zxRN6H\":[\"頁首連結、首頁動態與更多選單\"]}")as Messages;
|
package/src/i18n/middleware.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { I18n } from "@lingui/core";
|
|
|
7
7
|
import {
|
|
8
8
|
createI18n,
|
|
9
9
|
baseLocale,
|
|
10
|
+
isLocale,
|
|
10
11
|
isValidContentLanguage,
|
|
11
12
|
normalizeContentLanguage,
|
|
12
13
|
resolveCatalogLocale,
|
|
@@ -47,12 +48,14 @@ function isAdminPath(path: string): boolean {
|
|
|
47
48
|
* Two related-but-distinct values are computed per request:
|
|
48
49
|
*
|
|
49
50
|
* - `lang` (used for `<html lang>` and RSS): the verbatim BCP 47 content
|
|
50
|
-
* language tag the operator configured. Accepts any tag —
|
|
51
|
-
* `fr-CA`, etc. — independent of whether Jant has a dashboard
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
51
|
+
* language tag the operator configured (`SITE_LANGUAGE`). Accepts any tag —
|
|
52
|
+
* `fi`, `de`, `fr-CA`, etc. — independent of whether Jant has a dashboard
|
|
53
|
+
* catalog.
|
|
54
|
+
* - The active i18n locale: the catalog Jant renders the admin dashboard in.
|
|
55
|
+
* Driven by the explicit `DASHBOARD_LANGUAGE` setting when set; otherwise it
|
|
56
|
+
* falls back to deriving from the content language (exact catalog match →
|
|
57
|
+
* language family → `baseLocale`). On non-admin routes it is always
|
|
58
|
+
* `baseLocale` so public chrome stays English regardless.
|
|
56
59
|
*/
|
|
57
60
|
export function i18nMiddleware(): MiddlewareHandler {
|
|
58
61
|
return async (c, next) => {
|
|
@@ -60,8 +63,14 @@ export function i18nMiddleware(): MiddlewareHandler {
|
|
|
60
63
|
const contentLang = isValidContentLanguage(rawSetting)
|
|
61
64
|
? normalizeContentLanguage(rawSetting)
|
|
62
65
|
: baseLocale;
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
// Dashboard locale: explicit DASHBOARD_LANGUAGE wins; otherwise derive from
|
|
67
|
+
// the content language (the historical behaviour, so sites without the
|
|
68
|
+
// setting are unchanged).
|
|
69
|
+
const dashboardSetting = c.get("allSettings")?.DASHBOARD_LANGUAGE;
|
|
70
|
+
const dashboardLocale = isLocale(dashboardSetting)
|
|
71
|
+
? dashboardSetting
|
|
72
|
+
: resolveCatalogLocale(contentLang);
|
|
73
|
+
const uiLang = isAdminPath(c.req.path) ? dashboardLocale : baseLocale;
|
|
65
74
|
const i18n = createI18n(uiLang);
|
|
66
75
|
|
|
67
76
|
c.set("lang", contentLang);
|
|
@@ -53,15 +53,16 @@ export const SUPPORTED_LOCALE_TAGS = [
|
|
|
53
53
|
"ro",
|
|
54
54
|
"uk",
|
|
55
55
|
|
|
56
|
-
// Common regional variants worth preselecting.
|
|
56
|
+
// Common regional variants worth preselecting. Chinese is offered only via
|
|
57
|
+
// its script subtags (zh-Hans/zh-Hant) above — the region forms (zh-CN/zh-TW/
|
|
58
|
+
// zh-HK) are near-duplicates for `<html lang>` and the W3C recommends script
|
|
59
|
+
// subtags for Chinese, so they are intentionally omitted here. Any stored
|
|
60
|
+
// region tag still resolves and displays correctly via getOrBuildEntry.
|
|
57
61
|
"en-GB",
|
|
58
62
|
"en-US",
|
|
59
63
|
"fr-CA",
|
|
60
64
|
"pt-BR",
|
|
61
65
|
"es-MX",
|
|
62
|
-
"zh-CN",
|
|
63
|
-
"zh-TW",
|
|
64
|
-
"zh-HK",
|
|
65
66
|
] as const;
|
|
66
67
|
|
|
67
68
|
export interface LocaleEntry {
|
package/src/lib/ids.ts
CHANGED
|
@@ -142,6 +142,7 @@ export function resolveConfig(
|
|
|
142
142
|
siteDescription: resolve("SITE_DESCRIPTION", allSettings, env),
|
|
143
143
|
siteDescriptionExplicit,
|
|
144
144
|
siteLanguage: resolve("SITE_LANGUAGE", allSettings, env),
|
|
145
|
+
dashboardLanguage: resolve("DASHBOARD_LANGUAGE", allSettings, env),
|
|
145
146
|
cjkSerifFont: resolve("CJK_SERIF_FONT", allSettings, env),
|
|
146
147
|
homeDefaultView: resolve("HOME_DEFAULT_VIEW", allSettings, env),
|
|
147
148
|
mainRssFeed: resolve("MAIN_RSS_FEED", allSettings, env),
|
package/src/lib/upload.ts
CHANGED
|
@@ -13,6 +13,20 @@ const MEDIA_POSTERS_STORAGE_PREFIX = "posters";
|
|
|
13
13
|
const MEDIA_ASSET_STORAGE_PREFIX = "assets";
|
|
14
14
|
const MEDIA_PREVIEWS_STORAGE_PREFIX = "previews";
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* SQL `LIKE` pattern matching site asset objects (avatars, favicons) stored
|
|
18
|
+
* under `media/<siteId>/assets/<kind>/...`.
|
|
19
|
+
*
|
|
20
|
+
* These assets are referenced from site settings (`SITE_AVATAR`,
|
|
21
|
+
* `SITE_FAVICON_*`), not from posts, so they are intentionally persisted with
|
|
22
|
+
* `postId = null`. The orphan-media reaper must exclude them — otherwise it
|
|
23
|
+
* deletes avatars and favicons as if they were abandoned compose uploads.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* media.storageKey LIKE this pattern → it is a site asset, never an orphan.
|
|
27
|
+
*/
|
|
28
|
+
export const SITE_ASSET_STORAGE_KEY_LIKE_PATTERN = `%/${MEDIA_ASSET_STORAGE_PREFIX}/%`;
|
|
29
|
+
|
|
16
30
|
/** MIME types — images */
|
|
17
31
|
const IMAGE_MIME_TYPES = [
|
|
18
32
|
"image/jpeg",
|
|
@@ -623,7 +623,7 @@ describe("Settings API Routes", () => {
|
|
|
623
623
|
expect(res.status).toBe(401);
|
|
624
624
|
});
|
|
625
625
|
|
|
626
|
-
it("removes avatar-related settings
|
|
626
|
+
it("removes avatar-related settings", async () => {
|
|
627
627
|
const storage = createMockStorage();
|
|
628
628
|
const { app, services } = createTestApp({
|
|
629
629
|
authenticated: true,
|
|
@@ -658,9 +658,6 @@ describe("Settings API Routes", () => {
|
|
|
658
658
|
await services.settings.get("SITE_FAVICON_APPLE_TOUCH"),
|
|
659
659
|
).toBeNull();
|
|
660
660
|
expect(await services.settings.get("SITE_FAVICON_VERSION")).toBeNull();
|
|
661
|
-
expect(storage.delete).toHaveBeenCalledWith(
|
|
662
|
-
"media/sit_test00000000000000000000000/assets/favicon/apple-touch-icon.png",
|
|
663
|
-
);
|
|
664
661
|
});
|
|
665
662
|
});
|
|
666
663
|
});
|
|
@@ -118,6 +118,8 @@ describe("Upload API Routes", () => {
|
|
|
118
118
|
expect(res.status).toBe(200);
|
|
119
119
|
await expect(res.json()).resolves.toEqual({ success: true });
|
|
120
120
|
expect(await services.media.getById(media.id)).toBeNull();
|
|
121
|
+
// This mock exposes no server-side copy, so the object is retired by
|
|
122
|
+
// deleting the original key immediately (no recycle window).
|
|
121
123
|
expect(storage.delete).toHaveBeenCalledWith("media/photo.webp");
|
|
122
124
|
});
|
|
123
125
|
|
|
@@ -62,6 +62,11 @@ function createMockStorage(): StorageDriver & {
|
|
|
62
62
|
async delete(key) {
|
|
63
63
|
files.delete(key);
|
|
64
64
|
},
|
|
65
|
+
|
|
66
|
+
async copy(sourceKey, destKey) {
|
|
67
|
+
const file = files.get(sourceKey);
|
|
68
|
+
if (file) files.set(destKey, { ...file });
|
|
69
|
+
},
|
|
65
70
|
};
|
|
66
71
|
}
|
|
67
72
|
|
|
@@ -121,6 +126,7 @@ describe("Internal upload admin routes", () => {
|
|
|
121
126
|
abortedMultipartUploads: 0,
|
|
122
127
|
deletedSessions: 1,
|
|
123
128
|
deletedOrphanMedia: 0,
|
|
129
|
+
purgedStorageObjects: 0,
|
|
124
130
|
});
|
|
125
131
|
|
|
126
132
|
const remaining = sqlite
|
|
@@ -182,13 +188,25 @@ describe("Internal upload admin routes", () => {
|
|
|
182
188
|
abortedMultipartUploads: 0,
|
|
183
189
|
deletedSessions: 0,
|
|
184
190
|
deletedOrphanMedia: 1,
|
|
191
|
+
// The reaped orphan's object is freshly enqueued (30 days out), so the
|
|
192
|
+
// same sweep purges nothing yet.
|
|
193
|
+
purgedStorageObjects: 0,
|
|
185
194
|
});
|
|
186
195
|
|
|
187
|
-
// Old orphan: DB row
|
|
196
|
+
// Old orphan: DB row gone, original key freed immediately, bytes moved to
|
|
197
|
+
// a trash/ key recorded in storage_purge (recoverable within the window).
|
|
188
198
|
expect(
|
|
189
199
|
sqlite.prepare("select id from media where id = ?").get(oldOrphan.id),
|
|
190
200
|
).toBeUndefined();
|
|
191
201
|
expect(storage.files.has(oldStorageKey)).toBe(false);
|
|
202
|
+
expect([...storage.files.keys()].some((k) => k.startsWith("trash/"))).toBe(
|
|
203
|
+
true,
|
|
204
|
+
);
|
|
205
|
+
expect(
|
|
206
|
+
sqlite
|
|
207
|
+
.prepare("select id from storage_purge where original_key = ?")
|
|
208
|
+
.get(oldStorageKey),
|
|
209
|
+
).toBeDefined();
|
|
192
210
|
|
|
193
211
|
// Fresh orphan: untouched.
|
|
194
212
|
expect(
|
|
@@ -195,7 +195,8 @@ settingsApiRoutes.post("/avatar", requireAuthApi(), async (c) => {
|
|
|
195
195
|
|
|
196
196
|
// Remove site avatar (requires auth)
|
|
197
197
|
settingsApiRoutes.delete("/avatar", requireAuthApi(), async (c) => {
|
|
198
|
-
await c.var.services.settings.removeAvatar(
|
|
198
|
+
await c.var.services.settings.removeAvatar({
|
|
199
|
+
storage: c.var.storage,
|
|
199
200
|
media: c.var.services.media,
|
|
200
201
|
storageProvider: c.var.appConfig.storageDriver,
|
|
201
202
|
});
|
|
@@ -79,6 +79,20 @@ describe("Setup bootstrap logic", () => {
|
|
|
79
79
|
);
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
+
it("pins the dashboard language to the detected catalog locale", async () => {
|
|
83
|
+
await runSetupBootstrap(services, { siteLanguage: "zh-TW" });
|
|
84
|
+
|
|
85
|
+
const rows = await services.db.select().from(settings);
|
|
86
|
+
// Content language stays verbatim; the dashboard locale is the resolved
|
|
87
|
+
// catalog (zh-Hant) so it is stable if content language later changes.
|
|
88
|
+
expect(rows.find((row) => row.key === "SITE_LANGUAGE")?.value).toBe(
|
|
89
|
+
"zh-TW",
|
|
90
|
+
);
|
|
91
|
+
expect(rows.find((row) => row.key === "DASHBOARD_LANGUAGE")?.value).toBe(
|
|
92
|
+
"zh-Hant",
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
82
96
|
it("is idempotent when default navigation already exists", async () => {
|
|
83
97
|
const timestamp = Math.floor(Date.now() / 1000);
|
|
84
98
|
await services.db.run(sql`
|
|
@@ -215,29 +215,47 @@ describe("Settings - Avatar Upload Logic", () => {
|
|
|
215
215
|
expect(await settingsService.get("SITE_FAVICON_VERSION")).toBeNull();
|
|
216
216
|
});
|
|
217
217
|
|
|
218
|
-
it("
|
|
218
|
+
it("removes the apple-touch-icon media row and retires its object", async () => {
|
|
219
219
|
const storage = createMockStorage();
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
);
|
|
220
|
+
const appleTouchKey = `media/${DEFAULT_TEST_SITE_ID}/assets/favicon/apple-touch-icon.png`;
|
|
221
|
+
const media = await mediaService.create({
|
|
222
|
+
filename: "apple-touch-icon.png",
|
|
223
|
+
originalName: "apple-touch-icon.png",
|
|
224
|
+
mimeType: "image/png",
|
|
225
|
+
size: 1234,
|
|
226
|
+
storageKey: appleTouchKey,
|
|
227
|
+
provider: "r2",
|
|
228
|
+
});
|
|
229
|
+
await settingsService.set("SITE_FAVICON_APPLE_TOUCH", appleTouchKey);
|
|
230
|
+
|
|
231
|
+
await settingsService.removeAvatar({
|
|
232
|
+
storage,
|
|
233
|
+
media: mediaService,
|
|
234
|
+
storageProvider: "r2",
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Row removed and setting cleared; the object is retired via storage
|
|
238
|
+
// (this mock has no server-side copy, so it's deleted outright).
|
|
239
|
+
expect(await mediaService.getById(media.id)).toBeNull();
|
|
240
|
+
expect(await settingsService.get("SITE_FAVICON_APPLE_TOUCH")).toBeNull();
|
|
241
|
+
expect(storage.delete).toHaveBeenCalledWith(appleTouchKey);
|
|
230
242
|
});
|
|
231
243
|
|
|
232
|
-
it("
|
|
233
|
-
|
|
244
|
+
it("is a no-op for media when no apple-touch-icon key exists", async () => {
|
|
245
|
+
await settingsService.set(
|
|
246
|
+
"SITE_AVATAR",
|
|
247
|
+
`media/${DEFAULT_TEST_SITE_ID}/assets/avatar/some-id.png`,
|
|
248
|
+
);
|
|
234
249
|
|
|
235
|
-
await settingsService.removeAvatar(
|
|
250
|
+
await settingsService.removeAvatar({
|
|
251
|
+
media: mediaService,
|
|
252
|
+
storageProvider: "r2",
|
|
253
|
+
});
|
|
236
254
|
|
|
237
|
-
expect(
|
|
255
|
+
expect(await settingsService.get("SITE_AVATAR")).toBeNull();
|
|
238
256
|
});
|
|
239
257
|
|
|
240
|
-
it("
|
|
258
|
+
it("clears settings even without a media service", async () => {
|
|
241
259
|
await settingsService.set(
|
|
242
260
|
"SITE_AVATAR",
|
|
243
261
|
`media/${DEFAULT_TEST_SITE_ID}/assets/avatar/some-id.png`,
|
|
@@ -247,7 +265,7 @@ describe("Settings - Avatar Upload Logic", () => {
|
|
|
247
265
|
`media/${DEFAULT_TEST_SITE_ID}/assets/favicon/apple-touch-icon.png`,
|
|
248
266
|
);
|
|
249
267
|
|
|
250
|
-
await settingsService.removeAvatar(
|
|
268
|
+
await settingsService.removeAvatar();
|
|
251
269
|
|
|
252
270
|
expect(await settingsService.get("SITE_AVATAR")).toBeNull();
|
|
253
271
|
expect(await settingsService.get("SITE_FAVICON_APPLE_TOUCH")).toBeNull();
|
|
@@ -13,7 +13,7 @@ import { z } from "zod";
|
|
|
13
13
|
import type { Bindings } from "../../types.js";
|
|
14
14
|
import type { AppVariables } from "../../types/app-context.js";
|
|
15
15
|
import { sse, dsRedirect, dsToast } from "../../lib/sse.js";
|
|
16
|
-
import { getI18n } from "../../i18n/index.js";
|
|
16
|
+
import { getI18n, isLocale, resolveCatalogLocale } from "../../i18n/index.js";
|
|
17
17
|
import { renderPublicPage } from "../../lib/render.js";
|
|
18
18
|
import { getNavigationData } from "../../lib/navigation.js";
|
|
19
19
|
import { buildPageTitle } from "../../lib/page-title.js";
|
|
@@ -94,6 +94,9 @@ export const settingsRoutes = new Hono<Env>();
|
|
|
94
94
|
|
|
95
95
|
const UpdateLocaleSettingsSchema = z.object({
|
|
96
96
|
siteLanguage: z.string(),
|
|
97
|
+
// Optional for back-compat: absent = leave the dashboard language untouched,
|
|
98
|
+
// "" = clear it (follow content language).
|
|
99
|
+
dashboardLanguage: z.string().optional(),
|
|
97
100
|
cjkSerifFont: z.string(),
|
|
98
101
|
timeZone: z.string(),
|
|
99
102
|
});
|
|
@@ -318,6 +321,11 @@ settingsRoutes.get("/general", async (c) => {
|
|
|
318
321
|
siteName={dbSiteName || ""}
|
|
319
322
|
siteDescription={dbSiteDescription || ""}
|
|
320
323
|
siteLanguage={appConfig.siteLanguage}
|
|
324
|
+
dashboardLanguage={
|
|
325
|
+
isLocale(appConfig.dashboardLanguage)
|
|
326
|
+
? appConfig.dashboardLanguage
|
|
327
|
+
: resolveCatalogLocale(appConfig.siteLanguage)
|
|
328
|
+
}
|
|
321
329
|
cjkSerifFont={appConfig.cjkSerifFont}
|
|
322
330
|
siteNameFallback={appConfig.fallbacks.siteName}
|
|
323
331
|
siteDescriptionFallback={appConfig.fallbacks.siteDescription}
|
|
@@ -427,6 +435,7 @@ settingsRoutes.post("/general/language-time", async (c) => {
|
|
|
427
435
|
await c.var.services.settings.updateLocaleSettings(body, {
|
|
428
436
|
oldLanguage: c.var.appConfig.siteLanguage,
|
|
429
437
|
oldCjkSerifFont: c.var.appConfig.cjkSerifFont,
|
|
438
|
+
oldDashboardLanguage: c.var.appConfig.dashboardLanguage,
|
|
430
439
|
});
|
|
431
440
|
|
|
432
441
|
const wantsJson = c.req.header("accept")?.includes("application/json");
|
|
@@ -651,7 +660,11 @@ settingsRoutes.post("/avatar", async (c) => {
|
|
|
651
660
|
});
|
|
652
661
|
|
|
653
662
|
settingsRoutes.post("/avatar/remove", async (c) => {
|
|
654
|
-
await c.var.services.settings.removeAvatar(
|
|
663
|
+
await c.var.services.settings.removeAvatar({
|
|
664
|
+
storage: c.var.storage,
|
|
665
|
+
media: c.var.services.media,
|
|
666
|
+
storageProvider: c.var.appConfig.storageDriver,
|
|
667
|
+
});
|
|
655
668
|
try {
|
|
656
669
|
await syncHostedControlPlaneSiteAvatar({
|
|
657
670
|
appConfig: c.var.appConfig,
|