From d014ae4fcf61b99040aca067711aa61c93f089e0 Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Sun, 16 Nov 2025 14:23:48 +0200
Subject: [PATCH 1/8] chore(ckeditor5): set up dependency to commons
---
packages/ckeditor5/package.json | 1 +
pnpm-lock.yaml | 57 ++-------------------------------
2 files changed, 4 insertions(+), 54 deletions(-)
diff --git a/packages/ckeditor5/package.json b/packages/ckeditor5/package.json
index a97c1079b..cf2dc5fde 100644
--- a/packages/ckeditor5/package.json
+++ b/packages/ckeditor5/package.json
@@ -6,6 +6,7 @@
"type": "module",
"main": "./src/index.ts",
"dependencies": {
+ "@triliumnext/commons": "workspace:*",
"@triliumnext/ckeditor5-admonition": "workspace:*",
"@triliumnext/ckeditor5-footnotes": "workspace:*",
"@triliumnext/ckeditor5-keyboard-marker": "workspace:*",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c422288ce..89e346f04 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -856,6 +856,9 @@ importers:
'@triliumnext/ckeditor5-mermaid':
specifier: workspace:*
version: link:../ckeditor5-mermaid
+ '@triliumnext/commons':
+ specifier: workspace:*
+ version: link:../commons
ckeditor5:
specifier: 47.2.0
version: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
@@ -15543,8 +15546,6 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.2.0
'@ckeditor/ckeditor5-upload': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-ai@47.2.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)':
dependencies:
@@ -15615,8 +15616,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-block-quote@47.2.0':
dependencies:
@@ -15627,8 +15626,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-bookmark@47.2.0':
dependencies:
@@ -15639,8 +15636,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
'@ckeditor/ckeditor5-widget': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-case-change@47.2.0':
dependencies:
@@ -15691,8 +15686,6 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-code-block@47.2.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
dependencies:
@@ -15926,8 +15919,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-editor-classic@47.2.0':
dependencies:
@@ -15937,8 +15928,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-editor-decoupled@47.2.0':
dependencies:
@@ -15948,8 +15937,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-editor-inline@47.2.0':
dependencies:
@@ -15959,8 +15946,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-editor-multi-root@47.2.0':
dependencies:
@@ -15983,8 +15968,6 @@ snapshots:
'@ckeditor/ckeditor5-table': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-emoji@47.2.0':
dependencies:
@@ -15997,8 +15980,6 @@ snapshots:
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
fuzzysort: 3.1.0
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-engine@47.2.0':
dependencies:
@@ -16041,8 +16022,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-export-word@47.2.0':
dependencies:
@@ -16067,8 +16046,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-font@47.2.0':
dependencies:
@@ -16110,8 +16087,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-heading@47.2.0':
dependencies:
@@ -16122,8 +16097,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-highlight@47.2.0':
dependencies:
@@ -16143,8 +16116,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
'@ckeditor/ckeditor5-widget': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-html-embed@47.2.0':
dependencies:
@@ -16171,8 +16142,6 @@ snapshots:
'@ckeditor/ckeditor5-widget': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-icons@47.2.0': {}
@@ -16204,8 +16173,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-indent@47.2.0':
dependencies:
@@ -16342,8 +16309,6 @@ snapshots:
'@ckeditor/ckeditor5-widget': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-minimap@47.2.0':
dependencies:
@@ -16436,8 +16401,6 @@ snapshots:
'@ckeditor/ckeditor5-paste-from-office': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-paste-from-office@47.2.0':
dependencies:
@@ -16445,8 +16408,6 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.2.0
'@ckeditor/ckeditor5-engine': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-real-time-collaboration@47.2.0(bufferutil@4.0.9)(utf-8-validate@6.0.5)':
dependencies:
@@ -16477,8 +16438,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-restricted-editing@47.2.0':
dependencies:
@@ -16488,8 +16447,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-revision-history@47.2.0':
dependencies:
@@ -16567,8 +16524,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-special-characters@47.2.0':
dependencies:
@@ -16578,8 +16533,6 @@ snapshots:
'@ckeditor/ckeditor5-ui': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-style@47.2.0':
dependencies:
@@ -16682,8 +16635,6 @@ snapshots:
'@ckeditor/ckeditor5-icons': 47.2.0
'@ckeditor/ckeditor5-ui': 47.2.0
'@ckeditor/ckeditor5-utils': 47.2.0
- transitivePeerDependencies:
- - supports-color
'@ckeditor/ckeditor5-upload@47.2.0':
dependencies:
@@ -16741,8 +16692,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.2.0
ckeditor5: 47.2.0(patch_hash=8331a09d41443b39ea1c784daaccfeb0da4f9065ed556e7de92e9c77edd9eb41)
es-toolkit: 1.39.5
- transitivePeerDependencies:
- - supports-color
'@codemirror/autocomplete@6.18.6':
dependencies:
From a10d99f93870d01e654c24c0b50dc464aecdb16e Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Sun, 16 Nov 2025 14:24:07 +0200
Subject: [PATCH 2/8] feat(ckeditor5): set up multi-language support
---
.../text/CKEditorWithWatchdog.tsx | 7 +-
.../src/widgets/type_widgets/text/config.ts | 10 +--
packages/ckeditor5/src/i18n.ts | 90 +++++++++++++++++++
packages/ckeditor5/src/index.ts | 1 +
4 files changed, 101 insertions(+), 7 deletions(-)
create mode 100644 packages/ckeditor5/src/i18n.ts
diff --git a/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx b/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx
index b7346dd9a..fd6814528 100644
--- a/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx
+++ b/apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx
@@ -1,9 +1,10 @@
import { HTMLProps, RefObject, useEffect, useImperativeHandle, useRef, useState } from "preact/compat";
import { PopupEditor, ClassicEditor, EditorWatchdog, type WatchdogConfig, CKTextEditor, TemplateDefinition } from "@triliumnext/ckeditor5";
import { buildConfig, BuildEditorOptions } from "./config";
-import { useKeyboardShortcuts, useLegacyImperativeHandlers, useNoteContext, useSyncedRef } from "../../react/hooks";
+import { useKeyboardShortcuts, useLegacyImperativeHandlers, useNoteContext, useSyncedRef, useTriliumOption } from "../../react/hooks";
import link from "../../../services/link";
import froca from "../../../services/froca";
+import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
export type BoxSize = "small" | "medium" | "full";
@@ -37,6 +38,7 @@ interface CKEditorWithWatchdogProps extends Pick, "cla
export default function CKEditorWithWatchdog({ containerRef: externalContainerRef, content, contentLanguage, className, tabIndex, isClassicEditor, watchdogRef: externalWatchdogRef, watchdogConfig, onNotificationWarning, onWatchdogStateChange, onChange, onEditorInitialized, editorApi, templates }: CKEditorWithWatchdogProps) {
const containerRef = useSyncedRef(externalContainerRef, null);
const watchdogRef = useRef(null);
+ const [ uiLanguage ] = useTriliumOption("locale");
const [ editor, setEditor ] = useState();
const { parentComponent } = useNoteContext();
@@ -156,6 +158,7 @@ export default function CKEditorWithWatchdog({ containerRef: externalContainerRe
const editor = await buildEditor(container, !!isClassicEditor, {
forceGplLicense: false,
isClassicEditor: !!isClassicEditor,
+ uiLanguage: uiLanguage as DISPLAYABLE_LOCALE_IDS,
contentLanguage: contentLanguage ?? null,
templates
});
@@ -180,7 +183,7 @@ export default function CKEditorWithWatchdog({ containerRef: externalContainerRe
watchdog.create(container);
return () => watchdog.destroy();
- }, [ contentLanguage, templates ]);
+ }, [ contentLanguage, templates, uiLanguage ]);
// React to content changes.
useEffect(() => editor?.setData(content ?? ""), [ editor, content ]);
diff --git a/apps/client/src/widgets/type_widgets/text/config.ts b/apps/client/src/widgets/type_widgets/text/config.ts
index 7f39c4ea2..a12d384ef 100644
--- a/apps/client/src/widgets/type_widgets/text/config.ts
+++ b/apps/client/src/widgets/type_widgets/text/config.ts
@@ -1,5 +1,5 @@
-import { ALLOWED_PROTOCOLS, MIME_TYPE_AUTO } from "@triliumnext/commons";
-import { buildExtraCommands, type EditorConfig, PREMIUM_PLUGINS, TemplateDefinition } from "@triliumnext/ckeditor5";
+import { ALLOWED_PROTOCOLS, DISPLAYABLE_LOCALE_IDS, MIME_TYPE_AUTO } from "@triliumnext/commons";
+import { buildExtraCommands, type EditorConfig, getCkLocale, PREMIUM_PLUGINS, TemplateDefinition } from "@triliumnext/ckeditor5";
import { getHighlightJsNameForMime } from "../../../services/mime_types.js";
import options from "../../../services/options.js";
import { ensureMimeTypesForHighlighting, isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js";
@@ -17,6 +17,7 @@ export const OPEN_SOURCE_LICENSE_KEY = "GPL";
export interface BuildEditorOptions {
forceGplLicense: boolean;
isClassicEditor: boolean;
+ uiLanguage: DISPLAYABLE_LOCALE_IDS;
contentLanguage: string | null;
templates: TemplateDefinition[];
}
@@ -161,9 +162,8 @@ export async function buildConfig(opts: BuildEditorOptions): Promise Promise<{ default: Translations }>;
+ premiumFeaturesTranslation: () => Promise<{ default: Translations }>;
+}
+
+const LOCALE_MAPPINGS: Record = {
+ en: null,
+ en_rtl: null,
+ ar: {
+ languageCode: "ar",
+ coreTranslation: () => import("ckeditor5/translations/ar.js"),
+ premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/ar.js"),
+ },
+ cn: {
+ languageCode: "zh",
+ coreTranslation: () => import("ckeditor5/translations/zh-cn.js"),
+ premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/zh-cn.js"),
+ },
+ de: {
+ languageCode: "de",
+ coreTranslation: () => import("ckeditor5/translations/de.js"),
+ premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/de.js"),
+ },
+ es: {
+ languageCode: "es",
+ coreTranslation: () => import("ckeditor5/translations/es.js"),
+ premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/es.js"),
+ },
+ fr: {
+ languageCode: "fr",
+ coreTranslation: () => import("ckeditor5/translations/fr.js"),
+ premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/fr.js"),
+ },
+ it: {
+ languageCode: "it",
+ coreTranslation: () => import("ckeditor5/translations/it.js"),
+ premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/it.js"),
+ },
+ ja: {
+ languageCode: "ja",
+ coreTranslation: () => import("ckeditor5/translations/ja.js"),
+ premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/ja.js"),
+ },
+ pt: {
+ languageCode: "pt",
+ coreTranslation: () => import("ckeditor5/translations/pt.js"),
+ premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/pt.js"),
+ },
+ pt_br: {
+ languageCode: "pt-br",
+ coreTranslation: () => import("ckeditor5/translations/pt-br.js"),
+ premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/pt-br.js"),
+ },
+ ro: {
+ languageCode: "ro",
+ coreTranslation: () => import("ckeditor5/translations/ro.js"),
+ premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/ro.js"),
+ },
+ tw: {
+ languageCode: "zh-tw",
+ coreTranslation: () => import("ckeditor5/translations/zh.js"),
+ premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/zh.js"),
+ },
+ uk: {
+ languageCode: "uk",
+ coreTranslation: () => import("ckeditor5/translations/uk.js"),
+ premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/uk.js"),
+ },
+ ru: {
+ languageCode: "ru",
+ coreTranslation: () => import("ckeditor5/translations/ru.js"),
+ premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/ru.js")
+ },
+};
+
+export default async function getCkLocale(locale: DISPLAYABLE_LOCALE_IDS): Promise> {
+ const mapping = LOCALE_MAPPINGS[locale];
+ if (!mapping) return {};
+
+ const coreTranslation = (await (mapping.coreTranslation())).default;
+ const premiumFeaturesTranslation = (await (mapping.premiumFeaturesTranslation())).default;
+ return {
+ language: mapping.languageCode,
+ translations: [ coreTranslation, premiumFeaturesTranslation ]
+ };
+}
diff --git a/packages/ckeditor5/src/index.ts b/packages/ckeditor5/src/index.ts
index 1f7be24e4..2d73ecdea 100644
--- a/packages/ckeditor5/src/index.ts
+++ b/packages/ckeditor5/src/index.ts
@@ -8,6 +8,7 @@ export { PREMIUM_PLUGINS } from "./plugins.js";
export type { EditorConfig, MentionFeed, MentionFeedObjectItem, ModelNode, ModelPosition, ModelElement, WatchdogConfig, WatchdogState } from "ckeditor5";
export type { TemplateDefinition } from "ckeditor5-premium-features";
export { default as buildExtraCommands } from "./extra_slash_commands.js";
+export { default as getCkLocale } from "./i18n.js";
// Import with sideffects to ensure that type augmentations are present.
import "@triliumnext/ckeditor5-math";
From 89228f264fa74dbdfa12f1edf8944d49a1096ab0 Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Sun, 16 Nov 2025 17:34:56 +0200
Subject: [PATCH 3/8] feat(mindmap): add support for locales
---
.../src/widgets/type_widgets/MindMap.tsx | 27 ++++++++++++++++---
1 file changed, 24 insertions(+), 3 deletions(-)
diff --git a/apps/client/src/widgets/type_widgets/MindMap.tsx b/apps/client/src/widgets/type_widgets/MindMap.tsx
index 4b3c8fe0d..715612947 100644
--- a/apps/client/src/widgets/type_widgets/MindMap.tsx
+++ b/apps/client/src/widgets/type_widgets/MindMap.tsx
@@ -1,15 +1,16 @@
import { useCallback, useEffect, useRef } from "preact/hooks";
import { TypeWidgetProps } from "./type_widget";
-import { MindElixirData, MindElixirInstance, Operation, default as VanillaMindElixir } from "mind-elixir";
+import { MindElixirData, MindElixirInstance, Operation, Options, default as VanillaMindElixir } from "mind-elixir";
import { HTMLAttributes, RefObject } from "preact";
// allow node-menu plugin css to be bundled by webpack
import nodeMenu from "@mind-elixir/node-menu";
import "mind-elixir/style";
import "@mind-elixir/node-menu/dist/style.css";
import "./MindMap.css";
-import { useEditorSpacedUpdate, useNoteLabelBoolean, useSyncedRef, useTriliumEvent, useTriliumEvents } from "../react/hooks";
+import { useEditorSpacedUpdate, useNoteLabelBoolean, useSyncedRef, useTriliumEvent, useTriliumEvents, useTriliumOption } from "../react/hooks";
import { refToJQuerySelector } from "../react/react_utils";
import utils from "../../services/utils";
+import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
const NEW_TOPIC_NAME = "";
@@ -21,6 +22,24 @@ interface MindElixirProps {
onChange?: () => void;
}
+const LOCALE_MAPPINGS: Record = {
+ ar: null,
+ cn: "zh_CN",
+ de: null,
+ en: "en",
+ en_rtl: "en",
+ es: "es",
+ fr: "fr",
+ it: "it",
+ ja: "ja",
+ pt: "pt",
+ pt_br: "pt",
+ ro: null,
+ ru: "ru",
+ tw: "zh_TW",
+ uk: null
+};
+
export default function MindMap({ note, ntxId, noteContext }: TypeWidgetProps) {
const apiRef = useRef(null);
const containerRef = useRef(null);
@@ -110,12 +129,14 @@ export default function MindMap({ note, ntxId, noteContext }: TypeWidgetProps) {
function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef: externalApiRef, onChange, editable }: MindElixirProps) {
const containerRef = useSyncedRef(externalContainerRef, null);
const apiRef = useRef(null);
+ const [ locale ] = useTriliumOption("locale");
function reinitialize() {
if (!containerRef.current) return;
const mind = new VanillaMindElixir({
el: containerRef.current,
+ locale: LOCALE_MAPPINGS[locale],
editable
});
@@ -143,7 +164,7 @@ function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef
if (data) {
apiRef.current?.init(data);
}
- }, [ editable ]);
+ }, [ editable, locale ]);
// On change listener.
useEffect(() => {
From 7d1453ffbdb9e6bdd2ac8be68cd58e38e21d2102 Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Sun, 16 Nov 2025 17:49:55 +0200
Subject: [PATCH 4/8] feat(canvas): add support for locales
---
.../widgets/type_widgets/canvas/Canvas.tsx | 23 ++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx b/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx
index 79f7f3795..968c5c994 100644
--- a/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx
+++ b/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx
@@ -1,7 +1,7 @@
import { Excalidraw } from "@excalidraw/excalidraw";
import { TypeWidgetProps } from "../type_widget";
import "@excalidraw/excalidraw/index.css";
-import { useNoteLabelBoolean } from "../../react/hooks";
+import { useNoteLabelBoolean, useTriliumOption } from "../../react/hooks";
import { useCallback, useMemo, useRef } from "preact/hooks";
import { type ExcalidrawImperativeAPI, type AppState } from "@excalidraw/excalidraw/types";
import options from "../../../services/options";
@@ -9,11 +9,30 @@ import "./Canvas.css";
import { NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types";
import { goToLinkExt } from "../../../services/link";
import useCanvasPersistence from "./persistence";
+import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
// currently required by excalidraw, in order to allows self-hosting fonts locally.
// this avoids making excalidraw load the fonts from an external CDN.
window.EXCALIDRAW_ASSET_PATH = `${window.location.pathname}/node_modules/@excalidraw/excalidraw/dist/prod`;
+const LANGUAGE_MAPPINGS: Record = {
+ ar: "ar-SA",
+ cn: "zh-CN",
+ de: "de-DE",
+ en: "en",
+ en_rtl: "en",
+ es: "es-ES",
+ fr: "fr-FR",
+ it: "it-IT",
+ ja: "ja-JP",
+ pt: "pt-PT",
+ pt_br: "pt-BR",
+ ro: "ro-RO",
+ ru: "ru-RU",
+ tw: "zh-TW",
+ uk: "uk-UA"
+};
+
export default function Canvas({ note, noteContext }: TypeWidgetProps) {
const apiRef = useRef(null);
const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly");
@@ -21,6 +40,7 @@ export default function Canvas({ note, noteContext }: TypeWidgetProps) {
const documentStyle = window.getComputedStyle(document.documentElement);
return documentStyle.getPropertyValue("--theme-style")?.trim() as AppState["theme"];
}, []);
+ const [ locale ] = useTriliumOption("locale");
const persistence = useCanvasPersistence(note, noteContext, apiRef, themeStyle, isReadOnly);
/** Use excalidraw's native zoom instead of the global zoom. */
@@ -58,6 +78,7 @@ export default function Canvas({ note, noteContext }: TypeWidgetProps) {
detectScroll={false}
handleKeyboardGlobally={false}
autoFocus={false}
+ langCode={LANGUAGE_MAPPINGS[locale]}
UIOptions={{
canvasActions: {
saveToActiveFile: false,
From 497bb352098ecc2f092a5304733207850baae243 Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Sun, 16 Nov 2025 21:03:53 +0200
Subject: [PATCH 5/8] test(canvas): test all languages are mapped correctly
---
.../widgets/type_widgets/canvas/Canvas.tsx | 20 +------------
.../widgets/type_widgets/canvas/i18n.spec.ts | 29 +++++++++++++++++++
.../src/widgets/type_widgets/canvas/i18n.ts | 19 ++++++++++++
3 files changed, 49 insertions(+), 19 deletions(-)
create mode 100644 apps/client/src/widgets/type_widgets/canvas/i18n.spec.ts
create mode 100644 apps/client/src/widgets/type_widgets/canvas/i18n.ts
diff --git a/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx b/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx
index 968c5c994..fb5cc1df5 100644
--- a/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx
+++ b/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx
@@ -9,30 +9,12 @@ import "./Canvas.css";
import { NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types";
import { goToLinkExt } from "../../../services/link";
import useCanvasPersistence from "./persistence";
-import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
+import { LANGUAGE_MAPPINGS } from "./i18n";
// currently required by excalidraw, in order to allows self-hosting fonts locally.
// this avoids making excalidraw load the fonts from an external CDN.
window.EXCALIDRAW_ASSET_PATH = `${window.location.pathname}/node_modules/@excalidraw/excalidraw/dist/prod`;
-const LANGUAGE_MAPPINGS: Record = {
- ar: "ar-SA",
- cn: "zh-CN",
- de: "de-DE",
- en: "en",
- en_rtl: "en",
- es: "es-ES",
- fr: "fr-FR",
- it: "it-IT",
- ja: "ja-JP",
- pt: "pt-PT",
- pt_br: "pt-BR",
- ro: "ro-RO",
- ru: "ru-RU",
- tw: "zh-TW",
- uk: "uk-UA"
-};
-
export default function Canvas({ note, noteContext }: TypeWidgetProps) {
const apiRef = useRef(null);
const [ isReadOnly ] = useNoteLabelBoolean(note, "readOnly");
diff --git a/apps/client/src/widgets/type_widgets/canvas/i18n.spec.ts b/apps/client/src/widgets/type_widgets/canvas/i18n.spec.ts
new file mode 100644
index 000000000..71eb3d18c
--- /dev/null
+++ b/apps/client/src/widgets/type_widgets/canvas/i18n.spec.ts
@@ -0,0 +1,29 @@
+import { LOCALES } from "@triliumnext/commons";
+import { readdirSync } from "fs";
+import { join } from "path";
+import { describe, expect, it } from "vitest";
+import { LANGUAGE_MAPPINGS } from "./i18n.js";
+
+const localeDir = join(__dirname, "../../../../../../node_modules/@excalidraw/excalidraw/dist/prod/locales");
+
+describe("Canvas i18n", () => {
+ it("all languages are mapped correctly", () => {
+ // Read the node_modules dir to obtain all the supported locales.
+ const supportedLanguageCodes = new Set();
+ for (const file of readdirSync(localeDir)) {
+ if (file.startsWith("percentages")) continue;
+ const match = file.match("^[a-z]{2,3}(?:-[A-Z]{2,3})?");
+ if (!match) continue;
+ supportedLanguageCodes.add(match[0]);
+ }
+
+ // Cross-check the locales.
+ for (const locale of LOCALES) {
+ if (locale.contentOnly || locale.devOnly) continue;
+ const languageCode = LANGUAGE_MAPPINGS[locale.id];
+ if (!supportedLanguageCodes.has(languageCode)) {
+ expect.fail(`Unable to find locale for ${locale.id} -> ${languageCode}.`)
+ }
+ }
+ });
+});
diff --git a/apps/client/src/widgets/type_widgets/canvas/i18n.ts b/apps/client/src/widgets/type_widgets/canvas/i18n.ts
new file mode 100644
index 000000000..43ee724cf
--- /dev/null
+++ b/apps/client/src/widgets/type_widgets/canvas/i18n.ts
@@ -0,0 +1,19 @@
+import type { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
+
+export const LANGUAGE_MAPPINGS: Record = {
+ ar: "ar-SA",
+ cn: "zh-CN",
+ de: "de-DE",
+ en: "en",
+ en_rtl: "en",
+ es: "es-ES",
+ fr: "fr-FR",
+ it: "it-IT",
+ ja: "ja-JP",
+ pt: "pt-PT",
+ pt_br: "pt-BR",
+ ro: "ro-RO",
+ ru: "ru-RU",
+ tw: "zh-TW",
+ uk: "uk-UA"
+};
From cc0e30e3f559404c8392df93bfb7ebbf4365b385 Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Sun, 16 Nov 2025 21:18:34 +0200
Subject: [PATCH 6/8] test(ckeditor): test all languages are mapped correctly
---
.../widgets/type_widgets/text/config.spec.ts | 39 +++++++++++++++++++
1 file changed, 39 insertions(+)
create mode 100644 apps/client/src/widgets/type_widgets/text/config.spec.ts
diff --git a/apps/client/src/widgets/type_widgets/text/config.spec.ts b/apps/client/src/widgets/type_widgets/text/config.spec.ts
new file mode 100644
index 000000000..5e85bab3b
--- /dev/null
+++ b/apps/client/src/widgets/type_widgets/text/config.spec.ts
@@ -0,0 +1,39 @@
+import { DISPLAYABLE_LOCALE_IDS, LOCALES } from "@triliumnext/commons";
+import { describe, expect, it, vi } from "vitest";
+
+vi.mock('../../../services/options.js', () => ({
+ default: {
+ get(name: string) {
+ if (name === "allowedHtmlTags") return "[]";
+ return undefined;
+ },
+ getJson: () => []
+ }
+}));
+
+describe("CK config", () => {
+ it("maps all languages correctly", async () => {
+ const { buildConfig } = await import("./config.js");
+ for (const locale of LOCALES) {
+ if (locale.contentOnly || locale.devOnly) continue;
+
+ const config = await buildConfig({
+ uiLanguage: locale.id as DISPLAYABLE_LOCALE_IDS,
+ contentLanguage: locale.id,
+ forceGplLicense: false,
+ isClassicEditor: false,
+ templates: []
+ });
+
+ let expectedLocale = locale.id.substring(0, 2);
+ if (expectedLocale === "cn") expectedLocale = "zh";
+ if (expectedLocale === "tw") expectedLocale = "zh-tw";
+
+ if (locale.id !== "en") {
+ expect((config.language as any).ui).toMatch(new RegExp(`^${expectedLocale}`));
+ expect(config.translations, locale.id).toBeDefined();
+ expect(config.translations, locale.id).toHaveLength(2);
+ }
+ }
+ });
+});
From 5281e8e5b4b6f0a434cb0de46b2dc759e06cf637 Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Sun, 16 Nov 2025 21:24:34 +0200
Subject: [PATCH 7/8] docs(dev): document adding a new locale
---
.../User Guide/Advanced Usage/Attributes.html | 24 +-
.../Attributes/Promoted Attributes.html | 112 ++-
.../User Guide/Collections/Geo Map.html | 654 +++++++++---------
.../User Guide/Collections/Kanban Board.html | 155 ++---
docs/Developer Guide/!!!meta.json | 35 +
.../Internationalisation Translat.md | 8 +-
.../Adding a new locale.md | 11 +
.../Developer Guide/Documentation.md | 2 +-
docs/User Guide/!!!meta.json | 184 ++---
9 files changed, 601 insertions(+), 584 deletions(-)
create mode 100644 docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Adding a new locale.md
diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes.html
index 3825c8af8..c52770536 100644
--- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes.html
+++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Attributes.html
@@ -5,14 +5,14 @@
In Trilium, attributes are key-value pairs assigned to notes, providing
additional metadata or functionality. There are two primary types of attributes:
-
+
Labels can
be used for a variety of purposes, such as storing metadata or configuring
the behavior of notes. Labels are also searchable, enhancing note retrieval.
For more information, including predefined labels, see Labels .
-
+
Relations define
connections between notes, similar to links. These can be used for metadata
and scripting purposes.
@@ -27,25 +27,24 @@
Conceptually there are two types of attributes (applying to both labels
and relations):
- System attributes
+ System attributes
As the name suggest, these attributes have a special meaning since they
are interpreted by Trilium. For example the color attribute
will change the color of the note as displayed in the Note Tree and
- links, and iconClass will change the icon of a note.
-
- User-defined attributes
+ href="#root/_help_oPVyFC7WL2Lp">Note Tree and links, and iconClass will
+ change the icon of a note.
+ User-defined attributes
These are free-form labels or relations that can be used by the user.
They can be used purely for categorization purposes (especially if combined
- with Search ),
+ with Search ),
or they can be given meaning through the use of Scripting .
+ href="#root/_help_CdNpE2pqjmI6">Scripting.
In practice, Trilium makes no direct distinction of whether an attribute
is a system one or a user-defined one. A label or relation is considered
a system attribute if it matches one of the built-in names (e.g. like the
aforementioned iconClass). Keep this in mind when creating
- Promoted Attributes in
+ Promoted Attributes in
order not to accidentally alter a system attribute (unless intended).
Viewing the list of attributes
Both the labels and relations for the current note are displayed in the Owned Attributes section
@@ -56,14 +55,13 @@
In the list of attributes, labels are prefixed with the # character
whereas relations are prefixed with the ~ character.
Attribute Definitions and Promoted Attributes
-Promoted Attributes create
+
Promoted Attributes create
a form-like editing experience for attributes, which makes it easy to enhancing
the organization and management of attributes
Multiplicity
Attributes in Trilium can be "multi-valued", meaning multiple attributes
with the same name can co-exist. This can be combined with Promoted Attributes to
- easily add them.
+ href="#root/_help_OFXdgB2nNk1F">Promoted Attributes to easily add them.
Attribute Inheritance
Trilium supports attribute inheritance, allowing child notes to inherit
attributes from their parents. For more information, see
The Attribute definition specifies how should this value be interpreted:
- Is it just string, or is it a date?
- Should we allow multiple values or note?
- Should we promote the attribute or not?
+ Is it just string, or is it a date?
+ Should we allow multiple values or note?
+ Should we promote the attribute or not?
Creating a new promoted attribute definition
To create a new promoted attribute:
- Go to a note.
- Go to Owned Attributes in the Ribbon .
- Press the + button.
- Select either Add new label definition or Add new relation definition .
- Select the name which will be name of the label or relation that will
- be created when the promoted attribute is edited.
- Ensure Promoted is checked in order to display it at the top of
- notes.
- Optionally, choose an Alias which will be displayed next to the
- promoted attribute instead of the attribute name. Generally it's best to
- choose a “user-friendly” name since it can contain spaces and other characters
- which are not supported as attribute names.
- Check Inheritable to apply it to this note and all its descendants.
- To keep it only for the current note, un-check it.
- Press “Save & Close” to apply the changes.
+ Go to a note.
+ Go to Owned Attributes in the Ribbon .
+ Press the + button.
+ Select either Add new label definition or Add new relation definition .
+ Select the name which will be name of the label or relation that will
+ be created when the promoted attribute is edited.
+ Ensure Promoted is checked in order to display it at the top of
+ notes.
+ Optionally, choose an Alias which will be displayed next to the
+ promoted attribute instead of the attribute name. Generally it's best to
+ choose a “user-friendly” name since it can contain spaces and other characters
+ which are not supported as attribute names.
+ Check Inheritable to apply it to this note and all its descendants.
+ To keep it only for the current note, un-check it.
+ Press “Save & Close” to apply the changes.
How attribute definitions actually work
When a new promoted attribute definition is created, it creates a corresponding
@@ -54,37 +52,37 @@
The only purpose of the attribute definition is to set up a template.
If the attribute was marked as promoted, then it's also displayed to the
user for easy editing.
-
-
-
-
-
-
-
-
-
-
-
-
-
- Notice how the promoted attribute definition only creates a “Due date”
- box above the text content.
-
-
-
-
-
-
-
- Once a value is set by the user, a new label (or relation, depending on
- the type) is created. The name of the attribute matches one set when creating
- the promoted attribute.
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Notice how the promoted attribute definition only creates a “Due date”
+ box above the text content.
+
+
+
+
+
+
+
+ Once a value is set by the user, a new label (or relation, depending on
+ the type) is created. The name of the attribute matches one set when creating
+ the promoted attribute.
+
+
+
So there's one attribute for value and one for definition. But notice
how an definition attribute can be made Inheritable ,
meaning that it's also applied to all descendant notes. In this case, the
@@ -95,22 +93,22 @@
to be able to easily alter them.
Here are a few practical examples:
- Collections already
+ Collections already
make use of this practice, for example:
- Calendars add “Start Date”, “End Date”, “Start Time” and “End Time” as
+ Calendars add “Start Date”, “End Date”, “Start Time” and “End Time” as
promoted attributes. These map to system attributes such as startDate which
are then interpreted by the calendar view.
- Presentation adds
+ Presentation adds
a “Background” promoted attribute for each of the slide to easily be able
to customize.
- The Trilium documentation (which is edited in Trilium) uses a promoted
+ The Trilium documentation (which is edited in Trilium) uses a promoted
attribute to be able to easily edit the #shareAlias (see
Sharing ) in order to form clean URLs.
- If you always edit a particular system attribute such as #color,
+ class="reference-link" href="#root/_help_R9pX4DGra2Vt">Sharing) in order to form clean URLs.
+ If you always edit a particular system attribute such as #color,
simply create a promoted attribute for it to make it easier.
Inverse relation
diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Geo Map.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Geo Map.html
index 58f9014d1..e7e3c73f6 100644
--- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Geo Map.html
+++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Geo Map.html
@@ -1,11 +1,9 @@
Creating a new geo map
-
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
- Right click on any note on the note tree and select Insert child note → Geo Map (beta) .
-
-
- 2
-
-
-
-
-
- By default the map will be empty and will show the entire world.
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+ Right click on any note on the note tree and select Insert child note → Geo Map (beta) .
+
+
+ 2
+
+
+
+
+
+ By default the map will be empty and will show the entire world.
+
+
+
+
Repositioning the map
- Click and drag the map in order to move across the map.
- Use the mouse wheel, two-finger gesture on a touchpad or the +/- buttons
+ Click and drag the map in order to move across the map.
+ Use the mouse wheel, two-finger gesture on a touchpad or the +/- buttons
on the top-left to adjust the zoom.
The position on the map and the zoom are saved inside the map note and
restored when visiting again the note.
Adding a marker using the map
Adding a new note using the plus button
-
-
-
-
-
-
-
-
-
-
-
- 1
- To create a marker, first navigate to the desired point on the map. Then
- press the
- button in the Floating buttons (top-right)
- area.
-
- If the button is not visible, make sure the button section is visible
- by pressing the chevron button (
- ) in the top-right of the map.
-
-
-
- 2
-
-
-
- Once pressed, the map will enter in the insert mode, as illustrated by
- the notification.
-
- Simply click the point on the map where to place the marker, or the Escape
- key to cancel.
-
-
- 3
-
-
-
- Enter the name of the marker/note to be created.
-
-
- 4
-
-
-
- Once confirmed, the marker will show up on the map and it will also be
- displayed as a child note of the map.
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ 1
+ To create a marker, first navigate to the desired point on the map. Then
+ press the
+ button in the Floating buttons (top-right)
+ area.
+
+ If the button is not visible, make sure the button section is visible
+ by pressing the chevron button (
+ ) in the top-right of the map.
+
+
+
+ 2
+
+
+
+ Once pressed, the map will enter in the insert mode, as illustrated by
+ the notification.
+
+ Simply click the point on the map where to place the marker, or the Escape
+ key to cancel.
+
+
+ 3
+
+
+
+ Enter the name of the marker/note to be created.
+
+
+ 4
+
+
+
+ Once confirmed, the marker will show up on the map and it will also be
+ displayed as a child note of the map.
+
+
+
+
Adding a new note using the contextual menu
- Right click anywhere on the map, where to place the newly created marker
+ Right click anywhere on the map, where to place the newly created marker
(and corresponding note).
- Select Add a marker at this location .
- Enter the name of the neNote Tree wly
+ Select Add a marker at this location .
+ Enter the name of the neNote Tree wly
created note.
- The map should be updated with the new marker.
+ The map should be updated with the new marker.
Adding an existing note on note from the note tree
- Select the desired note in the Note Tree .
- Hold the mouse on the note and drag it to the map to the desired location.
- The map should be updated with the new marker.
+ Select the desired note in the Note Tree .
+ Hold the mouse on the note and drag it to the map to the desired location.
+ The map should be updated with the new marker.
This works for:
- Notes that are not part of the geo map, case in which a clone will
+ Notes that are not part of the geo map, case in which a clone will
be created.
- Notes that are a child of the geo map but not yet positioned on the map.
- Notes that are a child of the geo map and also positioned, case in which
+ Notes that are a child of the geo map but not yet positioned on the map.
+ Notes that are a child of the geo map and also positioned, case in which
the marker will be relocated to the new position.
@@ -145,10 +138,8 @@
How the location of the markers is stored
The location of a marker is stored in the #geolocation attribute
of the child notes:
-
-
-
+
This value can be added manually if needed. The value of the attribute
is made up of the latitude and longitude separated by a comma.
Repositioning markers
@@ -160,17 +151,16 @@
page (Ctrl +R ) to cancel it.
Interaction with the markers
- Hovering over a marker will display a Note Tooltip with
+ Hovering over a marker will display a Note Tooltip with
the content of the note it belongs to.
- Clicking on the note title in the tooltip will navigate to the note in
+ Clicking on the note title in the tooltip will navigate to the note in
the current view.
- Middle-clicking the marker will open the note in a new tab.
- Right-clicking the marker will open a contextual menu (as described below).
- If the map is in read-only mode, clicking on a marker will open a
+ Middle-clicking the marker will open the note in a new tab.
+ Right-clicking the marker will open a contextual menu (as described below).
+ If the map is in read-only mode, clicking on a marker will open a
Quick edit popup for the corresponding note.
@@ -178,24 +168,24 @@
It's possible to press the right mouse button to display a contextual
menu.
- If right-clicking an empty section of the map (not on a marker), it allows
+ If right-clicking an empty section of the map (not on a marker), it allows
to:
- Displays the latitude and longitude. Clicking this option will copy them
+ Displays the latitude and longitude. Clicking this option will copy them
to the clipboard.
- Open the location using an external application (if the operating system
+ Open the location using an external application (if the operating system
supports it).
- Adding a new marker at that location.
+ Adding a new marker at that location.
- If right-clicking on a marker, it allows to:
+ If right-clicking on a marker, it allows to:
- Displays the latitude and longitude. Clicking this option will copy them
+ Displays the latitude and longitude. Clicking this option will copy them
to the clipboard.
- Open the location using an external application (if the operating system
+ Open the location using an external application (if the operating system
supports it).
- Open the note in a new tab, split or window.
- Remove the marker from the map, which will remove the #geolocation attribute
+ Open the note in a new tab, split or window.
+ Remove the marker from the map, which will remove the #geolocation attribute
of the note. To add it back again, the coordinates have to be manually
added back in.
@@ -215,215 +205,209 @@
The value of the attribute is made up of the latitude and longitude separated
by a comma.
Adding from Google Maps
-
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
- Go to Google Maps on the web and look for a desired location, right click
- on it and a context menu will show up.
-
- Simply click on the first item displaying the coordinates and they will
- be copied to clipboard.
-
- Then paste the value inside the text box into the #geolocation attribute
- of a child note of the map (don't forget to surround the value with a " character).
-
-
- 2
-
-
-
-
-
- In Trilium, create a child note under the map.
-
-
- 3
-
-
-
-
-
- And then go to Owned Attributes and type #geolocation=", then
- paste from the clipboard as-is and then add the ending " character.
- Press Enter to confirm and the map should now be updated to contain the
- new note.
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+ Go to Google Maps on the web and look for a desired location, right click
+ on it and a context menu will show up.
+
+ Simply click on the first item displaying the coordinates and they will
+ be copied to clipboard.
+
+ Then paste the value inside the text box into the #geolocation attribute
+ of a child note of the map (don't forget to surround the value with a " character).
+
+
+ 2
+
+
+
+
+
+ In Trilium, create a child note under the map.
+
+
+ 3
+
+
+
+
+
+ And then go to Owned Attributes and type #geolocation=", then
+ paste from the clipboard as-is and then add the ending " character.
+ Press Enter to confirm and the map should now be updated to contain the
+ new note.
+
+
+
+
Adding from OpenStreetMap
Similarly to the Google Maps approach:
-
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
- Go to any location on openstreetmap.org and right click to bring up the
- context menu. Select the “Show address” item.
-
-
- 2
-
-
-
- The address will be visible in the top-left of the screen, in the place
- of the search bar.
-
- Select the coordinates and copy them into the clipboard.
-
-
- 3
-
-
-
- Simply paste the value inside the text box into the #geolocation attribute
- of a child note of the map and then it should be displayed on the map.
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+ Go to any location on openstreetmap.org and right click to bring up the
+ context menu. Select the “Show address” item.
+
+
+ 2
+
+
+
+ The address will be visible in the top-left of the screen, in the place
+ of the search bar.
+
+ Select the coordinates and copy them into the clipboard.
+
+
+ 3
+
+
+
+ Simply paste the value inside the text box into the #geolocation attribute
+ of a child note of the map and then it should be displayed on the map.
+
+
+
+
Adding GPS tracks (.gpx)
Trilium has basic support for displaying GPS tracks on the geo map.
-
-
-
-
-
-
-
-
-
-
-
- 1
-
-
-
-
-
- To add a track, simply drag & drop a .gpx file inside the geo map
- in the note tree.
-
-
- 2
-
-
-
-
-
- In order for the file to be recognized as a GPS track, it needs to show
- up as application/gpx+xml in the File type field.
-
-
- 3
-
-
-
-
-
- When going back to the map, the track should now be visible.
-
- The start and end points of the track are indicated by the two blue markers.
-
-
-
-
-
- The starting point of the track will be displayed as a marker, with the
- name of the note underneath. The start marker will also respect the icon
- and the color of the note. The end marker is displayed with
- a distinct icon.
- If the GPX contains waypoints, they will also be displayed. If they have
- a name, it is displayed when hovering over it with the mouse.
-
- Read-only mode
- When a map is in read-only all editing features will be disabled such
- as:
-
- The add button in the Floating buttons .
- Dragging markers.
- Editing from the contextual menu (removing locations or adding new items).
-
- To enable read-only mode simply press the Lock icon from the
- Floating buttons . To disable it, press the button again.
- Configuration
- Map Style
- The styling of the map can be adjusted in the Collection Properties tab
- in the Ribbon or
- manually via the #map:style attribute.
- The geo map comes with two different types of styles:
-
- Raster styles
-
- For these styles the map is represented as a grid of images at different
- zoom levels. This is the traditional way OpenStreetMap used to work.
- Zoom is slightly restricted.
- Currently, the only raster theme is the original OpenStreetMap style.
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+ To add a track, simply drag & drop a .gpx file inside the geo map
+ in the note tree.
+
+
+ 2
+
+
+
+
+
+ In order for the file to be recognized as a GPS track, it needs to show
+ up as application/gpx+xml in the File type field.
+
+
+ 3
+
+
+
+
+
+ When going back to the map, the track should now be visible.
+
+ The start and end points of the track are indicated by the two blue markers.
+
+
+
+
+ The starting point of the track will be displayed as a marker, with the
+ name of the note underneath. The start marker will also respect the icon
+ and the color of the note. The end marker is displayed with
+ a distinct icon.
+ If the GPX contains waypoints, they will also be displayed. If they have
+ a name, it is displayed when hovering over it with the mouse.
+
+Read-only mode
+When a map is in read-only all editing features will be disabled such
+ as:
+
+ The add button in the Floating buttons .
+ Dragging markers.
+ Editing from the contextual menu (removing locations or adding new items).
+
+To enable read-only mode simply press the Lock icon from the
+ Floating buttons . To disable it, press the button again.
+Configuration
+Map Style
+The styling of the map can be adjusted in the Collection Properties tab
+ in the Ribbon or
+ manually via the #map:style attribute.
+The geo map comes with two different types of styles:
+
+ Raster styles
+
+ For these styles the map is represented as a grid of images at different
+ zoom levels. This is the traditional way OpenStreetMap used to work.
+ Zoom is slightly restricted.
+ Currently, the only raster theme is the original OpenStreetMap style.
-
- Vector styles
-
- Vector styles are not represented as images, but as geometrical shapes.
- This makes the rendering much smoother, especially when zooming and looking
- at the building edges, for example.
- The map can be zoomed in much further.
- These come both in a light and a dark version.
- The vector styles come from VersaTiles ,
- a free and open-source project providing map tiles based on OpenStreetMap.
-
-
-
-
- Scale
- Activating this option via the Ribbon or
- manually via #map:scale will display an indicator in the bottom-left
- of the scale of the map.
- Troubleshooting
-
-
-
- Grid-like artifacts on the map
- This occurs if the application is not at 100% zoom which causes the pixels
- of the map to not render correctly due to fractional scaling. The only
- possible solution is to set the UI zoom at 100% (default keyboard shortcut
- is Ctrl +0 ).
\ No newline at end of file
+
+ Vector styles
+
+ Vector styles are not represented as images, but as geometrical shapes.
+ This makes the rendering much smoother, especially when zooming and looking
+ at the building edges, for example.
+ The map can be zoomed in much further.
+ These come both in a light and a dark version.
+ The vector styles come from VersaTiles ,
+ a free and open-source project providing map tiles based on OpenStreetMap.
+
+
+
+
+Scale
+Activating this option via the Ribbon or
+ manually via #map:scale will display an indicator in the bottom-left
+ of the scale of the map.
+Troubleshooting
+
+
+
+
+Grid-like artifacts on the map
+This occurs if the application is not at 100% zoom which causes the pixels
+ of the map to not render correctly due to fractional scaling. The only
+ possible solution is to set the UI zoom at 100% (default keyboard shortcut
+ is Ctrl +0 ).
\ No newline at end of file
diff --git a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Kanban Board.html b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Kanban Board.html
index 30dc5340c..de9b60c9c 100644
--- a/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Kanban Board.html
+++ b/apps/server/src/assets/doc_notes/en/User Guide/User Guide/Collections/Kanban Board.html
@@ -15,90 +15,86 @@
Interaction
Working with columns
- Create a new column by pressing Add Column near the last column.
+ Create a new column by pressing Add Column near the last column.
- Once pressed, a text box will be displayed to set the name of the column.
+ Once pressed, a text box will be displayed to set the name of the column.
Press Enter to confirm, or Escape to dismiss.
- To reorder a column, simply hold the mouse over the title and drag it
+ To reorder a column, simply hold the mouse over the title and drag it
to the desired position.
- To delete a column, right click on its title and select Delete column .
- To rename a column, click on the note title.
+ To delete a column, right click on its title and select Delete column .
+ To rename a column, click on the note title.
- Press Enter to confirm.
- Upon renaming a column, the corresponding status attribute of all its
+ Press Enter to confirm.
+ Upon renaming a column, the corresponding status attribute of all its
notes will be changed in bulk.
-
- If there are many columns, use the mouse wheel to scroll.
+
+ If there are many columns, use the mouse wheel to scroll.
Working with notes
- Create a new note in any column by pressing New item
+ Create a new note in any column by pressing New item
- Enter the name of the note and press Enter or click away. To
+ Enter the name of the note and press Enter or click away. To
dismiss the creation of a new note, simply press Escape or leave
the name empty.
- Once created, the new note will have an attribute (status label
+ Once created, the new note will have an attribute (status label
by default) set to the name of the column.
- To open the note, simply click on it.
- To change the title of the note directly from the board, hover the mouse
+ To open the note, simply click on it.
+ To change the title of the note directly from the board, hover the mouse
over its card and press the edit button on the right.
- To change the state of a note, simply drag a note from one column to the
+ To change the state of a note, simply drag a note from one column to the
other to change its state.
- The order of the notes in each column corresponds to their position in
+ The order of the notes in each column corresponds to their position in
the tree.
- It's possible to reorder notes simply by dragging them to the desired
+ It's possible to reorder notes simply by dragging them to the desired
position within the same columns.
- It's also possible to drag notes across columns, at the desired position.
+ It's also possible to drag notes across columns, at the desired position.
- For more options, right click on a note to display a context menu with
+ For more options, right click on a note to display a context menu with
the following options:
- Open the note in a new tab/split/window or quick edit.
- Move the note to any column.
- Insert a new note above/below the current one.
- Archive/unarchive the current note.
- Delete the current note.
+ Open the note in a new tab/split/window or quick edit.
+ Move the note to any column.
+ Insert a new note above/below the current one.
+ Archive/unarchive the current note.
+ Delete the current note.
- If there are many notes within the column, move the mouse over the column
+ If there are many notes within the column, move the mouse over the column
and use the mouse wheel to scroll.
Working with the note tree
It's also possible to add items on the board using the Note Tree .
- Select the desired note in the Note Tree .
- Hold the mouse on the note and drag it to the to the desired column.
+ Select the desired note in the Note Tree .
+ Hold the mouse on the note and drag it to the to the desired column.
This works for:
- Notes that are not children of the board, case in which a clone will
+ Notes that are not children of the board, case in which a clone will
be created.
- Notes that are children of the board, but not yet assigned on the board.
- Notes that are children of the board, case in which they will be moved
+ Notes that are children of the board, but not yet assigned on the board.
+ Notes that are children of the board, case in which they will be moved
to the new column.
Keyboard interaction
The board view has mild support for keyboard-based navigation:
- Use Tab and Shift +Tab to navigate between
+ Use Tab and Shift +Tab to navigate between
column titles, notes and the “New item” button for each of the columns,
in sequential order.
- To rename a column or a note, press F2 while it is focused.
- To open a specific note or create a new item, press Enter while
+ To rename a column or a note, press F2 while it is focused.
+ To open a specific note or create a new item, press Enter while
it is focused.
- To dismiss a rename of a note or a column, press Escape .
+ To dismiss a rename of a note or a column, press Escape .
Configuration
Displaying custom attributes
@@ -109,33 +105,30 @@
Note attributes can be displayed on the board to enhance it with custom
information such as adding a Due date for your tasks.
This feature works exclusively via attribute definitions (Promoted Attributes ). The easiest
- way to add these is:
+ href="#root/_help_OFXdgB2nNk1F">Promoted Attributes). The easiest way to
+ add these is:
- Go to board note.
- In the ribbon select Owned Attributes → plus button → Add new label/relation definition .
- Configure the attribute as desired.
- Check Inheritable to make it applicable to child notes automatically.
+ Go to board note.
+ In the ribbon select Owned Attributes → plus button → Add new label/relation definition .
+ Configure the attribute as desired.
+ Check Inheritable to make it applicable to child notes automatically.
After creating the attribute, click on a note and fill in the promoted
attributes which should then reflect inside the board.
Of note:
- Both promoted and non-promoted attribute definitions are supported. The
+ Both promoted and non-promoted attribute definitions are supported. The
only difference is that non-promoted attributes don't have an “Alias” for
assigning a custom name.
- Both “Single value” and “Multi value” attributes are supported. In case
+ Both “Single value” and “Multi value” attributes are supported. In case
of multi-value, a badge is displayed for every instance of the attribute.
- All label types are supported, including dates, booleans and URLs.
- Relation attributes are also supported as well, showing a link with the
- target note title and icon.
- Currently, it's not possible to adjust which promoted attributes are displayed,
- since all promoted attributes will be displayed (except the board:groupBy one).
- There are plans to improve upon this being able to hide promoted attributes
- individually.
+ All label types are supported, including dates, booleans and URLs.
+ Relation attributes are also supported as well, showing a link with the
+ target note title and icon.
+ Currently, it's not possible to adjust which promoted attributes are displayed,
+ since all promoted attributes will be displayed (except the board:groupBy one).
+ There are plans to improve upon this being able to hide promoted attributes
+ individually.
Grouping by another label
By default, the label used to group the notes is #status.
@@ -147,36 +140,38 @@
-A more advanced use-case is grouping by Relations .
+A more advanced use-case is grouping by Relations .
During this mode:
- The columns represent the target notes of a relation.
- When creating a new column, a note is selected instead of a column name.
- The column icon will match the target note.
- Moving notes between columns will change its relation.
- Renaming an existing column will change the target note of all the notes
- in that column.
+ The columns represent the target notes of a relation.
+ When creating a new column, a note is selected instead of a column name.
+ The column icon will match the target note.
+ Moving notes between columns will change its relation.
+ Renaming an existing column will change the target note of all the notes
+ in that column.
Using relations instead of labels has some benefits:
- The status/grouping of the notes is visible outside the Kanban board,
- for example on the Note Map .
- Columns can have icons.
- Renaming columns is less intensive since it simply involves changing the
- note title of the target note instead of having to do a bulk rename.
+ The status/grouping of the notes is visible outside the Kanban board,
+ for example on the Note Map .
+ Columns can have icons.
+ Renaming columns is less intensive since it simply involves changing the
+ note title of the target note instead of having to do a bulk rename.
To do so:
- First, create a Kanban board from scratch and not a template:
- Assign #viewType=board #hidePromotedAttributes to emulate the
- default template.
- Set #board:groupBy to the name of a relation to group by, including the ~ prefix (e.g. ~status).
-
- Optionally, use Promoted Attributes for
- easy status change within the note:
#relation:status(inheritable)="promoted,alias=Status,single"
-
+
+ First, create a Kanban board from scratch and not a template:
+
+
+ Assign #viewType=board #hidePromotedAttributes to emulate the
+ default template.
+
+
+ Set #board:groupBy to the name of a relation to group by, including the **~** prefix (e.g. ~status).
+
+
+ Optionally, use Promoted Attributes for
+ easy status change within the note:
#relation:status(inheritable)="promoted,alias=Status,single"
+
\ No newline at end of file
diff --git a/docs/Developer Guide/!!!meta.json b/docs/Developer Guide/!!!meta.json
index cdf47264f..9e479b0cc 100644
--- a/docs/Developer Guide/!!!meta.json
+++ b/docs/Developer Guide/!!!meta.json
@@ -1974,6 +1974,13 @@
"value": "i18n",
"isInheritable": false,
"position": 20
+ },
+ {
+ "type": "relation",
+ "name": "internalLink",
+ "value": "lXjOyKpUSKgE",
+ "isInheritable": false,
+ "position": 30
}
],
"format": "markdown",
@@ -2071,6 +2078,34 @@
"format": "markdown",
"dataFileName": "Server translations.md",
"attachments": []
+ },
+ {
+ "isClone": false,
+ "noteId": "lXjOyKpUSKgE",
+ "notePath": [
+ "jdjRLhLV3TtI",
+ "yeqU0zo0ZQ83",
+ "TLXJwBDo8Rdv",
+ "lXjOyKpUSKgE"
+ ],
+ "title": "Adding a new locale",
+ "notePosition": 40,
+ "prefix": null,
+ "isExpanded": false,
+ "type": "text",
+ "mime": "text/html",
+ "attributes": [
+ {
+ "type": "label",
+ "name": "shareAlias",
+ "value": "new-locale",
+ "isInheritable": false,
+ "position": 20
+ }
+ ],
+ "format": "markdown",
+ "dataFileName": "Adding a new locale.md",
+ "attachments": []
}
]
},
diff --git a/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translat.md b/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translat.md
index 7ae59016f..2449b6c72 100644
--- a/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translat.md
+++ b/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translat.md
@@ -15,7 +15,7 @@ One important aspect is the fact that we are using a key-based approach. This me
The key-based approach allows a hierarchical structure. For example, a key of `about.title` would be added in `translation.json` as follows:
-```json
+```
{
"about": {
"title": "About Trilium Notes"
@@ -27,11 +27,7 @@ Follow the Adding a new locale .
### Changing the language
diff --git a/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Adding a new locale.md b/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Adding a new locale.md
new file mode 100644
index 000000000..e36815b90
--- /dev/null
+++ b/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translations/Adding a new locale.md
@@ -0,0 +1,11 @@
+# Adding a new locale
+Once the Weblate translations for a single language have reached ~50% in coverage, it's time to add it to the application.
+
+To do so:
+
+1. In `packages/commons` look for `i18n.ts` and add a new entry to `UNSORTED_LOCALES` for the language.
+2. In `apps/server` look for `services/i18n.ts` and add a mapping for the new language in `DAYJS_LOADER`. Sort the entire list.
+3. In `apps/client`, look for `collections/calendar/index.tsx` and modify `LOCALE_MAPPINGS` to add support to the new language.
+4. In `apps/client`, look for `widgets/type_widgets/canvas/i18n.ts` and modify `LANGUAGE_MAPPINGS`. A unit test ensures that the language is actually loadable.
+5. In `apps/client`, look for `widgets/type_widgets/MindMap.tsx` and modify `LOCALE_MAPPINGS`. The type definitions should already validate if the new value is supported by Mind Elixir.
+6. In `packages/ckeditor5`, look for `i18n.ts` and modify `LOCALE_MAPPINGS`. The import validation should already check if the new value is supported by CKEditor, and there's also a test to ensure it.
\ No newline at end of file
diff --git a/docs/Developer Guide/Developer Guide/Documentation.md b/docs/Developer Guide/Developer Guide/Documentation.md
index 5f9f17e5f..617684bfd 100644
--- a/docs/Developer Guide/Developer Guide/Documentation.md
+++ b/docs/Developer Guide/Developer Guide/Documentation.md
@@ -1,5 +1,5 @@
# Documentation
-There are multiple types of documentation for Trilium:
+There are multiple types of documentation for Trilium:
* The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing F1 .
* The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.
diff --git a/docs/User Guide/!!!meta.json b/docs/User Guide/!!!meta.json
index f859cd689..40a40e9fc 100644
--- a/docs/User Guide/!!!meta.json
+++ b/docs/User Guide/!!!meta.json
@@ -9872,23 +9872,16 @@
"position": 10
},
{
- "type": "label",
- "name": "iconClass",
- "value": "bx bx-columns",
- "isInheritable": false,
- "position": 10
- },
- {
- "type": "label",
- "name": "shareAlias",
- "value": "kanban-board",
+ "type": "relation",
+ "name": "internalLink",
+ "value": "oPVyFC7WL2Lp",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
- "value": "Cq5X6iKQop6R",
+ "value": "IakOLONlIfGI",
"isInheritable": false,
"position": 30
},
@@ -9902,23 +9895,30 @@
{
"type": "relation",
"name": "internalLink",
- "value": "bdUJEHsAPYQR",
+ "value": "Cq5X6iKQop6R",
"isInheritable": false,
"position": 50
},
{
"type": "relation",
"name": "internalLink",
- "value": "oPVyFC7WL2Lp",
+ "value": "bdUJEHsAPYQR",
"isInheritable": false,
"position": 60
},
{
- "type": "relation",
- "name": "internalLink",
- "value": "IakOLONlIfGI",
+ "type": "label",
+ "name": "iconClass",
+ "value": "bx bx-columns",
"isInheritable": false,
- "position": 70
+ "position": 10
+ },
+ {
+ "type": "label",
+ "name": "shareAlias",
+ "value": "kanban-board",
+ "isInheritable": false,
+ "position": 20
}
],
"format": "markdown",
@@ -9968,59 +9968,73 @@
{
"type": "relation",
"name": "internalLink",
- "value": "KSZ04uQ2D1St",
+ "value": "zEY4DaJG4YT5",
"isInheritable": false,
"position": 10
},
{
"type": "relation",
"name": "internalLink",
- "value": "0ESUbbAxVnoK",
+ "value": "OFXdgB2nNk1F",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
- "value": "XpOYSgsLkTJy",
+ "value": "KSZ04uQ2D1St",
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "internalLink",
- "value": "oPVyFC7WL2Lp",
+ "value": "0ESUbbAxVnoK",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
- "value": "IakOLONlIfGI",
+ "value": "XpOYSgsLkTJy",
"isInheritable": false,
"position": 50
},
{
"type": "relation",
"name": "internalLink",
- "value": "lgKX7r3aL30x",
+ "value": "oPVyFC7WL2Lp",
"isInheritable": false,
"position": 60
},
{
"type": "relation",
"name": "internalLink",
- "value": "ZjLYv08Rp3qC",
+ "value": "IakOLONlIfGI",
"isInheritable": false,
"position": 70
},
{
"type": "relation",
"name": "internalLink",
- "value": "BlN9DFI679QC",
+ "value": "lgKX7r3aL30x",
"isInheritable": false,
"position": 80
},
+ {
+ "type": "relation",
+ "name": "internalLink",
+ "value": "ZjLYv08Rp3qC",
+ "isInheritable": false,
+ "position": 90
+ },
+ {
+ "type": "relation",
+ "name": "internalLink",
+ "value": "BlN9DFI679QC",
+ "isInheritable": false,
+ "position": 100
+ },
{
"type": "label",
"name": "iconClass",
@@ -10034,20 +10048,6 @@
"value": "geomap",
"isInheritable": false,
"position": 90
- },
- {
- "type": "relation",
- "name": "internalLink",
- "value": "zEY4DaJG4YT5",
- "isInheritable": false,
- "position": 100
- },
- {
- "type": "relation",
- "name": "internalLink",
- "value": "OFXdgB2nNk1F",
- "isInheritable": false,
- "position": 110
}
],
"format": "markdown",
@@ -11240,24 +11240,45 @@
{
"type": "relation",
"name": "internalLink",
- "value": "BlN9DFI679QC",
+ "value": "oPVyFC7WL2Lp",
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "internalLink",
- "value": "OFXdgB2nNk1F",
+ "value": "eIg8jdvaoNNd",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
- "value": "bwZpz2ajCEwO",
+ "value": "CdNpE2pqjmI6",
"isInheritable": false,
"position": 50
},
+ {
+ "type": "relation",
+ "name": "internalLink",
+ "value": "OFXdgB2nNk1F",
+ "isInheritable": false,
+ "position": 60
+ },
+ {
+ "type": "relation",
+ "name": "internalLink",
+ "value": "BlN9DFI679QC",
+ "isInheritable": false,
+ "position": 70
+ },
+ {
+ "type": "relation",
+ "name": "internalLink",
+ "value": "bwZpz2ajCEwO",
+ "isInheritable": false,
+ "position": 80
+ },
{
"type": "label",
"name": "shareAlias",
@@ -11271,27 +11292,6 @@
"value": "bx bx-list-check",
"isInheritable": false,
"position": 110
- },
- {
- "type": "relation",
- "name": "internalLink",
- "value": "oPVyFC7WL2Lp",
- "isInheritable": false,
- "position": 120
- },
- {
- "type": "relation",
- "name": "internalLink",
- "value": "eIg8jdvaoNNd",
- "isInheritable": false,
- "position": 130
- },
- {
- "type": "relation",
- "name": "internalLink",
- "value": "CdNpE2pqjmI6",
- "isInheritable": false,
- "position": 140
}
],
"format": "markdown",
@@ -11740,10 +11740,38 @@
{
"type": "relation",
"name": "internalLink",
- "value": "bwZpz2ajCEwO",
+ "value": "BlN9DFI679QC",
"isInheritable": false,
"position": 20
},
+ {
+ "type": "relation",
+ "name": "internalLink",
+ "value": "bwZpz2ajCEwO",
+ "isInheritable": false,
+ "position": 30
+ },
+ {
+ "type": "relation",
+ "name": "internalLink",
+ "value": "GTwFsgaA0lCt",
+ "isInheritable": false,
+ "position": 40
+ },
+ {
+ "type": "relation",
+ "name": "internalLink",
+ "value": "zP3PMqaG71Ct",
+ "isInheritable": false,
+ "position": 50
+ },
+ {
+ "type": "relation",
+ "name": "internalLink",
+ "value": "R9pX4DGra2Vt",
+ "isInheritable": false,
+ "position": 60
+ },
{
"type": "label",
"name": "shareAlias",
@@ -11757,34 +11785,6 @@
"value": "bx bx-table",
"isInheritable": false,
"position": 20
- },
- {
- "type": "relation",
- "name": "internalLink",
- "value": "BlN9DFI679QC",
- "isInheritable": false,
- "position": 50
- },
- {
- "type": "relation",
- "name": "internalLink",
- "value": "GTwFsgaA0lCt",
- "isInheritable": false,
- "position": 60
- },
- {
- "type": "relation",
- "name": "internalLink",
- "value": "zP3PMqaG71Ct",
- "isInheritable": false,
- "position": 70
- },
- {
- "type": "relation",
- "name": "internalLink",
- "value": "R9pX4DGra2Vt",
- "isInheritable": false,
- "position": 80
}
],
"format": "markdown",
From 17298edfcc316ce31bd158f993ca09b47d954147 Mon Sep 17 00:00:00 2001
From: Elian Doran
Date: Sun, 16 Nov 2025 21:32:08 +0200
Subject: [PATCH 8/8] chore: handle requested changes
---
apps/client/src/widgets/type_widgets/MindMap.tsx | 2 +-
apps/client/src/widgets/type_widgets/canvas/Canvas.tsx | 3 ++-
.../Developer Guide/Concepts/Internationalisation Translat.md | 2 +-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/apps/client/src/widgets/type_widgets/MindMap.tsx b/apps/client/src/widgets/type_widgets/MindMap.tsx
index 715612947..b4ccdf371 100644
--- a/apps/client/src/widgets/type_widgets/MindMap.tsx
+++ b/apps/client/src/widgets/type_widgets/MindMap.tsx
@@ -136,7 +136,7 @@ function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef
const mind = new VanillaMindElixir({
el: containerRef.current,
- locale: LOCALE_MAPPINGS[locale],
+ locale: LOCALE_MAPPINGS[locale as DISPLAYABLE_LOCALE_IDS] ?? undefined,
editable
});
diff --git a/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx b/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx
index fb5cc1df5..6a5ea9377 100644
--- a/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx
+++ b/apps/client/src/widgets/type_widgets/canvas/Canvas.tsx
@@ -10,6 +10,7 @@ import { NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/type
import { goToLinkExt } from "../../../services/link";
import useCanvasPersistence from "./persistence";
import { LANGUAGE_MAPPINGS } from "./i18n";
+import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
// currently required by excalidraw, in order to allows self-hosting fonts locally.
// this avoids making excalidraw load the fonts from an external CDN.
@@ -60,7 +61,7 @@ export default function Canvas({ note, noteContext }: TypeWidgetProps) {
detectScroll={false}
handleKeyboardGlobally={false}
autoFocus={false}
- langCode={LANGUAGE_MAPPINGS[locale]}
+ langCode={LANGUAGE_MAPPINGS[locale as DISPLAYABLE_LOCALE_IDS] ?? undefined}
UIOptions={{
canvasActions: {
saveToActiveFile: false,
diff --git a/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translat.md b/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translat.md
index 2449b6c72..f420ff7ad 100644
--- a/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translat.md
+++ b/docs/Developer Guide/Developer Guide/Concepts/Internationalisation Translat.md
@@ -15,7 +15,7 @@ One important aspect is the fact that we are using a key-based approach. This me
The key-based approach allows a hierarchical structure. For example, a key of `about.title` would be added in `translation.json` as follows:
-```
+```json
{
"about": {
"title": "About Trilium Notes"