From 21cf5e1df725d34dda22322c8a7d8e9b3ff243cd Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 3 Jan 2026 20:29:54 +0200 Subject: [PATCH 01/23] chore(client/pdf): use custom spaced update hook --- apps/client/src/widgets/react/hooks.tsx | 65 +++++++++++++++++++ .../src/widgets/type_widgets/file/Pdf.tsx | 29 ++++++--- 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 7fade29b9..1063def55 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -170,6 +170,71 @@ export function useEditorSpacedUpdate({ note, noteType, noteContext, getData, on return spacedUpdate; } +export function useBlobEditorSpacedUpdate({ note, noteType, noteContext, getData, onContentChange, dataSaved, updateInterval }: { + noteType: NoteType; + note: FNote, + noteContext: NoteContext | null | undefined, + getData: () => Promise | Blob | undefined, + onContentChange: (newBlob: FBlob) => void, + dataSaved?: (savedData: Blob) => void, + updateInterval?: number; +}) { + const parentComponent = useContext(ParentComponent); + const blob = useNoteBlob(note, parentComponent?.componentId); + + const callback = useMemo(() => { + return async () => { + const data = await getData(); + + // for read only notes + if (data === undefined || note.type !== noteType) return; + + protected_session_holder.touchProtectedSessionIfNecessary(note); + await server.upload(`notes/${note.noteId}/file`, new File([ data ], note.title, { type: note.mime }), parentComponent?.componentId); + dataSaved?.(data); + }; + }, [ note, getData, dataSaved, noteType, parentComponent ]); + const stateCallback = useCallback((state) => { + noteContext?.setContextData("saveState", { + state + }); + }, [ noteContext ]); + const spacedUpdate = useSpacedUpdate(callback, updateInterval, stateCallback); + + // React to note/blob changes. + useEffect(() => { + if (!blob) return; + spacedUpdate.allowUpdateWithoutChange(() => onContentChange(blob)); + }, [ blob ]); + + // React to update interval changes. + useEffect(() => { + if (!updateInterval) return; + spacedUpdate.setUpdateInterval(updateInterval); + }, [ updateInterval ]); + + // Save if needed upon switching tabs. + useTriliumEvent("beforeNoteSwitch", async ({ noteContext: eventNoteContext }) => { + if (eventNoteContext.ntxId !== noteContext?.ntxId) return; + await spacedUpdate.updateNowIfNecessary(); + }); + + // Save if needed upon tab closing. + useTriliumEvent("beforeNoteContextRemove", async ({ ntxIds }) => { + if (!noteContext?.ntxId || !ntxIds.includes(noteContext.ntxId)) return; + await spacedUpdate.updateNowIfNecessary(); + }); + + // Save if needed upon window/browser closing. + useEffect(() => { + const listener = () => spacedUpdate.isAllSavedAndTriggerUpdate(); + appContext.addBeforeUnloadListener(listener); + return () => appContext.removeBeforeUnloadListener(listener); + }, []); + + return spacedUpdate; +} + export function useNoteSavedData(noteId: string | undefined) { return useSyncExternalStore( (cb) => noteId ? noteSavedDataStore.subscribe(noteId, cb) : () => {}, diff --git a/apps/client/src/widgets/type_widgets/file/Pdf.tsx b/apps/client/src/widgets/type_widgets/file/Pdf.tsx index ad4bb8f56..cbd34eef8 100644 --- a/apps/client/src/widgets/type_widgets/file/Pdf.tsx +++ b/apps/client/src/widgets/type_widgets/file/Pdf.tsx @@ -4,9 +4,8 @@ import appContext from "../../../components/app_context"; import type NoteContext from "../../../components/note_context"; import FBlob from "../../../entities/fblob"; import FNote from "../../../entities/fnote"; -import server from "../../../services/server"; import { useViewModeConfig } from "../../collections/NoteList"; -import { useTriliumEvent } from "../../react/hooks"; +import { useBlobEditorSpacedUpdate, useTriliumEvent } from "../../react/hooks"; import PdfViewer from "./PdfViewer"; export default function PdfPreview({ note, blob, componentId, noteContext }: { @@ -17,13 +16,30 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: { }) { const iframeRef = useRef(null); const historyConfig = useViewModeConfig(note, "pdfHistory"); + const dataRef = useRef(new Blob()); + + const spacedUpdate = useBlobEditorSpacedUpdate({ + note, + noteType: "file", + noteContext, + getData() { + return dataRef.current; + }, + onContentChange() { + if (iframeRef.current?.contentWindow) { + iframeRef.current.contentWindow.location.reload(); + } + } + }); useEffect(() => { function handleMessage(event: PdfMessageEvent) { if (event.data?.type === "pdfjs-viewer-document-modified") { const blob = new Blob([event.data.data as Uint8Array], { type: note.mime }); if (event.data.noteId === note.noteId && event.data.ntxId === noteContext.ntxId) { - server.upload(`notes/${note.noteId}/file`, new File([blob], note.title, { type: note.mime }), componentId); + dataRef.current = blob; + spacedUpdate.resetUpdateTimer(); + spacedUpdate.scheduleUpdate(); } } @@ -138,13 +154,6 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: { }; }, [ note, historyConfig, componentId, blob, noteContext ]); - // Refresh when blob changes. - useEffect(() => { - if (iframeRef.current?.contentWindow) { - iframeRef.current.contentWindow.location.reload(); - } - }, [ blob ]); - useTriliumEvent("customDownload", ({ ntxId }) => { if (ntxId !== noteContext.ntxId) return; iframeRef.current?.contentWindow?.postMessage({ From 69511134e5d975761df5c974a1d319aadd9859b2 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 3 Jan 2026 20:54:28 +0200 Subject: [PATCH 02/23] refactor(client/pdf): handle blob request on client side --- apps/client/src/types-pdfjs.d.ts | 5 +++ apps/client/src/widgets/react/hooks.tsx | 2 +- .../src/widgets/type_widgets/file/Pdf.tsx | 20 ++++++++-- packages/pdfjs-viewer/src/custom.ts | 40 +++++++++++-------- 4 files changed, 45 insertions(+), 22 deletions(-) diff --git a/apps/client/src/types-pdfjs.d.ts b/apps/client/src/types-pdfjs.d.ts index 12756778e..3a4b3f16d 100644 --- a/apps/client/src/types-pdfjs.d.ts +++ b/apps/client/src/types-pdfjs.d.ts @@ -45,6 +45,10 @@ interface WithContext { interface PdfDocumentModifiedMessage extends WithContext { type: "pdfjs-viewer-document-modified"; +} + +interface PdfDocumentBlobResultMessage extends WithContext { + type: "pdfjs-viewer-blob"; data: Uint8Array; } @@ -113,4 +117,5 @@ type PdfMessageEvent = MessageEvent< | PdfViewerThumbnailMessage | PdfViewerAttachmentsMessage | PdfViewerLayersMessage + | PdfDocumentBlobResultMessage >; diff --git a/apps/client/src/widgets/react/hooks.tsx b/apps/client/src/widgets/react/hooks.tsx index 1063def55..17efcc275 100644 --- a/apps/client/src/widgets/react/hooks.tsx +++ b/apps/client/src/widgets/react/hooks.tsx @@ -174,7 +174,7 @@ export function useBlobEditorSpacedUpdate({ note, noteType, noteContext, getData noteType: NoteType; note: FNote, noteContext: NoteContext | null | undefined, - getData: () => Promise | Blob | undefined, + getData: () => Promise | Blob | undefined, onContentChange: (newBlob: FBlob) => void, dataSaved?: (savedData: Blob) => void, updateInterval?: number; diff --git a/apps/client/src/widgets/type_widgets/file/Pdf.tsx b/apps/client/src/widgets/type_widgets/file/Pdf.tsx index cbd34eef8..60d6f2059 100644 --- a/apps/client/src/widgets/type_widgets/file/Pdf.tsx +++ b/apps/client/src/widgets/type_widgets/file/Pdf.tsx @@ -16,14 +16,28 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: { }) { const iframeRef = useRef(null); const historyConfig = useViewModeConfig(note, "pdfHistory"); - const dataRef = useRef(new Blob()); const spacedUpdate = useBlobEditorSpacedUpdate({ note, noteType: "file", noteContext, getData() { - return dataRef.current; + if (!iframeRef.current?.contentWindow) return undefined; + + return new Promise((resolve) => { + const onMessageReceived = (event: PdfMessageEvent) => { + if (event.data.type !== "pdfjs-viewer-blob") return; + if (event.data.noteId !== note.noteId || event.data.ntxId !== noteContext.ntxId) return; + const blob = new Blob([event.data.data as Uint8Array], { type: note.mime }); + window.removeEventListener("message", onMessageReceived); + resolve(blob); + }; + + window.addEventListener("message", onMessageReceived); + iframeRef.current?.contentWindow?.postMessage({ + type: "trilium-request-blob", + }); + }); }, onContentChange() { if (iframeRef.current?.contentWindow) { @@ -35,9 +49,7 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: { useEffect(() => { function handleMessage(event: PdfMessageEvent) { if (event.data?.type === "pdfjs-viewer-document-modified") { - const blob = new Blob([event.data.data as Uint8Array], { type: note.mime }); if (event.data.noteId === note.noteId && event.data.ntxId === noteContext.ntxId) { - dataRef.current = blob; spacedUpdate.resetUpdateTimer(); spacedUpdate.scheduleUpdate(); } diff --git a/packages/pdfjs-viewer/src/custom.ts b/packages/pdfjs-viewer/src/custom.ts index c58e4104b..d78560122 100644 --- a/packages/pdfjs-viewer/src/custom.ts +++ b/packages/pdfjs-viewer/src/custom.ts @@ -55,35 +55,41 @@ function getCustomAppOptions(urlParams: URLSearchParams) { function manageSave() { const app = window.PDFViewerApplication; const storage = app.pdfDocument.annotationStorage; - let timeout = null; - function debouncedSave() { - if (timeout) { - clearTimeout(timeout); - } - timeout = setTimeout(async () => { - if (!storage) return; + function onChange() { + if (!storage) return; + window.parent.postMessage({ + type: "pdfjs-viewer-document-modified", + ntxId: window.TRILIUM_NTX_ID, + noteId: window.TRILIUM_NOTE_ID + } satisfies PdfDocumentModifiedMessage, window.location.origin); + storage.resetModified(); + } + + window.addEventListener("message", async (event) => { + if (event.origin !== window.location.origin) return; + + if (event.data?.type === "trilium-request-blob") { + const app = window.PDFViewerApplication; const data = await app.pdfDocument.saveDocument(); window.parent.postMessage({ - type: "pdfjs-viewer-document-modified", + type: "pdfjs-viewer-blob", data, ntxId: window.TRILIUM_NTX_ID, noteId: window.TRILIUM_NOTE_ID - } satisfies PdfDocumentModifiedMessage, window.location.origin); - storage.resetModified(); - timeout = null; - }, 2_000); - } + } satisfies PdfDocumentBlobResultMessage, window.location.origin) + } + }); - app.pdfDocument.annotationStorage.onSetModified = debouncedSave; // works great for most cases, including forms. - app.eventBus.on("annotationeditorcommit", debouncedSave); - app.eventBus.on("annotationeditorparamschanged", debouncedSave); + app.pdfDocument.annotationStorage.onSetModified = onChange; // works great for most cases, including forms. + app.eventBus.on("annotationeditorcommit", onChange); + app.eventBus.on("annotationeditorparamschanged", onChange); app.eventBus.on("annotationeditorstateschanged", evt => { // needed for detecting when annotations are moved around. const { activeEditorId } = evt; // When activeEditorId becomes null, an editor was just committed if (activeEditorId === null) { - debouncedSave(); + onChange(); } }); } From 2f7448dbd452f7291e0f64154d0ae1c56cf3258a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 4 Jan 2026 02:13:34 +0000 Subject: [PATCH 03/23] chore(deps): update dependency webdriverio to v9.23.0 --- packages/ckeditor5-admonition/package.json | 2 +- packages/ckeditor5-footnotes/package.json | 2 +- .../ckeditor5-keyboard-marker/package.json | 2 +- packages/ckeditor5-math/package.json | 2 +- packages/ckeditor5-mermaid/package.json | 2 +- pnpm-lock.yaml | 68 ++++++++++--------- 6 files changed, 41 insertions(+), 37 deletions(-) diff --git a/packages/ckeditor5-admonition/package.json b/packages/ckeditor5-admonition/package.json index 3015fb2a7..0e647466b 100644 --- a/packages/ckeditor5-admonition/package.json +++ b/packages/ckeditor5-admonition/package.json @@ -39,7 +39,7 @@ "typescript": "5.9.3", "vite-plugin-svgo": "~2.0.0", "vitest": "4.0.16", - "webdriverio": "9.22.0" + "webdriverio": "9.23.0" }, "peerDependencies": { "ckeditor5": "47.3.0" diff --git a/packages/ckeditor5-footnotes/package.json b/packages/ckeditor5-footnotes/package.json index 29a382af3..bd5fea55e 100644 --- a/packages/ckeditor5-footnotes/package.json +++ b/packages/ckeditor5-footnotes/package.json @@ -40,7 +40,7 @@ "typescript": "5.9.3", "vite-plugin-svgo": "~2.0.0", "vitest": "4.0.16", - "webdriverio": "9.22.0" + "webdriverio": "9.23.0" }, "peerDependencies": { "ckeditor5": "47.3.0" diff --git a/packages/ckeditor5-keyboard-marker/package.json b/packages/ckeditor5-keyboard-marker/package.json index 516bf8849..64c958ad8 100644 --- a/packages/ckeditor5-keyboard-marker/package.json +++ b/packages/ckeditor5-keyboard-marker/package.json @@ -42,7 +42,7 @@ "typescript": "5.9.3", "vite-plugin-svgo": "~2.0.0", "vitest": "4.0.16", - "webdriverio": "9.22.0" + "webdriverio": "9.23.0" }, "peerDependencies": { "ckeditor5": "47.3.0" diff --git a/packages/ckeditor5-math/package.json b/packages/ckeditor5-math/package.json index 4dbfdfaeb..2992bbdae 100644 --- a/packages/ckeditor5-math/package.json +++ b/packages/ckeditor5-math/package.json @@ -42,7 +42,7 @@ "typescript": "5.9.3", "vite-plugin-svgo": "~2.0.0", "vitest": "4.0.16", - "webdriverio": "9.22.0" + "webdriverio": "9.23.0" }, "peerDependencies": { "ckeditor5": "47.3.0" diff --git a/packages/ckeditor5-mermaid/package.json b/packages/ckeditor5-mermaid/package.json index c59a583f5..b06fa3447 100644 --- a/packages/ckeditor5-mermaid/package.json +++ b/packages/ckeditor5-mermaid/package.json @@ -42,7 +42,7 @@ "typescript": "5.9.3", "vite-plugin-svgo": "~2.0.0", "vitest": "4.0.16", - "webdriverio": "9.22.0" + "webdriverio": "9.23.0" }, "peerDependencies": { "ckeditor5": "47.3.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d61761b3e..31fc4bd62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,7 +54,7 @@ importers: version: 24.10.4 '@vitest/browser-webdriverio': specifier: 4.0.16 - version: 4.0.16(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.16)(webdriverio@9.22.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) + version: 4.0.16(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.16)(webdriverio@9.23.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) '@vitest/coverage-v8': specifier: 4.0.16 version: 4.0.16(@vitest/browser@4.0.16(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.16))(vitest@4.0.16) @@ -945,8 +945,8 @@ importers: specifier: 4.0.16 version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(@vitest/browser-webdriverio@4.0.16)(@vitest/ui@4.0.16)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: - specifier: 9.22.0 - version: 9.22.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.23.0 + version: 9.23.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/ckeditor5-footnotes: devDependencies: @@ -1005,8 +1005,8 @@ importers: specifier: 4.0.16 version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(@vitest/browser-webdriverio@4.0.16)(@vitest/ui@4.0.16)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: - specifier: 9.22.0 - version: 9.22.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.23.0 + version: 9.23.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/ckeditor5-keyboard-marker: devDependencies: @@ -1065,8 +1065,8 @@ importers: specifier: 4.0.16 version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(@vitest/browser-webdriverio@4.0.16)(@vitest/ui@4.0.16)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: - specifier: 9.22.0 - version: 9.22.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.23.0 + version: 9.23.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/ckeditor5-math: dependencies: @@ -1129,8 +1129,8 @@ importers: specifier: 4.0.16 version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(@vitest/browser-webdriverio@4.0.16)(@vitest/ui@4.0.16)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: - specifier: 9.22.0 - version: 9.22.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.23.0 + version: 9.23.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/ckeditor5-mermaid: dependencies: @@ -1196,8 +1196,8 @@ importers: specifier: 4.0.16 version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(@vitest/browser-webdriverio@4.0.16)(@vitest/ui@4.0.16)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) webdriverio: - specifier: 9.22.0 - version: 9.22.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) + specifier: 9.23.0 + version: 9.23.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) packages/codemirror: dependencies: @@ -6065,8 +6065,8 @@ packages: '@vue/shared@3.5.14': resolution: {integrity: sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ==} - '@wdio/config@9.22.0': - resolution: {integrity: sha512-SQsTSZowEI+whPlwPLsX9ICr6BiG39NLmzED7OWfaowribQ0XylRhoWodcRu6cB/ZCzminZajBUG5XgarNWnRw==} + '@wdio/config@9.23.0': + resolution: {integrity: sha512-hhtngUG2uCxYmScSEor+k22EVlsTW3ARXgke8NPVeQA4p1+GC2CvRZi4P7nmhRTZubgLrENYYsveFcYR+1UXhQ==} engines: {node: '>=18.20.0'} '@wdio/logger@9.18.0': @@ -6084,8 +6084,8 @@ packages: resolution: {integrity: sha512-zMmAtse2UMCSOW76mvK3OejauAdcFGuKopNRH7crI0gwKTZtvV89yXWRziz9cVXpFgfmJCjf9edxKFWdhuF5yw==} engines: {node: '>=18.20.0'} - '@wdio/utils@9.22.0': - resolution: {integrity: sha512-5j2nn2bBjj41wxXsVT43sUMOKR0qiKNDRG1UcKQ6NkfsWFObSehMAS0a9ZZu//+ooTxRkwHjvLdQrXIrPnTLzg==} + '@wdio/utils@9.23.0': + resolution: {integrity: sha512-WhXuVSxEvPw/i34bL1aCHAOi+4g29kRkIMyBShNSxH+Shxh2G91RJYsXm4IAiPMGcC4H6G8T2VcbZ32qnGPm5Q==} engines: {node: '>=18.20.0'} '@webassemblyjs/ast@1.14.1': @@ -14150,12 +14150,12 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} - webdriver@9.22.0: - resolution: {integrity: sha512-jf4irPhIJAssrF3mqUrBZGZnzjRfM86Q24ePUOgFKWI04LtdvRsnc9SsWU05mrN/a6pTJzGps6GsvLpNhvcalg==} + webdriver@9.23.0: + resolution: {integrity: sha512-XkZOhjoBOY7maKI3BhDF2rNiDne4wBD6Gw6VUnt4X9b7j9NtfzcCrThBlT0hnA8W77bWNtMRCSpw9Ajy08HqKg==} engines: {node: '>=18.20.0'} - webdriverio@9.22.0: - resolution: {integrity: sha512-sqXZG11hRM9KjqioVPcXCPLIcdJprNM9e+B6JlyacN6ImgC64MQbgs0vtCDLVsSIX7vg+x771lrS/VxXxqlkJw==} + webdriverio@9.23.0: + resolution: {integrity: sha512-Y5y4jpwHvuduUfup+gXTuCU6AROn/k6qOba3st0laFluKHY+q5SHOpQAJdS8acYLwE8caDQ2dXJhmXyxuJrm0Q==} engines: {node: '>=18.20.0'} peerDependencies: puppeteer-core: '>=22.x || <=24.x' @@ -15361,6 +15361,8 @@ snapshots: '@ckeditor/ckeditor5-core': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': dependencies: @@ -16091,6 +16093,8 @@ snapshots: '@ckeditor/ckeditor5-ui': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0 ckeditor5: 47.3.0 + transitivePeerDependencies: + - supports-color '@ckeditor/ckeditor5-restricted-editing@47.3.0': dependencies: @@ -20865,11 +20869,11 @@ snapshots: - bufferutil - utf-8-validate - '@vitest/browser-webdriverio@4.0.16(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.16)(webdriverio@9.22.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))': + '@vitest/browser-webdriverio@4.0.16(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.16)(webdriverio@9.23.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))': dependencies: '@vitest/browser': 4.0.16(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.16) vitest: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(@vitest/browser-webdriverio@4.0.16)(@vitest/ui@4.0.16)(happy-dom@20.0.11)(jiti@2.6.1)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5))(less@4.1.3)(lightningcss@1.30.1)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1) - webdriverio: 9.22.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) + webdriverio: 9.23.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bufferutil - msw @@ -21027,11 +21031,11 @@ snapshots: '@vue/shared@3.5.14': {} - '@wdio/config@9.22.0': + '@wdio/config@9.23.0': dependencies: '@wdio/logger': 9.18.0 '@wdio/types': 9.20.0 - '@wdio/utils': 9.22.0 + '@wdio/utils': 9.23.0 deepmerge-ts: 7.1.5 glob: 10.4.5 import-meta-resolve: 4.2.0 @@ -21057,7 +21061,7 @@ snapshots: dependencies: '@types/node': 20.19.25 - '@wdio/utils@9.22.0': + '@wdio/utils@9.23.0': dependencies: '@puppeteer/browsers': 2.10.10 '@wdio/logger': 9.18.0 @@ -30844,7 +30848,7 @@ snapshots: optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 24.10.4 - '@vitest/browser-webdriverio': 4.0.16(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.16)(webdriverio@9.22.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) + '@vitest/browser-webdriverio': 4.0.16(bufferutil@4.0.9)(msw@2.7.5(@types/node@24.10.4)(typescript@5.9.3))(utf-8-validate@6.0.5)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(less@4.1.3)(lightningcss@1.30.1)(sass-embedded@1.91.0)(sass@1.91.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1))(vitest@4.0.16)(webdriverio@9.23.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)) '@vitest/ui': 4.0.16(vitest@4.0.16) happy-dom: 20.0.11 jsdom: 26.1.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) @@ -30934,15 +30938,15 @@ snapshots: web-streams-polyfill@3.3.3: {} - webdriver@9.22.0(bufferutil@4.0.9)(utf-8-validate@6.0.5): + webdriver@9.23.0(bufferutil@4.0.9)(utf-8-validate@6.0.5): dependencies: '@types/node': 20.19.25 '@types/ws': 8.18.1 - '@wdio/config': 9.22.0 + '@wdio/config': 9.23.0 '@wdio/logger': 9.18.0 '@wdio/protocols': 9.16.2 '@wdio/types': 9.20.0 - '@wdio/utils': 9.22.0 + '@wdio/utils': 9.23.0 deepmerge-ts: 7.1.5 https-proxy-agent: 7.0.6 undici: 6.21.3 @@ -30953,16 +30957,16 @@ snapshots: - supports-color - utf-8-validate - webdriverio@9.22.0(bufferutil@4.0.9)(utf-8-validate@6.0.5): + webdriverio@9.23.0(bufferutil@4.0.9)(utf-8-validate@6.0.5): dependencies: '@types/node': 20.19.25 '@types/sinonjs__fake-timers': 8.1.5 - '@wdio/config': 9.22.0 + '@wdio/config': 9.23.0 '@wdio/logger': 9.18.0 '@wdio/protocols': 9.16.2 '@wdio/repl': 9.16.2 '@wdio/types': 9.20.0 - '@wdio/utils': 9.22.0 + '@wdio/utils': 9.23.0 archiver: 7.0.1 aria-query: 5.3.2 cheerio: 1.1.2 @@ -30979,7 +30983,7 @@ snapshots: rgb2hex: 0.2.5 serialize-error: 12.0.0 urlpattern-polyfill: 10.1.0 - webdriver: 9.22.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) + webdriver: 9.23.0(bufferutil@4.0.9)(utf-8-validate@6.0.5) transitivePeerDependencies: - bare-buffer - bufferutil From b23252d04605a311013339b797c9b61c4700f044 Mon Sep 17 00:00:00 2001 From: SiriusXT <1160925501@qq.com> Date: Sun, 4 Jan 2026 10:25:43 +0800 Subject: [PATCH 04/23] fix(note_grid): the note grid cannot open the context menu --- .../src/widgets/collections/legacy/ListOrGridView.css | 6 ++++++ .../src/widgets/collections/legacy/ListOrGridView.tsx | 11 +---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/apps/client/src/widgets/collections/legacy/ListOrGridView.css b/apps/client/src/widgets/collections/legacy/ListOrGridView.css index c6b21c0b1..1bfa389e5 100644 --- a/apps/client/src/widgets/collections/legacy/ListOrGridView.css +++ b/apps/client/src/widgets/collections/legacy/ListOrGridView.css @@ -142,4 +142,10 @@ border: 1px solid var(--main-border-color); background: var(--more-accented-background-color); } + +.note-list.grid-view .note-path { + margin-left: 0.5em; + vertical-align: middle; + opacity: 0.5; +} /* #endregion */ diff --git a/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx b/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx index 8180e6d65..136a8f225 100644 --- a/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx +++ b/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx @@ -103,16 +103,7 @@ function ListNoteCard({ note, parentNote, highlightedTokens, currentLevel, expan } function GridNoteCard({ note, parentNote, highlightedTokens }: { note: FNote, parentNote: FNote, highlightedTokens: string[] | null | undefined }) { - const titleRef = useRef(null); - const [ noteTitle, setNoteTitle ] = useState(); const notePath = getNotePath(parentNote, note); - const highlightSearch = useImperativeSearchHighlighlighting(highlightedTokens); - - useEffect(() => { - tree.getNoteTitle(note.noteId, parentNote.noteId).then(setNoteTitle); - }, [ note ]); - - useEffect(() => highlightSearch(titleRef.current), [ noteTitle, highlightedTokens ]); return (
- {noteTitle} +
Date: Sun, 4 Jan 2026 10:48:45 +0800 Subject: [PATCH 05/23] chore(note_grid): remove unused tree import --- apps/client/src/widgets/collections/legacy/ListOrGridView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx b/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx index 136a8f225..5b0d47d97 100644 --- a/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx +++ b/apps/client/src/widgets/collections/legacy/ListOrGridView.tsx @@ -7,7 +7,6 @@ import attribute_renderer from "../../../services/attribute_renderer"; import content_renderer from "../../../services/content_renderer"; import { t } from "../../../services/i18n"; import link from "../../../services/link"; -import tree from "../../../services/tree"; import { useImperativeSearchHighlighlighting, useNoteLabel, useNoteLabelBoolean } from "../../react/hooks"; import Icon from "../../react/Icon"; import NoteLink from "../../react/NoteLink"; From e485b75a448ef2f1b064c74d33b4b140ee6bfaf5 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 4 Jan 2026 17:06:41 +0200 Subject: [PATCH 06/23] fix(pdfjs): saves as soon as document is opened --- packages/pdfjs-viewer/src/custom.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/pdfjs-viewer/src/custom.ts b/packages/pdfjs-viewer/src/custom.ts index d78560122..8ec01752c 100644 --- a/packages/pdfjs-viewer/src/custom.ts +++ b/packages/pdfjs-viewer/src/custom.ts @@ -81,16 +81,11 @@ function manageSave() { } }); - app.pdfDocument.annotationStorage.onSetModified = onChange; // works great for most cases, including forms. - app.eventBus.on("annotationeditorcommit", onChange); - app.eventBus.on("annotationeditorparamschanged", onChange); - app.eventBus.on("annotationeditorstateschanged", evt => { // needed for detecting when annotations are moved around. - const { activeEditorId } = evt; - - // When activeEditorId becomes null, an editor was just committed - if (activeEditorId === null) { - onChange(); - } + app.pdfDocument.annotationStorage.onSetModified = () => { + onChange(); + }; // works great for most cases, including forms. + app.eventBus.on("switchannotationeditorparams", () => { + onChange(); }); } From 23f7dc63b88abd64724709844aaf7271136e1ce9 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 4 Jan 2026 17:11:16 +0200 Subject: [PATCH 07/23] feat(pdfjs): enable editing features only if in main editor --- .../src/widgets/type_widgets/file/Pdf.tsx | 1 + .../widgets/type_widgets/file/PdfViewer.tsx | 8 ++++-- packages/pdfjs-viewer/src/custom.ts | 27 +++++++++++-------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/file/Pdf.tsx b/apps/client/src/widgets/type_widgets/file/Pdf.tsx index 60d6f2059..d7a09b35a 100644 --- a/apps/client/src/widgets/type_widgets/file/Pdf.tsx +++ b/apps/client/src/widgets/type_widgets/file/Pdf.tsx @@ -192,6 +192,7 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: { }); } }} + editable /> ); } diff --git a/apps/client/src/widgets/type_widgets/file/PdfViewer.tsx b/apps/client/src/widgets/type_widgets/file/PdfViewer.tsx index 82bc4a2b9..7e6870dd4 100644 --- a/apps/client/src/widgets/type_widgets/file/PdfViewer.tsx +++ b/apps/client/src/widgets/type_widgets/file/PdfViewer.tsx @@ -15,12 +15,16 @@ interface PdfViewerProps extends Pick, "tabInd /** Note: URLs are relative to /pdfjs/web. */ pdfUrl: string; onLoad?(): void; + /** + * If set, enables editable mode which includes persistence of user settings, annotations as well as specific features such as sending table of contents data for the sidebar. + */ + editable?: boolean; } /** * Reusable component displaying a PDF. The PDF needs to be provided via a URL. */ -export default function PdfViewer({ iframeRef: externalIframeRef, pdfUrl, onLoad }: PdfViewerProps) { +export default function PdfViewer({ iframeRef: externalIframeRef, pdfUrl, onLoad, editable }: PdfViewerProps) { const iframeRef = useSyncedRef(externalIframeRef, null); const [ locale ] = useTriliumOption("locale"); const [ newLayout ] = useTriliumOptionBool("newLayout"); @@ -30,7 +34,7 @@ export default function PdfViewer({ iframeRef: externalIframeRef, pdfUrl, onLoad '); - $pdfPreview.attr("src", openService.getUrlForDownload(`api/${entityType}/${entityId}/open`)); + $pdfPreview.attr("src", openService.getUrlForDownload(`pdfjs/web/viewer.html?file=../../api/${entityType}/${entityId}/open`)); $content.append($pdfPreview); } else if (type === "audio") { From 757fc7a7fe823540e6b4ba2b4540a7b04fef186f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 4 Jan 2026 18:50:40 +0200 Subject: [PATCH 11/23] chore(pdfjs): embed sandbox file --- packages/pdfjs-viewer/scripts/build.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/pdfjs-viewer/scripts/build.ts b/packages/pdfjs-viewer/scripts/build.ts index 6f9d3a53d..9683b31cc 100644 --- a/packages/pdfjs-viewer/scripts/build.ts +++ b/packages/pdfjs-viewer/scripts/build.ts @@ -34,8 +34,9 @@ async function main() { build.writeJson("web/locale/locale.json", localeMappings); // Copy pdfjs-dist files. - build.copy("/node_modules/pdfjs-dist/build/pdf.mjs", "build/pdf.mjs"); - build.copy("/node_modules/pdfjs-dist/build/pdf.worker.mjs", "build/pdf.worker.mjs"); + for (const file of [ "pdf.mjs", "pdf.worker.mjs", "pdf.sandbox.mjs" ]) { + build.copy(join("/node_modules/pdfjs-dist/build", file), join("build", file)); + } if (watchMode) { watchForChanges(); From 971d6ad9e3c3254db73c621823f8311af7939077 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 4 Jan 2026 20:06:11 +0200 Subject: [PATCH 12/23] chore(pdfjs): use version-based system for cache busting --- packages/pdfjs-viewer/scripts/build.ts | 18 ++++++++++++++++++ scripts/update-nightly-version.ts | 13 +++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/packages/pdfjs-viewer/scripts/build.ts b/packages/pdfjs-viewer/scripts/build.ts index 9683b31cc..ff0cd9805 100644 --- a/packages/pdfjs-viewer/scripts/build.ts +++ b/packages/pdfjs-viewer/scripts/build.ts @@ -3,6 +3,8 @@ import BuildHelper from "../../../scripts/build-utils"; import { build as esbuild } from "esbuild"; import { LOCALES } from "@triliumnext/commons"; import { watch } from "chokidar"; +import { readFileSync, writeFileSync } from "fs"; +import packageJson from "../package.json" with { type: "json "}; const build = new BuildHelper("packages/pdfjs-viewer"); const watchMode = process.argv.includes("--watch"); @@ -16,6 +18,7 @@ async function main() { for (const file of [ "viewer.css", "viewer.html", "viewer.mjs" ]) { build.copy(`viewer/${file}`, `web/${file}`); } + patchCacheBuster(`${build.outDir}/web/viewer.html`); build.copy(`viewer/images`, `web/images`); // Copy the custom files. @@ -60,6 +63,21 @@ async function rebuildCustomFiles() { build.copy("src/custom.css", "web/custom.css"); } +function patchCacheBuster(htmlFilePath: string) { + const version = packageJson.version; + console.log(`Versioned URLs: ${version}.`) + let html = readFileSync(htmlFilePath, "utf-8"); + html = html.replace( + ``, + ``); + html = html.replace( + `` , + `` + ); + + writeFileSync(htmlFilePath, html); +} + function watchForChanges() { console.log("Watching for changes in src directory..."); const watcher = watch(join(build.projectDir, "src"), { diff --git a/scripts/update-nightly-version.ts b/scripts/update-nightly-version.ts index 2c0179331..71f5a9684 100644 --- a/scripts/update-nightly-version.ts +++ b/scripts/update-nightly-version.ts @@ -11,9 +11,9 @@ * */ -import { fileURLToPath } from "url"; -import { dirname, join } from "path"; import fs from "fs"; +import { dirname, join } from "path"; +import { fileURLToPath } from "url"; function processVersion(version) { // Remove the beta suffix if any. @@ -42,14 +42,19 @@ function patchPackageJson(packageJsonPath) { function main() { const scriptDir = dirname(fileURLToPath(import.meta.url)); - + const rootPackageJson = join(scriptDir, "..", "package.json"); patchPackageJson(rootPackageJson); - + for (const app of ["server", "client"]) { const appPackageJsonPath = join(scriptDir, "..", "apps", app, "package.json"); patchPackageJson(appPackageJsonPath); } + + for (const packageName of [ "pdfjs-viewer" ]) { + const packageJsonPath = join(scriptDir, "..", "packages", packageName, "package.json"); + patchPackageJson(packageJsonPath); + } } main(); From 20c265201325ec306612504adfba377535d7d57a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 4 Jan 2026 20:33:54 +0200 Subject: [PATCH 13/23] chore(server): set up environment for starting Nginx proxy with subdir --- apps/server/docker/nginx.conf | 2 +- apps/server/package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/server/docker/nginx.conf b/apps/server/docker/nginx.conf index 3c207c1ce..e4e744b18 100644 --- a/apps/server/docker/nginx.conf +++ b/apps/server/docker/nginx.conf @@ -9,7 +9,7 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; - proxy_pass http://host.docker.internal:8082; # change it to a different port if non-default is used + proxy_pass http://127.0.0.1:8082; proxy_cookie_path / /trilium/; proxy_read_timeout 90; } diff --git a/apps/server/package.json b/apps/server/package.json index 4dcd1c561..ecde42986 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -25,7 +25,8 @@ "docker-start-rootless-debian": "pnpm docker-build-rootless-debian && docker run -p 8081:8080 triliumnext-rootless-debian", "docker-start-rootless-alpine": "pnpm docker-build-rootless-alpine && docker run -p 8081:8080 triliumnext-rootless-alpine", "generate-document": "cross-env TRILIUM_ENV=dev TRILIUM_DATA_DIR=data TRILIUM_RESOURCE_DIR=src tsx ./scripts/generate_document.ts", - "proxy-traefik": "docker run --name trilium-traefik --rm --network=host -v ./docker/traefik/traefik.yml:/etc/traefik/traefik.yml -v ./docker/traefik/dynamic:/etc/traefik/dynamic traefik:latest" + "proxy-traefik": "docker run --name trilium-traefik --rm --network=host -v ./docker/traefik/traefik.yml:/etc/traefik/traefik.yml:ro -v ./docker/traefik/dynamic:/etc/traefik/dynamic traefik:latest", + "proxy-nginx-subdir": "docker run --name trilium-nginx-subdir --rm --network=host -v ./docker/nginx.conf:/etc/nginx/conf.d/default.conf:ro nginx:latest" }, "dependencies": { "better-sqlite3": "12.5.0", From fb4e912ed0cca7217927ef226e3e23f221b0de01 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 4 Jan 2026 21:08:39 +0200 Subject: [PATCH 14/23] chore(pdfjs): address requested changes --- apps/client/src/widgets/type_widgets/file/Pdf.tsx | 10 ++++++++-- packages/pdfjs-viewer/scripts/build.ts | 4 ++-- packages/pdfjs-viewer/src/custom.ts | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/file/Pdf.tsx b/apps/client/src/widgets/type_widgets/file/Pdf.tsx index 7acbc6d6c..ee731448a 100644 --- a/apps/client/src/widgets/type_widgets/file/Pdf.tsx +++ b/apps/client/src/widgets/type_widgets/file/Pdf.tsx @@ -24,11 +24,17 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: { getData() { if (!iframeRef.current?.contentWindow) return undefined; - return new Promise((resolve) => { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error("Timeout while waiting for blob response")); + }, 10_000); + const onMessageReceived = (event: PdfMessageEvent) => { if (event.data.type !== "pdfjs-viewer-blob") return; if (event.data.noteId !== note.noteId || event.data.ntxId !== noteContext.ntxId) return; const blob = new Blob([event.data.data as Uint8Array], { type: note.mime }); + + clearTimeout(timeout); window.removeEventListener("message", onMessageReceived); resolve(blob); }; @@ -36,7 +42,7 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: { window.addEventListener("message", onMessageReceived); iframeRef.current?.contentWindow?.postMessage({ type: "trilium-request-blob", - }); + }, window.location.origin); }); }, onContentChange() { diff --git a/packages/pdfjs-viewer/scripts/build.ts b/packages/pdfjs-viewer/scripts/build.ts index ff0cd9805..4a7e56880 100644 --- a/packages/pdfjs-viewer/scripts/build.ts +++ b/packages/pdfjs-viewer/scripts/build.ts @@ -4,7 +4,7 @@ import { build as esbuild } from "esbuild"; import { LOCALES } from "@triliumnext/commons"; import { watch } from "chokidar"; import { readFileSync, writeFileSync } from "fs"; -import packageJson from "../package.json" with { type: "json "}; +import packageJson from "../package.json" with { type: "json " }; const build = new BuildHelper("packages/pdfjs-viewer"); const watchMode = process.argv.includes("--watch"); @@ -71,7 +71,7 @@ function patchCacheBuster(htmlFilePath: string) { ``, ``); html = html.replace( - `` , + ``, `` ); diff --git a/packages/pdfjs-viewer/src/custom.ts b/packages/pdfjs-viewer/src/custom.ts index 2532c8d76..95b56a3c2 100644 --- a/packages/pdfjs-viewer/src/custom.ts +++ b/packages/pdfjs-viewer/src/custom.ts @@ -82,7 +82,7 @@ function manageSave() { data, ntxId: window.TRILIUM_NTX_ID, noteId: window.TRILIUM_NOTE_ID - } satisfies PdfDocumentBlobResultMessage, window.location.origin) + } satisfies PdfDocumentBlobResultMessage, window.location.origin); } }); From 51a19d054470a51b115ee6d78a1d7c9a1c333d99 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 4 Jan 2026 21:11:13 +0200 Subject: [PATCH 15/23] docs(user): add missing slug --- docs/User Guide/!!!meta.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/User Guide/!!!meta.json b/docs/User Guide/!!!meta.json index 272c4dbfe..50b9abb02 100644 --- a/docs/User Guide/!!!meta.json +++ b/docs/User Guide/!!!meta.json @@ -10201,6 +10201,13 @@ "value": "bx bxs-file-pdf", "isInheritable": false, "position": 30 + }, + { + "type": "label", + "name": "shareAlias", + "value": "pdf", + "isInheritable": false, + "position": 60 } ], "format": "markdown", From 5d6b25a29edaadc4698baedee5d02d31b1c0d3ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kim=20N=C3=B8glegaard?= Date: Sun, 4 Jan 2026 17:24:21 +0100 Subject: [PATCH 16/23] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 57.2% (87 of 152 strings) Translation: Trilium Notes/Website Translate-URL: https://hosted.weblate.org/projects/trilium/website/nb_NO/ --- .../src/translations/nb-NO/translation.json | 51 ++++++++++++++----- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/apps/website/src/translations/nb-NO/translation.json b/apps/website/src/translations/nb-NO/translation.json index d52a893c1..f9e3b5a58 100644 --- a/apps/website/src/translations/nb-NO/translation.json +++ b/apps/website/src/translations/nb-NO/translation.json @@ -3,7 +3,8 @@ "title": "Kom i gang", "architecture": "Arkitektur:", "desktop_title": "Last ned skrivebordsprogram (v{{version}})", - "older_releases": "Se tidligere versjoner" + "older_releases": "Se tidligere versjoner", + "server_title": "Sett opp en server for adgang fra flere enheter" }, "hero_section": { "title": "Organiser tankene dine. Bygg din personlige kunnskapsbase.", @@ -13,14 +14,19 @@ }, "organization_benefits": { "title": "Organisering", - "note_structure_title": "Notatstruktur" + "note_structure_title": "Notatstruktur", + "hoisting_title": "Arbeidsflate og fokusering", + "attributes_description": "Bruk relasjoner mellom notater eller legg til etiketter for enkel kategorisering. Bruk fremhevede attributter for å legge inn strukturert informasjon som kan brukes i tabeller og tavler." }, "productivity_benefits": { "sync_title": "Synkronisering", "search_title": "Kraftig søk", "web_clipper_title": "Web clipper", "revisions_title": "Notatrevisjon", - "protected_notes_title": "Beskyttede notater" + "protected_notes_title": "Beskyttede notater", + "title": "Produktivitet og sikkerhet", + "sync_content": "Bruk en selv-hostet eller cloud-instans for å enkelt synkronisere notater på tvers av enheter, og ha de tilgjengelige fra din mobiltelefon ved hjelp av progressiv web-app.", + "jump_to_content": "Hopp raskt til notater eller grensesnittkommandoer over hele hierarkiet ved å søke etter tittel, med \"fuzzy\" matching for å ta hensyn til skrivefeil eller små differanser." }, "note_types": { "canvas_title": "Kanvas", @@ -33,7 +39,8 @@ "extensibility_benefits": { "import_export_title": "Import/eksport", "scripting_title": "Avansert skripting", - "api_title": "REST API" + "api_title": "REST API", + "title": "Deling og utvidbarhet" }, "collections": { "title": "Samlinger", @@ -41,7 +48,8 @@ "table_title": "Tabell", "geomap_title": "Geokart", "presentation_title": "Presentasjon", - "board_title": "Kanbantavle" + "board_title": "Kanbantavle", + "geomap_description": "Planlegg ferien din eller merk deg dine interessepunkter på et geografisk kart ved hjelp av definerbare markører. Vis lagrede GPX-spor for å se reisen din." }, "header": { "documentation": "Dokumentasjon", @@ -58,12 +66,15 @@ "paypal": "PayPal", "title": "Støtt oss", "financial_donations_title": "Finansiell donasjon", - "github_sponsors": "GitHub Sponsors" + "github_sponsors": "GitHub Sponsors", + "financial_donations_description": "Trilium er bygget og vedlikeholdt med flere hundre timers arbeid. Ditt bidrag hjelper å holde det åpen kildekode, forbedre funksjonalitet og dekker driftskostnader." }, "download_helper_desktop_windows": { "download_scoop": "Scoop", "title_x64": "Windows 64-bit", - "download_zip": "Portable (.zip)" + "download_zip": "Portable (.zip)", + "title_arm64": "Windows på ARM", + "download_exe": "Last ned installasjonsprogram (.exe)" }, "download_helper_desktop_linux": { "download_deb": ".deb", @@ -72,15 +83,19 @@ "download_nixpkgs": "nixpkgs", "download_aur": "AUR", "title_x64": "Linux 64-bit", - "download_zip": "Portable (.zip)" + "download_zip": "Portable (.zip)", + "title_arm64": "Linux på ARM" }, "download_helper_server_docker": { "download_ghcr": "ghcr.io", - "download_dockerhub": "Docker Hub" + "download_dockerhub": "Docker Hub", + "title": "Selv-hostet med Docker" }, "download_helper_desktop_macos": { "download_homebrew_cask": "Homebrew Cask", - "download_zip": "Portable (.zip)" + "download_zip": "Portable (.zip)", + "title_x64": "macOS for Intel", + "download_dmg": "Last ned installasjonsprogram (.dmg)" }, "final_cta": { "get_started": "Kom i gang" @@ -91,7 +106,9 @@ "download_now": { "text": "Last ned nå ", "platform_small": "for {{platform}}", - "linux_small": "for Linux" + "linux_small": "for Linux", + "platform_big": "v{{version}} for {{platform}}", + "linux_big": "v{{version}} for Linux" }, "footer": { "copyright_and_the": " og ", @@ -100,9 +117,17 @@ "download_helper_server_linux": { "download_tar_x64": "x64 (.tar.xz)", "download_tar_arm64": "ARM (.tar.xz)", - "download_nixos": "NixOS modul" + "download_nixos": "NixOS modul", + "title": "Selv-hostet på Linux" }, "download_helper_server_hosted": { - "title": "Betalt hosting" + "title": "Betalt hosting", + "download_triliumcc": "Alternativt sjekk trilium.cc" + }, + "faq": { + "title": "Ofte stilte spørsmål" + }, + "404": { + "title": "404: Siden ble ikke funnet" } } From 0857e1a536ca1216cf4bf492738841413dc6f5cc Mon Sep 17 00:00:00 2001 From: green Date: Sun, 4 Jan 2026 04:11:59 +0100 Subject: [PATCH 17/23] Translated using Weblate (Japanese) Currently translated at 100.0% (1751 of 1751 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/ --- apps/client/src/translations/ja/translation.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/client/src/translations/ja/translation.json b/apps/client/src/translations/ja/translation.json index 9cde96ee8..7fb1bd58b 100644 --- a/apps/client/src/translations/ja/translation.json +++ b/apps/client/src/translations/ja/translation.json @@ -2196,7 +2196,14 @@ "execute_sql_description": "このノートは SQL ノートです。クリックすると SQL クエリが実行されます。", "shared_copy_to_clipboard": "リンクをクリップボードにコピー", "shared_open_in_browser": "ブラウザでリンクを開く", - "shared_unshare": "共有を削除" + "shared_unshare": "共有を削除", + "save_status_saved": "保存されました", + "save_status_saving": "保存中...", + "save_status_unsaved": "未保存", + "save_status_error": "保存に失敗しました", + "save_status_saving_tooltip": "変更を保存しています。", + "save_status_unsaved_tooltip": "未保存の変更があります。すぐに自動的に保存されます。", + "save_status_error_tooltip": "ノートの保存中にエラーが発生しました。可能であれば、ノートの内容を別の場所にコピーして、アプリケーションを再読み込みしてください。" }, "status_bar": { "language_title": "コンテンツの言語を変更", From 56341a1a73d0060b8e11e037d2914d5c60612735 Mon Sep 17 00:00:00 2001 From: "Francis C." Date: Sun, 4 Jan 2026 04:56:50 +0100 Subject: [PATCH 18/23] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (1751 of 1751 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/ --- apps/client/src/translations/tw/translation.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/tw/translation.json b/apps/client/src/translations/tw/translation.json index e3766db85..c51d511ed 100644 --- a/apps/client/src/translations/tw/translation.json +++ b/apps/client/src/translations/tw/translation.json @@ -2200,7 +2200,14 @@ "read_only_temporarily_disabled_description": "此筆記目前可編輯,但通常為唯讀狀態。當您切換至其他筆記時,本筆記將立即恢復為唯讀模式。\n\n點擊此處重新啟用唯讀模式。", "clipped_note_description": "本筆記原始來源為 {{url}}。\n\n點擊此處前往原網頁。", "execute_script_description": "此筆記為腳本筆記。點擊以執行腳本。", - "execute_sql_description": "此筆記為 SQL 筆記。點擊以執行 SQL 查詢。" + "execute_sql_description": "此筆記為 SQL 筆記。點擊以執行 SQL 查詢。", + "save_status_saved": "已儲存", + "save_status_saving": "正在儲存…", + "save_status_unsaved": "未儲存", + "save_status_error": "儲存失敗", + "save_status_saving_tooltip": "正在儲存更動。", + "save_status_unsaved_tooltip": "仍有更動尚未儲存。它們將在稍後自動儲存。", + "save_status_error_tooltip": "在儲存筆記時發生錯誤。如果可以,請嘗試將筆記內容複製至他處並重新載入應用程式。" }, "breadcrumb": { "hoisted_badge": "聚焦", @@ -2246,6 +2253,6 @@ "pages_one": "共 {{count}} 頁", "pages_other": "", "pages_alt": "第 {{pageNumber}} 頁", - "pages_loading": "載入中…" + "pages_loading": "正在載入…" } } From 3308c7bdf4bc288e2a9e4d012d0eac189482e1f6 Mon Sep 17 00:00:00 2001 From: noobhjy Date: Sun, 4 Jan 2026 02:57:19 +0100 Subject: [PATCH 19/23] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (1751 of 1751 strings) Translation: Trilium Notes/Client Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hans/ --- apps/client/src/translations/cn/translation.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/client/src/translations/cn/translation.json b/apps/client/src/translations/cn/translation.json index 90f785272..60487553c 100644 --- a/apps/client/src/translations/cn/translation.json +++ b/apps/client/src/translations/cn/translation.json @@ -1596,7 +1596,9 @@ "toggle-sidebar": "切换侧边栏", "dropping-not-allowed": "不允许移动笔记到此处。", "shared-indicator-tooltip": "此笔记已公开分享", - "shared-indicator-tooltip-with-url": "此笔记已公开分享至:{{- url}}" + "shared-indicator-tooltip-with-url": "此笔记已公开分享至:{{- url}}", + "clone-indicator-tooltip": "此笔记有 {{- count}} 个父级: {{- parents}}", + "clone-indicator-tooltip-single": "此笔记已克隆(1 个额外的父级:{{- parent}})" }, "title_bar_buttons": { "window-on-top": "保持此窗口置顶" @@ -2194,7 +2196,14 @@ "execute_sql_description": "这是一篇 SQL 笔记。点击即可执行 SQL 查询。", "shared_copy_to_clipboard": "复制链接到剪贴板", "shared_open_in_browser": "在浏览器中打开链接", - "shared_unshare": "取消共享" + "shared_unshare": "取消共享", + "save_status_saved": "已保存", + "save_status_saving": "保存中...", + "save_status_unsaved": "未保存", + "save_status_error": "保存失败", + "save_status_unsaved_tooltip": "还有一些更改尚未保存。它们将稍后自动保存。", + "save_status_error_tooltip": "保存笔记时出错。如果可以,请尝试将笔记内容复制到其他位置并重新加载应用程序。", + "save_status_saving_tooltip": "更改正在保存。" }, "status_bar": { "language_title": "更改内容语言", From cd10e66fbb98411a2786dd564d07030d6a103d05 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 4 Jan 2026 22:16:29 +0200 Subject: [PATCH 20/23] chore(scripts): build scripts not working properly on Windows --- scripts/build-utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/build-utils.ts b/scripts/build-utils.ts index 31992bcc7..94142c300 100644 --- a/scripts/build-utils.ts +++ b/scripts/build-utils.ts @@ -2,7 +2,7 @@ import { execSync } from "child_process"; import { build as esbuild } from "esbuild"; import { cpSync, existsSync, rmSync, writeFileSync } from "fs"; import { copySync, emptyDirSync, mkdirpSync } from "fs-extra"; -import { join } from "path"; +import { delimiter, join } from "path"; export default class BuildHelper { @@ -20,7 +20,7 @@ export default class BuildHelper { copy(projectDirPath: string, outDirPath: string) { let sourcePath: string; - if (projectDirPath.startsWith("/")) { + if (projectDirPath.startsWith("/") || projectDirPath.startsWith("\\")) { sourcePath = join(this.rootDir, projectDirPath.substring(1)); } else { sourcePath = join(this.projectDir, projectDirPath); From ed91a449280733a9a429844a7c640a63ca5dc120 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 4 Jan 2026 23:31:07 +0200 Subject: [PATCH 21/23] feat(scripts): check translation coverage --- .gitignore | 1 + scripts/translation/.language-stats.json | 1944 ----------------- .../translation/check-translation-coverage.ts | 21 + scripts/translation/manage-readme.ts | 35 +- scripts/translation/utils.ts | 33 + 5 files changed, 59 insertions(+), 1975 deletions(-) delete mode 100644 scripts/translation/.language-stats.json create mode 100644 scripts/translation/check-translation-coverage.ts create mode 100644 scripts/translation/utils.ts diff --git a/.gitignore b/.gitignore index 1d82de1ef..4dd7bde62 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ upload # docs site/ apps/*/coverage +scripts/translation/.language*.json \ No newline at end of file diff --git a/scripts/translation/.language-stats.json b/scripts/translation/.language-stats.json deleted file mode 100644 index 9c1befaa0..000000000 --- a/scripts/translation/.language-stats.json +++ /dev/null @@ -1,1944 +0,0 @@ -{ - "count": 35, - "next": null, - "previous": null, - "results": [ - { - "language": { - "id": 31, - "code": "en", - "name": "English", - "plural": { - "id": 75, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "base", - "en_en", - "en_us", - "eng", - "enp", - "source" - ], - "direction": "ltr", - "population": 1728003224, - "web_url": "https://hosted.weblate.org/languages/en/", - "url": "https://hosted.weblate.org/api/languages/en/", - "statistics_url": "https://hosted.weblate.org/api/languages/en/statistics/" - }, - "language_code": "en", - "id": 1614608, - "filename": "docs/README.md", - "revision": "5c59039312ea3d3684cbb62748ea9249d1a06a6d,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/en/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/en/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/en/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/en/", - "is_template": true, - "is_source": true, - "total": 118, - "total_words": 1110, - "translated": 118, - "translated_words": 1110, - "translated_percent": 100, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 16, - "failing_checks_words": 109, - "failing_checks_percent": 13.5, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:53:52.294489+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/en/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/en/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/en/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/en/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/en/units/" - }, - { - "language": { - "id": 186, - "code": "zh_Hans", - "name": "Chinese (Simplified Han script)", - "plural": { - "id": 57, - "source": 0, - "number": 1, - "formula": "0", - "type": 0 - }, - "aliases": [ - "chi", - "chinese", - "chinese_chs", - "chinese_zh", - "chs", - "cmn", - "cmn_hans", - "cn", - "schinese", - "zh", - "zh_chs", - "zh_cmn_hans", - "zh_cn", - "zh_hans_cn", - "zh_simplified", - "zhcn" - ], - "direction": "ltr", - "population": 1286444445, - "web_url": "https://hosted.weblate.org/languages/zh_Hans/", - "url": "https://hosted.weblate.org/api/languages/zh_Hans/", - "statistics_url": "https://hosted.weblate.org/api/languages/zh_Hans/statistics/" - }, - "language_code": "ZH_CN", - "id": 1614681, - "filename": "docs/README-ZH_CN.md", - "revision": "1830b2144a971d78896812027ac33f2348a4c513,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/zh_Hans/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/zh_Hans/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/zh_Hans/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/zh_Hans/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 118, - "translated_words": 1110, - "translated_percent": 100, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:14.143896+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/zh_Hans/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/zh_Hans/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/zh_Hans/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/zh_Hans/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/zh_Hans/units/" - }, - { - "language": { - "id": 185, - "code": "zh_Hant", - "name": "Chinese (Traditional Han script)", - "plural": { - "id": 734, - "source": 0, - "number": 1, - "formula": "0", - "type": 0 - }, - "aliases": [ - "cht", - "cmn_hant", - "tchinese", - "zh_cht", - "zh_cmn_hant", - "zh_hant@zh", - "zh_hant_tw", - "zh_traditional", - "zh_tw", - "zho", - "zhtw" - ], - "direction": "ltr", - "population": 39078482, - "web_url": "https://hosted.weblate.org/languages/zh_Hant/", - "url": "https://hosted.weblate.org/api/languages/zh_Hant/", - "statistics_url": "https://hosted.weblate.org/api/languages/zh_Hant/statistics/" - }, - "language_code": "ZH_TW", - "id": 1614682, - "filename": "docs/README-ZH_TW.md", - "revision": "2021df9f07639e22a8f4c0b2ec293180e9417519,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/zh_Hant/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/zh_Hant/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/zh_Hant/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/zh_Hant/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 118, - "translated_words": 1110, - "translated_percent": 100, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:14.334553+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/zh_Hant/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/zh_Hant/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/zh_Hant/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/zh_Hant/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/zh_Hant/units/" - }, - { - "language": { - "id": 103, - "code": "es", - "name": "Spanish", - "plural": { - "id": 291, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "es_es", - "es_la", - "esp", - "spa" - ], - "direction": "ltr", - "population": 507161083, - "web_url": "https://hosted.weblate.org/languages/es/", - "url": "https://hosted.weblate.org/api/languages/es/", - "statistics_url": "https://hosted.weblate.org/api/languages/es/statistics/" - }, - "language_code": "es", - "id": 1614683, - "filename": "docs/README-es.md", - "revision": "75f6c28eecc5ae7af9c151e1b2fae9581286f2a1,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/es/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/es/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/es/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/es/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 118, - "translated_words": 1110, - "translated_percent": 100, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:15.458107+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/es/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/es/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/es/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/es/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/es/units/" - }, - { - "language": { - "id": 86, - "code": "it", - "name": "Italian", - "plural": { - "id": 146, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "it_it", - "ita", - "ita_it" - ], - "direction": "ltr", - "population": 70475318, - "web_url": "https://hosted.weblate.org/languages/it/", - "url": "https://hosted.weblate.org/api/languages/it/", - "statistics_url": "https://hosted.weblate.org/api/languages/it/statistics/" - }, - "language_code": "it", - "id": 1614684, - "filename": "docs/README-it.md", - "revision": "2ddb0025430cee98d7f3c03d2267cdb8f4af5bcf,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/it/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/it/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/it/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/it/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 118, - "translated_words": 1110, - "translated_percent": 100, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:16.673093+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/it/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/it/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/it/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/it/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/it/units/" - }, - { - "language": { - "id": 49, - "code": "ja", - "name": "Japanese", - "plural": { - "id": 148, - "source": 0, - "number": 1, - "formula": "0", - "type": 0 - }, - "aliases": [ - "ja_ja", - "ja_jp", - "jp", - "jp_jpn", - "jpn", - "jpn_jp" - ], - "direction": "ltr", - "population": 117608755, - "web_url": "https://hosted.weblate.org/languages/ja/", - "url": "https://hosted.weblate.org/api/languages/ja/", - "statistics_url": "https://hosted.weblate.org/api/languages/ja/statistics/" - }, - "language_code": "ja", - "id": 1614685, - "filename": "docs/README-ja.md", - "revision": "fd2c1fa546f569a20a7fa451837504787e0ed095,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/ja/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/ja/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/ja/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/ja/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 118, - "translated_words": 1110, - "translated_percent": 100, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:16.823524+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/ja/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/ja/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/ja/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/ja/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/ja/units/" - }, - { - "language": { - "id": 38, - "code": "ro", - "name": "Romanian", - "plural": { - "id": 251, - "source": 0, - "number": 3, - "formula": "n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2", - "type": 2 - }, - "aliases": [ - "mol", - "ro_ro", - "ron", - "rum" - ], - "direction": "ltr", - "population": 20043506, - "web_url": "https://hosted.weblate.org/languages/ro/", - "url": "https://hosted.weblate.org/api/languages/ro/", - "statistics_url": "https://hosted.weblate.org/api/languages/ro/statistics/" - }, - "language_code": "ro", - "id": 1614686, - "filename": "docs/README-ro.md", - "revision": "6f672b32dbfedf2f57a710a62c11c5b753e9dc1d,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/ro/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/ro/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/ro/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/ro/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 118, - "translated_words": 1110, - "translated_percent": 100, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 1, - "failing_checks_words": 2, - "failing_checks_percent": 0.8, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:18.461356+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/ro/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/ro/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/ro/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/ro/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/ro/units/" - }, - { - "language": { - "id": 36, - "code": "ru", - "name": "Russian", - "plural": { - "id": 255, - "source": 0, - "number": 3, - "formula": "n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2", - "type": 22 - }, - "aliases": [ - "ru_r", - "ru_rr", - "ru_ru", - "rus" - ], - "direction": "ltr", - "population": 193610712, - "web_url": "https://hosted.weblate.org/languages/ru/", - "url": "https://hosted.weblate.org/api/languages/ru/", - "statistics_url": "https://hosted.weblate.org/api/languages/ru/statistics/" - }, - "language_code": "ru", - "id": 1614687, - "filename": "docs/README-ru.md", - "revision": "ce4750e0fd84f5b51f46a81f008d7249d876f203,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/ru/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/ru/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/ru/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/ru/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 78, - "translated_words": 640, - "translated_percent": 66.1, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 2, - "failing_checks_words": 7, - "failing_checks_percent": 1.6, - "have_suggestion": 2, - "have_comment": 0, - "last_change": "2025-12-08T10:55:18.631436+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/ru/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/ru/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/ru/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/ru/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/ru/units/" - }, - { - "language": { - "id": 12, - "code": "tr", - "name": "Turkish", - "plural": { - "id": 323, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "tr_tr", - "trk", - "tur" - ], - "direction": "ltr", - "population": 82419542, - "web_url": "https://hosted.weblate.org/languages/tr/", - "url": "https://hosted.weblate.org/api/languages/tr/", - "statistics_url": "https://hosted.weblate.org/api/languages/tr/statistics/" - }, - "language_code": "tr", - "id": 1614699, - "filename": "docs/README-tr.md", - "revision": "1b33459e924d5b3e8398f08f4d46d7256a59abf9,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/tr/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/tr/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/tr/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/tr/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 11, - "translated_words": 97, - "translated_percent": 9.3, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 1, - "have_comment": 0, - "last_change": "2025-12-08T10:55:19.281339+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/tr/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/tr/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/tr/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/tr/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/tr/units/" - }, - { - "language": { - "id": 23, - "code": "pt_BR", - "name": "Portuguese (Brazil)", - "plural": { - "id": 246, - "source": 0, - "number": 2, - "formula": "n > 1", - "type": 1 - }, - "aliases": [ - "bp", - "braz_por", - "por_br", - "portuguese_br", - "ptb" - ], - "direction": "ltr", - "population": 200247320, - "web_url": "https://hosted.weblate.org/languages/pt_BR/", - "url": "https://hosted.weblate.org/api/languages/pt_BR/", - "statistics_url": "https://hosted.weblate.org/api/languages/pt_BR/statistics/" - }, - "language_code": "pt_BR", - "id": 1614700, - "filename": "docs/README-pt_BR.md", - "revision": "88f2a4bbdb291cb85fbb65130d2a15c78a6fd8e5,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/pt_BR/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/pt_BR/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/pt_BR/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/pt_BR/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 0, - "translated_words": 0, - "translated_percent": 0, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:18.291539+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/pt_BR/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/pt_BR/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/pt_BR/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/pt_BR/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/pt_BR/units/" - }, - { - "language": { - "id": 24, - "code": "de", - "name": "German", - "plural": { - "id": 104, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "de_de", - "deu", - "deu_de", - "ger" - ], - "direction": "ltr", - "population": 141873196, - "web_url": "https://hosted.weblate.org/languages/de/", - "url": "https://hosted.weblate.org/api/languages/de/", - "statistics_url": "https://hosted.weblate.org/api/languages/de/statistics/" - }, - "language_code": "de", - "id": 1614701, - "filename": "docs/README-de.md", - "revision": "9ac27b9025f2b4530290bf14b68b64f20b6bdcf0,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/de/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/de/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/de/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/de/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 118, - "translated_words": 1110, - "translated_percent": 100, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 2, - "failing_checks_words": 4, - "failing_checks_percent": 1.6, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:14.982190+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/de/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/de/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/de/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/de/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/de/units/" - }, - { - "language": { - "id": 29, - "code": "el", - "name": "Greek", - "plural": { - "id": 108, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "el_gr", - "ell", - "gr", - "gre" - ], - "direction": "ltr", - "population": 12249401, - "web_url": "https://hosted.weblate.org/languages/el/", - "url": "https://hosted.weblate.org/api/languages/el/", - "statistics_url": "https://hosted.weblate.org/api/languages/el/statistics/" - }, - "language_code": "el", - "id": 1614702, - "filename": "docs/README-el.md", - "revision": "bc3eeb0d043038c93ba51939188657e38f5cf366,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/el/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/el/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/el/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/el/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 118, - "translated_words": 1110, - "translated_percent": 100, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:15.136428+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/el/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/el/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/el/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/el/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/el/units/" - }, - { - "language": { - "id": 42, - "code": "uk", - "name": "Ukrainian", - "plural": { - "id": 327, - "source": 0, - "number": 3, - "formula": "n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2", - "type": 22 - }, - "aliases": [ - "ua", - "ua_ua", - "uk_ua", - "uk_uk", - "ukr" - ], - "direction": "ltr", - "population": 24080585, - "web_url": "https://hosted.weblate.org/languages/uk/", - "url": "https://hosted.weblate.org/api/languages/uk/", - "statistics_url": "https://hosted.weblate.org/api/languages/uk/statistics/" - }, - "language_code": "uk", - "id": 1614703, - "filename": "docs/README-uk.md", - "revision": "a7578bd58f4beaf3a2594563865667833e124567,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/uk/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/uk/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/uk/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/uk/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 38, - "translated_words": 303, - "translated_percent": 32.2, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:19.440960+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/uk/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/uk/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/uk/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/uk/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/uk/units/" - }, - { - "language": { - "id": 53, - "code": "ca", - "name": "Catalan", - "plural": { - "id": 46, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "ca_ca", - "ca_es", - "ca_ps", - "cat" - ], - "direction": "ltr", - "population": 8218781, - "web_url": "https://hosted.weblate.org/languages/ca/", - "url": "https://hosted.weblate.org/api/languages/ca/", - "statistics_url": "https://hosted.weblate.org/api/languages/ca/statistics/" - }, - "language_code": "ca", - "id": 1614704, - "filename": "docs/README-ca.md", - "revision": "301e382cce216c34394bed5d0e5167bdae144f70,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/ca/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/ca/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/ca/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/ca/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 2, - "translated_words": 35, - "translated_percent": 1.6, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:14.660609+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/ca/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/ca/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/ca/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/ca/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/ca/units/" - }, - { - "language": { - "id": 56, - "code": "cs", - "name": "Czech", - "plural": { - "id": 68, - "source": 0, - "number": 3, - "formula": "(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2", - "type": 22 - }, - "aliases": [ - "ces", - "cs_cs", - "cs_cz", - "csy", - "cz", - "cze" - ], - "direction": "ltr", - "population": 13236057, - "web_url": "https://hosted.weblate.org/languages/cs/", - "url": "https://hosted.weblate.org/api/languages/cs/", - "statistics_url": "https://hosted.weblate.org/api/languages/cs/statistics/" - }, - "language_code": "cs", - "id": 1614705, - "filename": "docs/README-cs.md", - "revision": "ee6855faacd6887ca42429ab852ac4c404a840c4,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/cs/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/cs/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/cs/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/cs/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 43, - "translated_words": 339, - "translated_percent": 36.4, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:14.819508+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/cs/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/cs/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/cs/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/cs/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/cs/units/" - }, - { - "language": { - "id": 58, - "code": "pt", - "name": "Portuguese", - "plural": { - "id": 245, - "source": 0, - "number": 2, - "formula": "n > 1", - "type": 1 - }, - "aliases": [ - "por", - "por_pt", - "pt_pt", - "ptg" - ], - "direction": "ltr", - "population": 249463918, - "web_url": "https://hosted.weblate.org/languages/pt/", - "url": "https://hosted.weblate.org/api/languages/pt/", - "statistics_url": "https://hosted.weblate.org/api/languages/pt/statistics/" - }, - "language_code": "pt", - "id": 1614706, - "filename": "docs/README-pt.md", - "revision": "b3a9f45b8e23cb6378cd3dfa15691918eeed5a84,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/pt/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/pt/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/pt/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/pt/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 6, - "translated_words": 35, - "translated_percent": 5, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:18.156718+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/pt/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/pt/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/pt/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/pt/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/pt/units/" - }, - { - "language": { - "id": 60, - "code": "vi", - "name": "Vietnamese", - "plural": { - "id": 337, - "source": 0, - "number": 1, - "formula": "0", - "type": 0 - }, - "aliases": [ - "vi_vn", - "vie", - "vn" - ], - "direction": "ltr", - "population": 92370781, - "web_url": "https://hosted.weblate.org/languages/vi/", - "url": "https://hosted.weblate.org/api/languages/vi/", - "statistics_url": "https://hosted.weblate.org/api/languages/vi/statistics/" - }, - "language_code": "vi", - "id": 1614707, - "filename": "docs/README-vi.md", - "revision": "8a4fdf64dff418ae10af3faf26f8042fcad589aa,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/vi/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/vi/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/vi/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/vi/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 66, - "translated_words": 563, - "translated_percent": 55.9, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 1, - "failing_checks_words": 3, - "failing_checks_percent": 0.8, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:19.594969+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/vi/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/vi/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/vi/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/vi/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/vi/units/" - }, - { - "language": { - "id": 64, - "code": "hr", - "name": "Croatian", - "plural": { - "id": 66, - "source": 0, - "number": 3, - "formula": "n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2", - "type": 22 - }, - "aliases": [ - "hr_hr", - "hrv", - "scr" - ], - "direction": "ltr", - "population": 6790797, - "web_url": "https://hosted.weblate.org/languages/hr/", - "url": "https://hosted.weblate.org/api/languages/hr/", - "statistics_url": "https://hosted.weblate.org/api/languages/hr/statistics/" - }, - "language_code": "hr", - "id": 1614708, - "filename": "docs/README-hr.md", - "revision": "88f2a4bbdb291cb85fbb65130d2a15c78a6fd8e5,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/hr/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/hr/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/hr/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/hr/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 0, - "translated_words": 0, - "translated_percent": 0, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:16.211016+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/hr/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/hr/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/hr/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/hr/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/hr/units/" - }, - { - "language": { - "id": 66, - "code": "hu", - "name": "Hungarian", - "plural": { - "id": 136, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "hu_hu", - "hun" - ], - "direction": "ltr", - "population": 12313191, - "web_url": "https://hosted.weblate.org/languages/hu/", - "url": "https://hosted.weblate.org/api/languages/hu/", - "statistics_url": "https://hosted.weblate.org/api/languages/hu/statistics/" - }, - "language_code": "hu", - "id": 1614709, - "filename": "docs/README-hu.md", - "revision": "3301310befda08a5b5c45ef8770827e1fcec1e0a,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/hu/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/hu/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/hu/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/hu/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 32, - "translated_words": 254, - "translated_percent": 27.1, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 2, - "failing_checks_words": 23, - "failing_checks_percent": 1.6, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:16.360860+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/hu/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/hu/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/hu/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/hu/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/hu/units/" - }, - { - "language": { - "id": 78, - "code": "pl", - "name": "Polish", - "plural": { - "id": 244, - "source": 0, - "number": 3, - "formula": "n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2", - "type": 22 - }, - "aliases": [ - "pl_pl", - "plk", - "pol" - ], - "direction": "ltr", - "population": 41500500, - "web_url": "https://hosted.weblate.org/languages/pl/", - "url": "https://hosted.weblate.org/api/languages/pl/", - "statistics_url": "https://hosted.weblate.org/api/languages/pl/statistics/" - }, - "language_code": "pl", - "id": 1614710, - "filename": "docs/README-pl.md", - "revision": "46d6992a6c40344d26bd6b6a8601d44d41c1e88d,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/pl/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/pl/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/pl/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/pl/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 8, - "translated_words": 79, - "translated_percent": 6.7, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 16, - "have_comment": 0, - "last_change": "2025-12-08T10:55:18.019340+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/pl/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/pl/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/pl/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/pl/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/pl/units/" - }, - { - "language": { - "id": 88, - "code": "ar", - "name": "Arabic", - "plural": { - "id": 10, - "source": 0, - "number": 6, - "formula": "n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5", - "type": 3 - }, - "aliases": [ - "ar_aa", - "ar_ar", - "ara", - "arb" - ], - "direction": "rtl", - "population": 378792526, - "web_url": "https://hosted.weblate.org/languages/ar/", - "url": "https://hosted.weblate.org/api/languages/ar/", - "statistics_url": "https://hosted.weblate.org/api/languages/ar/statistics/" - }, - "language_code": "ar", - "id": 1614711, - "filename": "docs/README-ar.md", - "revision": "f4f63c8ac28ec0a962ebc0dfb568043877a094b6,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/ar/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/ar/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/ar/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/ar/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 32, - "translated_words": 75, - "translated_percent": 27.1, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:14.504235+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/ar/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/ar/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/ar/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/ar/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/ar/units/" - }, - { - "language": { - "id": 96, - "code": "nl", - "name": "Dutch", - "plural": { - "id": 73, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "dut", - "nl_nl", - "nld" - ], - "direction": "ltr", - "population": 32854898, - "web_url": "https://hosted.weblate.org/languages/nl/", - "url": "https://hosted.weblate.org/api/languages/nl/", - "statistics_url": "https://hosted.weblate.org/api/languages/nl/statistics/" - }, - "language_code": "nl", - "id": 1614712, - "filename": "docs/README-nl.md", - "revision": "fe2bf72bab1d531f2a689f0c5cd73946f968d421,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/nl/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/nl/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/nl/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/nl/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 31, - "translated_words": 249, - "translated_percent": 26.2, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 1, - "failing_checks_words": 9, - "failing_checks_percent": 0.8, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:17.883033+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/nl/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/nl/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/nl/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/nl/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/nl/units/" - }, - { - "language": { - "id": 105, - "code": "fr", - "name": "French", - "plural": { - "id": 93, - "source": 0, - "number": 2, - "formula": "n > 1", - "type": 1 - }, - "aliases": [ - "fr_fr", - "fra", - "fra_fr", - "fre" - ], - "direction": "ltr", - "population": 332956350, - "web_url": "https://hosted.weblate.org/languages/fr/", - "url": "https://hosted.weblate.org/api/languages/fr/", - "statistics_url": "https://hosted.weblate.org/api/languages/fr/statistics/" - }, - "language_code": "fr", - "id": 1614713, - "filename": "docs/README-fr.md", - "revision": "172740086a99f1b4e08438d444c2d51d2dda1f3a,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/fr/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/fr/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/fr/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/fr/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 118, - "translated_words": 1110, - "translated_percent": 100, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:15.910510+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/fr/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/fr/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/fr/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/fr/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/fr/units/" - }, - { - "language": { - "id": 108, - "code": "fa", - "name": "Persian", - "plural": { - "id": 241, - "source": 0, - "number": 2, - "formula": "n > 1", - "type": 1 - }, - "aliases": [ - "fa_ir", - "fas", - "fas_ir", - "per", - "pes" - ], - "direction": "rtl", - "population": 89208445, - "web_url": "https://hosted.weblate.org/languages/fa/", - "url": "https://hosted.weblate.org/api/languages/fa/", - "statistics_url": "https://hosted.weblate.org/api/languages/fa/statistics/" - }, - "language_code": "fa", - "id": 1614714, - "filename": "docs/README-fa.md", - "revision": "aa651f065af16732537b53874185968a62595e28,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/fa/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/fa/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/fa/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/fa/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 19, - "translated_words": 131, - "translated_percent": 16.1, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 2, - "failing_checks_words": 29, - "failing_checks_percent": 1.6, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:15.622121+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/fa/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/fa/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/fa/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/fa/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/fa/units/" - }, - { - "language": { - "id": 110, - "code": "fi", - "name": "Finnish", - "plural": { - "id": 89, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "fi_fi", - "fin", - "fin_fi" - ], - "direction": "ltr", - "population": 5789395, - "web_url": "https://hosted.weblate.org/languages/fi/", - "url": "https://hosted.weblate.org/api/languages/fi/", - "statistics_url": "https://hosted.weblate.org/api/languages/fi/statistics/" - }, - "language_code": "fi", - "id": 1614715, - "filename": "docs/README-fi.md", - "revision": "88f2a4bbdb291cb85fbb65130d2a15c78a6fd8e5,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/fi/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/fi/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/fi/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/fi/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 0, - "translated_words": 0, - "translated_percent": 0, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:15.762968+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/fi/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/fi/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/fi/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/fi/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/fi/units/" - }, - { - "language": { - "id": 114, - "code": "sr", - "name": "Serbian", - "plural": { - "id": 268, - "source": 0, - "number": 3, - "formula": "n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2", - "type": 22 - }, - "aliases": [ - "rs", - "scc", - "sr_cs", - "sr_rs", - "srp" - ], - "direction": "ltr", - "population": 7189403, - "web_url": "https://hosted.weblate.org/languages/sr/", - "url": "https://hosted.weblate.org/api/languages/sr/", - "statistics_url": "https://hosted.weblate.org/api/languages/sr/statistics/" - }, - "language_code": "sr", - "id": 1614716, - "filename": "docs/README-sr.md", - "revision": "88f2a4bbdb291cb85fbb65130d2a15c78a6fd8e5,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/sr/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/sr/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/sr/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/sr/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 0, - "translated_words": 0, - "translated_percent": 0, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:18.942066+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/sr/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/sr/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/sr/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/sr/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/sr/units/" - }, - { - "language": { - "id": 116, - "code": "ko", - "name": "Korean", - "plural": { - "id": 165, - "source": 0, - "number": 1, - "formula": "0", - "type": 0 - }, - "aliases": [ - "ko_ko", - "ko_kr", - "kor", - "kor_kr" - ], - "direction": "ltr", - "population": 79278717, - "web_url": "https://hosted.weblate.org/languages/ko/", - "url": "https://hosted.weblate.org/api/languages/ko/", - "statistics_url": "https://hosted.weblate.org/api/languages/ko/statistics/" - }, - "language_code": "ko", - "id": 1614717, - "filename": "docs/README-ko.md", - "revision": "99560c99007f00159d560c5870c09d5cbb586e9c,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/ko/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/ko/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/ko/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/ko/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 43, - "translated_words": 310, - "translated_percent": 36.4, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 1, - "have_comment": 0, - "last_change": "2025-12-08T10:55:17.293720+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/ko/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/ko/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/ko/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/ko/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/ko/units/" - }, - { - "language": { - "id": 124, - "code": "sl", - "name": "Slovenian", - "plural": { - "id": 280, - "source": 0, - "number": 4, - "formula": "n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3", - "type": 6 - }, - "aliases": [ - "sl_si", - "sl_sl", - "slv" - ], - "direction": "ltr", - "population": 1967012, - "web_url": "https://hosted.weblate.org/languages/sl/", - "url": "https://hosted.weblate.org/api/languages/sl/", - "statistics_url": "https://hosted.weblate.org/api/languages/sl/statistics/" - }, - "language_code": "sl", - "id": 1614718, - "filename": "docs/README-sl.md", - "revision": "88f2a4bbdb291cb85fbb65130d2a15c78a6fd8e5,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/sl/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/sl/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/sl/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/sl/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 0, - "translated_words": 0, - "translated_percent": 0, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:18.790431+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/sl/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/sl/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/sl/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/sl/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/sl/units/" - }, - { - "language": { - "id": 449, - "code": "nb_NO", - "name": "Norwegian Bokmål", - "plural": { - "id": 447, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "nb", - "nb_nb", - "no", - "no_nb", - "no_no", - "nob", - "nor", - "norwegian" - ], - "direction": "ltr", - "population": 5509730, - "web_url": "https://hosted.weblate.org/languages/nb_NO/", - "url": "https://hosted.weblate.org/api/languages/nb_NO/", - "statistics_url": "https://hosted.weblate.org/api/languages/nb_NO/statistics/" - }, - "language_code": "nb_NO", - "id": 1614719, - "filename": "docs/README-nb_NO.md", - "revision": "88f2a4bbdb291cb85fbb65130d2a15c78a6fd8e5,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/nb_NO/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/nb_NO/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/nb_NO/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/nb_NO/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 0, - "translated_words": 0, - "translated_percent": 0, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:17.749374+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/nb_NO/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/nb_NO/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/nb_NO/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/nb_NO/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/nb_NO/units/" - }, - { - "language": { - "id": 4429, - "code": "md", - "name": "md (generated)", - "plural": { - "id": 5665, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [], - "direction": "ltr", - "population": 0, - "web_url": "https://hosted.weblate.org/languages/md/", - "url": "https://hosted.weblate.org/api/languages/md/", - "statistics_url": "https://hosted.weblate.org/api/languages/md/statistics/" - }, - "language_code": "md", - "id": 1614720, - "filename": "docs/README-md.md", - "revision": "88f2a4bbdb291cb85fbb65130d2a15c78a6fd8e5,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/md/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/md/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/md/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/md/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 0, - "translated_words": 0, - "translated_percent": 0, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:17.448478+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/md/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/md/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/md/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/md/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/md/units/" - }, - { - "language": { - "id": 93, - "code": "id", - "name": "Indonesian", - "plural": { - "id": 141, - "source": 0, - "number": 1, - "formula": "0", - "type": 0 - }, - "aliases": [ - "id_id", - "in", - "in_id", - "ind" - ], - "direction": "ltr", - "population": 180519583, - "web_url": "https://hosted.weblate.org/languages/id/", - "url": "https://hosted.weblate.org/api/languages/id/", - "statistics_url": "https://hosted.weblate.org/api/languages/id/statistics/" - }, - "language_code": "id", - "id": 1615855, - "filename": "docs/README-id.md", - "revision": "1eeae4bf7459369c91c3d4e042de77931146c2e0,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/id/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/id/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/id/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/id/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 22, - "translated_words": 151, - "translated_percent": 18.6, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:16.508985+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/id/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/id/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/id/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/id/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/id/units/" - }, - { - "language": { - "id": 107, - "code": "sv", - "name": "Swedish", - "plural": { - "id": 301, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "sv_se", - "sve", - "swe", - "swe_se" - ], - "direction": "ltr", - "population": 13338376, - "web_url": "https://hosted.weblate.org/languages/sv/", - "url": "https://hosted.weblate.org/api/languages/sv/", - "statistics_url": "https://hosted.weblate.org/api/languages/sv/statistics/" - }, - "language_code": "sv", - "id": 1616650, - "filename": "docs/README-sv.md", - "revision": "6a71fb69bdb7eaf995dbdad69fe1691bbe24e38f,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/sv/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/sv/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/sv/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/sv/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 5, - "translated_words": 33, - "translated_percent": 4.2, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:19.117945+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/sv/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/sv/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/sv/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/sv/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/sv/units/" - }, - { - "language": { - "id": 67, - "code": "hi", - "name": "Hindi", - "plural": { - "id": 134, - "source": 0, - "number": 2, - "formula": "n > 1", - "type": 1 - }, - "aliases": [ - "hi_in", - "hin" - ], - "direction": "ltr", - "population": 580318483, - "web_url": "https://hosted.weblate.org/languages/hi/", - "url": "https://hosted.weblate.org/api/languages/hi/", - "statistics_url": "https://hosted.weblate.org/api/languages/hi/statistics/" - }, - "language_code": "hi", - "id": 1618799, - "filename": "docs/README-hi.md", - "revision": "a2707d32944aa74ea74f3fc593c7c96c73ca97f9,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/hi/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/hi/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/hi/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/hi/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 1, - "translated_words": 2, - "translated_percent": 0.8, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:16.061819+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/hi/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/hi/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/hi/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/hi/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/hi/units/" - }, - { - "language": { - "id": 80, - "code": "mr", - "name": "Marathi", - "plural": { - "id": 198, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "mar", - "mr_in" - ], - "direction": "ltr", - "population": 98639100, - "web_url": "https://hosted.weblate.org/languages/mr/", - "url": "https://hosted.weblate.org/api/languages/mr/", - "statistics_url": "https://hosted.weblate.org/api/languages/mr/statistics/" - }, - "language_code": "mr", - "id": 1618804, - "filename": "docs/README-mr.md", - "revision": "88f2a4bbdb291cb85fbb65130d2a15c78a6fd8e5,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/mr/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/mr/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/mr/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/mr/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 0, - "translated_words": 0, - "translated_percent": 0, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 0, - "failing_checks_words": 0, - "failing_checks_percent": 0, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:17.599588+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/mr/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/mr/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/mr/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/mr/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/mr/units/" - }, - { - "language": { - "id": 62, - "code": "en_GB", - "name": "English (United Kingdom)", - "plural": { - "id": 81, - "source": 0, - "number": 2, - "formula": "n != 1", - "type": 1 - }, - "aliases": [ - "eng_gb", - "english_uk" - ], - "direction": "ltr", - "population": 67089918, - "web_url": "https://hosted.weblate.org/languages/en_GB/", - "url": "https://hosted.weblate.org/api/languages/en_GB/", - "statistics_url": "https://hosted.weblate.org/api/languages/en_GB/statistics/" - }, - "language_code": "en_GB", - "id": 1627399, - "filename": "docs/README-en_GB.md", - "revision": "d8785285843cae2328d45313715b8f2d5c0efc09,5c59039312ea3d3684cbb62748ea9249d1a06a6d", - "web_url": "https://hosted.weblate.org/projects/trilium/readme/en_GB/", - "share_url": "https://hosted.weblate.org/engage/trilium/-/en_GB/", - "translate_url": "https://hosted.weblate.org/translate/trilium/readme/en_GB/", - "url": "https://hosted.weblate.org/api/translations/trilium/readme/en_GB/", - "is_template": false, - "is_source": false, - "total": 118, - "total_words": 1110, - "translated": 1, - "translated_words": 6, - "translated_percent": 0.8, - "fuzzy": 0, - "fuzzy_words": 0, - "fuzzy_percent": 0, - "failing_checks": 1, - "failing_checks_words": 42, - "failing_checks_percent": 0.8, - "have_suggestion": 0, - "have_comment": 0, - "last_change": "2025-12-08T10:55:15.298351+01:00", - "last_author": null, - "repository_url": "https://hosted.weblate.org/api/translations/trilium/readme/en_GB/repository/", - "file_url": "https://hosted.weblate.org/api/translations/trilium/readme/en_GB/file/", - "statistics_url": "https://hosted.weblate.org/api/translations/trilium/readme/en_GB/statistics/", - "changes_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/en_GB/changes/", - "units_list_url": "https://hosted.weblate.org/api/translations/trilium/readme/en_GB/units/" - } - ] -} \ No newline at end of file diff --git a/scripts/translation/check-translation-coverage.ts b/scripts/translation/check-translation-coverage.ts new file mode 100644 index 000000000..57ae4a57e --- /dev/null +++ b/scripts/translation/check-translation-coverage.ts @@ -0,0 +1,21 @@ +import { LOCALES } from "../../packages/commons/src/lib/i18n"; +import { getLanguageStats } from "./utils"; + +async function main() { + const languageStats = await getLanguageStats("client"); + const localeIdsWithCoverage = languageStats.results + .filter(language => language.translated_percent > 50) + .map(language => language.language_code); + + for (const localeId of localeIdsWithCoverage) { + const locale = LOCALES.find(l => l.id === localeId); + if (!locale) { + console.error(`Locale not found for id: ${localeId}`); + process.exit(1); + } + } + + console.log("Translation coverage check passed."); +} + +main(); diff --git a/scripts/translation/manage-readme.ts b/scripts/translation/manage-readme.ts index f5a3104b4..ba406f835 100644 --- a/scripts/translation/manage-readme.ts +++ b/scripts/translation/manage-readme.ts @@ -1,42 +1,15 @@ -import { readFile, stat, writeFile, } from "fs/promises"; +import { readFile, writeFile } from "fs/promises"; import { join } from "path"; +import { getLanguageStats } from "./utils"; + const scriptDir = __dirname; const rootDir = join(scriptDir, "../.."); const docsDir = join(rootDir, "docs"); -async function getLanguageStats() { - const cacheFile = join(scriptDir, ".language-stats.json"); - - // Try to read from the cache. - try { - const cacheStats = await stat(cacheFile); - const now = new Date(); - const oneDay = 24 * 60 * 60 * 1000; // milliseconds - if (cacheStats.mtimeMs < now.getTime() + oneDay) { - console.log("Reading language stats from cache."); - return JSON.parse(await readFile(cacheFile, "utf-8")); - } - } catch (e) { - if (!(e && typeof e === "object" && "code" in e && e.code === "ENOENT")) { - throw e; - } - } - - // Make the request - console.log("Reading language stats from Weblate API."); - const request = await fetch("https://hosted.weblate.org/api/components/trilium/readme/translations/"); - const stats = JSON.parse(await request.text()); - - // Update the cache - await writeFile(cacheFile, JSON.stringify(stats, null, 4)); - - return stats; -} - async function rewriteLanguageBar(readme: string) { // Filter languages by their availability. - const languageStats = await getLanguageStats(); + const languageStats = await getLanguageStats("readme"); const languagesWithCoverage: any[] = languageStats.results.filter(language => language.translated_percent > 75); const languageLinks = languagesWithCoverage .map(language => `[${language.language.name}](./${language.filename})`) diff --git a/scripts/translation/utils.ts b/scripts/translation/utils.ts new file mode 100644 index 000000000..6683edc08 --- /dev/null +++ b/scripts/translation/utils.ts @@ -0,0 +1,33 @@ +import { readFile, stat,writeFile } from "fs/promises"; +import { join } from "path"; + +const scriptDir = __dirname; + +export async function getLanguageStats(project: "readme" | "client") { + const cacheFile = join(scriptDir, `.language-stats-${project}.json`); + + // Try to read from the cache. + try { + const cacheStats = await stat(cacheFile); + const now = new Date(); + const oneDay = 24 * 60 * 60 * 1000; // milliseconds + if (cacheStats.mtimeMs < now.getTime() + oneDay) { + console.log("Reading language stats from cache."); + return JSON.parse(await readFile(cacheFile, "utf-8")); + } + } catch (e) { + if (!(e && typeof e === "object" && "code" in e && e.code === "ENOENT")) { + throw e; + } + } + + // Make the request + console.log("Reading language stats from Weblate API."); + const request = await fetch(`https://hosted.weblate.org/api/components/trilium/${project}/translations/`); + const stats = JSON.parse(await request.text()); + + // Update the cache + await writeFile(cacheFile, JSON.stringify(stats, null, 4)); + + return stats; +} From 93d50712a96c8bdedd0bdd7a897073706dffa5af Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 4 Jan 2026 23:38:34 +0200 Subject: [PATCH 22/23] chore(scripts): fix typecheck issue --- scripts/tsconfig.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json index 47304e52a..533c0c379 100644 --- a/scripts/tsconfig.json +++ b/scripts/tsconfig.json @@ -5,6 +5,7 @@ "moduleResolution": "bundler", "target": "es2023", "outDir": "dist", + "rootDir": "..", "types": [ "node", "express" @@ -12,7 +13,8 @@ "tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo" }, "include": [ - "**/*.ts" + "scripts/*.ts", + "packages/commons/src/lib/i18n.ts" ], "references": [] } From 6c2afc086c52dacb2d2bbe13533e107ef754d5ea Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 4 Jan 2026 23:38:46 +0200 Subject: [PATCH 23/23] feat(i18n): add Polish --- .../widgets/collections/calendar/index.tsx | 53 ++++++++++--------- .../src/widgets/type_widgets/MindMap.tsx | 1 + .../src/widgets/type_widgets/canvas/i18n.ts | 1 + apps/server-e2e/src/note_types/pdf.spec.ts | 18 +++++++ packages/commons/src/lib/dayjs.ts | 1 + packages/commons/src/lib/i18n.ts | 1 + 6 files changed, 50 insertions(+), 25 deletions(-) diff --git a/apps/client/src/widgets/collections/calendar/index.tsx b/apps/client/src/widgets/collections/calendar/index.tsx index 493640d25..9d1721a97 100644 --- a/apps/client/src/widgets/collections/calendar/index.tsx +++ b/apps/client/src/widgets/collections/calendar/index.tsx @@ -1,27 +1,29 @@ -import { DateSelectArg, EventChangeArg, EventMountArg, EventSourceFuncArg, LocaleInput, PluginDef } from "@fullcalendar/core/index.js"; -import { ViewModeProps } from "../interface"; -import Calendar from "./calendar"; -import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks"; import "./index.css"; -import { useNoteLabel, useNoteLabelBoolean, useResizeObserver, useSpacedUpdate, useTriliumEvent, useTriliumOption, useTriliumOptionInt } from "../../react/hooks"; -import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons"; + import { Calendar as FullCalendar } from "@fullcalendar/core"; -import { parseStartEndDateFromEvent, parseStartEndTimeFromEvent } from "./utils"; -import dialog from "../../../services/dialog"; -import { t } from "../../../services/i18n"; -import { buildEvents, buildEventsForCalendar } from "./event_builder"; -import { changeEvent, newEvent } from "./api"; -import froca from "../../../services/froca"; -import date_notes from "../../../services/date_notes"; -import appContext from "../../../components/app_context"; +import { DateSelectArg, EventChangeArg, EventMountArg, EventSourceFuncArg, LocaleInput, PluginDef } from "@fullcalendar/core/index.js"; import { DateClickArg } from "@fullcalendar/interaction"; -import FNote from "../../../entities/fnote"; -import Button, { ButtonGroup } from "../../react/Button"; -import ActionButton from "../../react/ActionButton"; +import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons"; import { RefObject } from "preact"; -import TouchBar, { TouchBarButton, TouchBarLabel, TouchBarSegmentedControl, TouchBarSpacer } from "../../react/TouchBar"; -import { openCalendarContextMenu } from "./context_menu"; +import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks"; + +import appContext from "../../../components/app_context"; +import FNote from "../../../entities/fnote"; +import date_notes from "../../../services/date_notes"; +import dialog from "../../../services/dialog"; +import froca from "../../../services/froca"; +import { t } from "../../../services/i18n"; import { isMobile } from "../../../services/utils"; +import ActionButton from "../../react/ActionButton"; +import Button, { ButtonGroup } from "../../react/Button"; +import { useNoteLabel, useNoteLabelBoolean, useResizeObserver, useSpacedUpdate, useTriliumEvent, useTriliumOption, useTriliumOptionInt } from "../../react/hooks"; +import TouchBar, { TouchBarButton, TouchBarLabel, TouchBarSegmentedControl, TouchBarSpacer } from "../../react/TouchBar"; +import { ViewModeProps } from "../interface"; +import { changeEvent, newEvent } from "./api"; +import Calendar from "./calendar"; +import { openCalendarContextMenu } from "./context_menu"; +import { buildEvents, buildEventsForCalendar } from "./event_builder"; +import { parseStartEndDateFromEvent, parseStartEndTimeFromEvent } from "./utils"; interface CalendarViewData { @@ -59,7 +61,7 @@ const CALENDAR_VIEWS = [ previousText: t("calendar.month_previous"), nextText: t("calendar.month_next") } -] +]; const SUPPORTED_CALENDAR_VIEW_TYPE = CALENDAR_VIEWS.map(v => v.type); @@ -75,6 +77,7 @@ export const LOCALE_MAPPINGS: Record Promise<{ de ru: () => import("@fullcalendar/core/locales/ru"), ja: () => import("@fullcalendar/core/locales/ja"), pt: () => import("@fullcalendar/core/locales/pt"), + pl: () => import("@fullcalendar/core/locales/pl"), "pt_br": () => import("@fullcalendar/core/locales/pt-br"), uk: () => import("@fullcalendar/core/locales/uk"), en: null, @@ -102,9 +105,9 @@ export default function CalendarView({ note, noteIds }: ViewModeProps { if (!isCalendarRoot) { return async () => await buildEvents(noteIds); - } else { - return async (e: EventSourceFuncArg) => await buildEventsForCalendar(note, e); - } + } + return async (e: EventSourceFuncArg) => await buildEventsForCalendar(note, e); + }, [isCalendarRoot, noteIds]); const plugins = usePlugins(isEditable, isCalendarRoot); @@ -178,7 +181,7 @@ function CalendarHeader({ calendarRef }: { calendarRef: RefObject calendarRef.current?.next()} />
- ) + ); } function usePlugins(isEditable: boolean, isCalendarRoot: boolean) { @@ -293,7 +296,7 @@ function useEventDisplayCustomization(parentNote: FNote) { if (promotedAttributes) { let promotedAttributesHtml = ""; for (const [name, value] of promotedAttributes) { - promotedAttributesHtml = promotedAttributesHtml + /*html*/`\ + promotedAttributesHtml = `${promotedAttributesHtml /*html*/}\ `; diff --git a/apps/client/src/widgets/type_widgets/MindMap.tsx b/apps/client/src/widgets/type_widgets/MindMap.tsx index 3b5631338..8c6b36eb7 100644 --- a/apps/client/src/widgets/type_widgets/MindMap.tsx +++ b/apps/client/src/widgets/type_widgets/MindMap.tsx @@ -37,6 +37,7 @@ const LOCALE_MAPPINGS: Record it: "it", ja: "ja", pt: "pt", + pl: null, pt_br: "pt", ro: "ro", ru: "ru", diff --git a/apps/client/src/widgets/type_widgets/canvas/i18n.ts b/apps/client/src/widgets/type_widgets/canvas/i18n.ts index 47324abfc..ba044032b 100644 --- a/apps/client/src/widgets/type_widgets/canvas/i18n.ts +++ b/apps/client/src/widgets/type_widgets/canvas/i18n.ts @@ -13,6 +13,7 @@ export const LANGUAGE_MAPPINGS: Record { await expect(attachmentsList.locator(".pdf-attachment-item")).toHaveCount(0); }); +test("Download original PDF works", async ({ page, context }) => { + const app = new App(page, context); + await app.goto(); + await app.goToNoteInNewTab("Dacia Logan.pdf"); + const pdfHelper = new PdfHelper(app); + await pdfHelper.toBeInitialized(); + + const [ download ] = await Promise.all([ + page.waitForEvent("download"), + app.currentNoteSplit.locator(".icon-action.bx.bx-download").click() + ]); + expect(download).toBeDefined(); +}); + test("Layers listing works", async ({ page, context }) => { const app = new App(page, context); await app.goto(); @@ -108,4 +122,8 @@ class PdfHelper { async expectPageToBe(expectedPageNumber: number) { await expect(this.contentFrame.locator("#pageNumber")).toHaveValue(`${expectedPageNumber}`); } + + async toBeInitialized() { + await expect(this.contentFrame.locator("#pageNumber")).toBeVisible(); + } } diff --git a/packages/commons/src/lib/dayjs.ts b/packages/commons/src/lib/dayjs.ts index fd9de6842..7ed1d8551 100644 --- a/packages/commons/src/lib/dayjs.ts +++ b/packages/commons/src/lib/dayjs.ts @@ -47,6 +47,7 @@ export const DAYJS_LOADER: Record Promise import("dayjs/locale/ku.js"), "pt_br": () => import("dayjs/locale/pt-br.js"), "pt": () => import("dayjs/locale/pt.js"), + "pl": () => import("dayjs/locale/pl.js"), "ro": () => import("dayjs/locale/ro.js"), "ru": () => import("dayjs/locale/ru.js"), "tw": () => import("dayjs/locale/zh-tw.js"), diff --git a/packages/commons/src/lib/i18n.ts b/packages/commons/src/lib/i18n.ts index 9003ae786..ac005c645 100644 --- a/packages/commons/src/lib/i18n.ts +++ b/packages/commons/src/lib/i18n.ts @@ -23,6 +23,7 @@ const UNSORTED_LOCALES = [ { id: "ja", name: "日本語", electronLocale: "ja" }, { id: "pt_br", name: "Português (Brasil)", electronLocale: "pt_BR" }, { id: "pt", name: "Português (Portugal)", electronLocale: "pt_PT" }, + { id: "pl", name: "Polski", electronLocale: "pl" }, { id: "ro", name: "Română", electronLocale: "ro" }, { id: "ru", name: "Русский", electronLocale: "ru" }, { id: "tw", name: "繁體中文", electronLocale: "zh_TW" },