Skip to content

Commit f57e90b

Browse files
committed
Merge branch 'main' into siriusbcd_close_split
2 parents 5a5d242 + fc8042a commit f57e90b

File tree

34 files changed

+1023
-643
lines changed

34 files changed

+1023
-643
lines changed

apps/client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"mark.js": "8.11.1",
5656
"marked": "16.4.2",
5757
"mermaid": "11.12.1",
58-
"mind-elixir": "5.3.5",
58+
"mind-elixir": "5.3.6",
5959
"normalize.css": "8.0.1",
6060
"panzoom": "9.4.3",
6161
"preact": "10.27.2",

apps/client/src/translations/ro/translation.json

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,10 @@
302302
"edit_branch_prefix": "Editează prefixul ramurii",
303303
"help_on_tree_prefix": "Informații despre prefixe de ierarhie",
304304
"prefix": "Prefix: ",
305-
"save": "Salvează"
305+
"save": "Salvează",
306+
"edit_branch_prefix_multiple": "Editează prefixul pentru {{count}} ramuri",
307+
"branch_prefix_saved_multiple": "Prefixul a fost modificat pentru {{count}} ramuri.",
308+
"affected_branches": "Ramuri afectate ({{count}}):"
306309
},
307310
"bulk_actions": {
308311
"affected_notes": "Notițe afectate",
@@ -537,7 +540,8 @@
537540
"opml_version_1": "OPML v1.0 - text simplu",
538541
"opml_version_2": "OPML v2.0 - permite și HTML",
539542
"format_html": "HTML - recomandat deoarece păstrează toata formatarea",
540-
"format_pdf": "PDF - cu scopul de printare sau partajare."
543+
"format_pdf": "PDF - cu scopul de printare sau partajare.",
544+
"share-format": "HTML pentru publicare web - folosește aceeași temă pentru notițele partajate, dar se pot publica într-un website static."
541545
},
542546
"fast_search": {
543547
"description": "Căutarea rapidă dezactivează căutarea la nivel de conținut al notițelor cu scopul de a îmbunătăți performanța de căutare pentru baze de date mari.",
@@ -753,7 +757,8 @@
753757
"placeholder": "Introduceți etichetele HTML, câte unul pe linie",
754758
"reset_button": "Resetează la lista implicită",
755759
"title": "Etichete HTML la importare"
756-
}
760+
},
761+
"importZipRecommendation": "Când importați un fișier ZIP, ierarhia notițelor va reflecta structura subdirectoarelor din arhivă."
757762
},
758763
"include_archived_notes": {
759764
"include_archived_notes": "Include notițele arhivate"
@@ -799,7 +804,8 @@
799804
"default_description": "În mod implicit Trilium limitează lățimea conținutului pentru a îmbunătăți lizibilitatea pentru ferestrele maximizate pe ecrane late.",
800805
"max_width_label": "Lungimea maximă a conținutului",
801806
"max_width_unit": "pixeli",
802-
"title": "Lățime conținut"
807+
"title": "Lățime conținut",
808+
"centerContent": "Centrează conținutul"
803809
},
804810
"mobile_detail_menu": {
805811
"delete_this_note": "Șterge această notiță",
@@ -856,7 +862,8 @@
856862
"convert_into_attachment_failed": "Nu s-a putut converti notița „{{title}}”.",
857863
"convert_into_attachment_successful": "Notița „{{title}}” a fost convertită în atașament.",
858864
"convert_into_attachment_prompt": "Doriți convertirea notiței „{{title}}” într-un atașament al notiței părinte?",
859-
"print_pdf": "Exportare ca PDF..."
865+
"print_pdf": "Exportare ca PDF...",
866+
"open_note_on_server": "Deschide notița pe server"
860867
},
861868
"note_erasure_timeout": {
862869
"deleted_notes_erased": "Notițele șterse au fost eliminate permanent.",
@@ -1246,11 +1253,11 @@
12461253
"timeout_unit": "milisecunde"
12471254
},
12481255
"table_of_contents": {
1249-
"description": "Tabela de conținut va apărea în notițele de tip text atunci când notița are un număr de titluri mai mare decât cel definit. Acest număr se poate personaliza:",
1256+
"description": "Cuprinsul va apărea în notițele de tip text atunci când notița are un număr de titluri mai mare decât cel definit. Acest număr se poate personaliza:",
12501257
"unit": "titluri",
1251-
"disable_info": "De asemenea se poate dezactiva tabela de conținut setând o valoare foarte mare.",
1252-
"shortcut_info": "Se poate configura și o scurtatură pentru a comuta rapid vizibilitatea panoului din dreapta (inclusiv tabela de conținut) în Opțiuni -> Scurtături (denumirea „toggleRightPane”).",
1253-
"title": "Tabelă de conținut"
1258+
"disable_info": "De asemenea se poate dezactiva cuprinsul setând o valoare foarte mare.",
1259+
"shortcut_info": "Se poate configura și o scurtatură pentru a comuta rapid vizibilitatea panoului din dreapta (inclusiv cuprinsul) în Opțiuni -> Scurtături (denumirea „toggleRightPane”).",
1260+
"title": "Cuprins"
12541261
},
12551262
"text_auto_read_only_size": {
12561263
"description": "Marchează pragul în care o notiță de o anumită dimensiune va fi afișată în mod de citire (pentru motive de performanță).",
@@ -1503,7 +1510,9 @@
15031510
"window-on-top": "Menține fereastra mereu vizibilă"
15041511
},
15051512
"note_detail": {
1506-
"could_not_find_typewidget": "Nu s-a putut găsi widget-ul corespunzător tipului „{{type}}”"
1513+
"could_not_find_typewidget": "Nu s-a putut găsi widget-ul corespunzător tipului „{{type}}”",
1514+
"printing": "Imprimare în curs...",
1515+
"printing_pdf": "Exportare ca PDF în curs..."
15071516
},
15081517
"note_title": {
15091518
"placeholder": "introduceți titlul notiței aici..."
@@ -2014,7 +2023,8 @@
20142023
"new-item-placeholder": "Introduceți titlul notiței...",
20152024
"add-column-placeholder": "Introduceți denumirea coloanei...",
20162025
"edit-note-title": "Clic pentru a edita titlul notiței",
2017-
"edit-column-title": "Clic pentru a edita titlul coloanei"
2026+
"edit-column-title": "Clic pentru a edita titlul coloanei",
2027+
"column-already-exists": "Această coloană deja există."
20182028
},
20192029
"command_palette": {
20202030
"tree-action-name": "Listă de notițe: {{name}}",
@@ -2076,5 +2086,14 @@
20762086
"edit-slide": "Editați acest slide",
20772087
"start-presentation": "Începeți prezentarea",
20782088
"slide-overview": "Afișați o imagine de ansamblu a slide-urilor"
2089+
},
2090+
"read-only-info": {
2091+
"read-only-note": "Vizualizați o notiță în modul doar în citire.",
2092+
"auto-read-only-note": "Această notiță este afișată în modul doar în citire din motive de performanță.",
2093+
"auto-read-only-learn-more": "Mai multe detalii",
2094+
"edit-note": "Editează notița"
2095+
},
2096+
"calendar_view": {
2097+
"delete_note": "Șterge notița..."
20792098
}
20802099
}

apps/client/src/widgets/collections/calendar/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
9191
const [ hideWeekends ] = useNoteLabelBoolean(note, "calendar:hideWeekends");
9292
const [ weekNumbers ] = useNoteLabelBoolean(note, "calendar:weekNumbers");
9393
const [ calendarView, setCalendarView ] = useNoteLabel(note, "calendar:view");
94+
const [ initialDate ] = useNoteLabel(note, "calendar:initialDate");
9495
const initialView = useRef(calendarView);
9596
const viewSpacedUpdate = useSpacedUpdate(() => setCalendarView(initialView.current));
9697
useResizeObserver(containerRef, () => calendarRef.current?.updateSize());
@@ -134,6 +135,7 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
134135
height="90%"
135136
nowIndicator
136137
handleWindowResize={false}
138+
initialDate={initialDate || undefined}
137139
locale={locale}
138140
{...editingProps}
139141
eventDidMount={eventDidMount}

apps/client/src/widgets/type_widgets/MindMap.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { useCallback, useEffect, useRef } from "preact/hooks";
22
import { TypeWidgetProps } from "./type_widget";
3-
import { MindElixirData, MindElixirInstance, Operation, default as VanillaMindElixir } from "mind-elixir";
3+
import { MindElixirData, MindElixirInstance, Operation, Options, default as VanillaMindElixir } from "mind-elixir";
44
import { HTMLAttributes, RefObject } from "preact";
55
// allow node-menu plugin css to be bundled by webpack
66
import nodeMenu from "@mind-elixir/node-menu";
77
import "mind-elixir/style";
88
import "@mind-elixir/node-menu/dist/style.css";
99
import "./MindMap.css";
10-
import { useEditorSpacedUpdate, useNoteLabelBoolean, useSyncedRef, useTriliumEvent, useTriliumEvents } from "../react/hooks";
10+
import { useEditorSpacedUpdate, useNoteLabelBoolean, useSyncedRef, useTriliumEvent, useTriliumEvents, useTriliumOption } from "../react/hooks";
1111
import { refToJQuerySelector } from "../react/react_utils";
1212
import utils from "../../services/utils";
13+
import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
1314

1415
const NEW_TOPIC_NAME = "";
1516

@@ -21,6 +22,24 @@ interface MindElixirProps {
2122
onChange?: () => void;
2223
}
2324

25+
const LOCALE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, Options["locale"] | null> = {
26+
ar: null,
27+
cn: "zh_CN",
28+
de: null,
29+
en: "en",
30+
en_rtl: "en",
31+
es: "es",
32+
fr: "fr",
33+
it: "it",
34+
ja: "ja",
35+
pt: "pt",
36+
pt_br: "pt",
37+
ro: null,
38+
ru: "ru",
39+
tw: "zh_TW",
40+
uk: null
41+
};
42+
2443
export default function MindMap({ note, ntxId, noteContext }: TypeWidgetProps) {
2544
const apiRef = useRef<MindElixirInstance>(null);
2645
const containerRef = useRef<HTMLDivElement>(null);
@@ -110,12 +129,14 @@ export default function MindMap({ note, ntxId, noteContext }: TypeWidgetProps) {
110129
function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef: externalApiRef, onChange, editable }: MindElixirProps) {
111130
const containerRef = useSyncedRef<HTMLDivElement>(externalContainerRef, null);
112131
const apiRef = useRef<MindElixirInstance>(null);
132+
const [ locale ] = useTriliumOption("locale");
113133

114134
function reinitialize() {
115135
if (!containerRef.current) return;
116136

117137
const mind = new VanillaMindElixir({
118138
el: containerRef.current,
139+
locale: LOCALE_MAPPINGS[locale as DISPLAYABLE_LOCALE_IDS] ?? undefined,
119140
editable
120141
});
121142

@@ -143,7 +164,7 @@ function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef
143164
if (data) {
144165
apiRef.current?.init(data);
145166
}
146-
}, [ editable ]);
167+
}, [ editable, locale ]);
147168

148169
// On change listener.
149170
useEffect(() => {

apps/client/src/widgets/type_widgets/canvas/Canvas.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { Excalidraw } from "@excalidraw/excalidraw";
22
import { TypeWidgetProps } from "../type_widget";
33
import "@excalidraw/excalidraw/index.css";
4-
import { useNoteLabelBoolean } from "../../react/hooks";
4+
import { useNoteLabelBoolean, useTriliumOption } from "../../react/hooks";
55
import { useCallback, useMemo, useRef } from "preact/hooks";
66
import { type ExcalidrawImperativeAPI, type AppState } from "@excalidraw/excalidraw/types";
77
import options from "../../../services/options";
88
import "./Canvas.css";
99
import { NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types";
1010
import { goToLinkExt } from "../../../services/link";
1111
import useCanvasPersistence from "./persistence";
12+
import { LANGUAGE_MAPPINGS } from "./i18n";
13+
import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
1214

1315
// currently required by excalidraw, in order to allows self-hosting fonts locally.
1416
// this avoids making excalidraw load the fonts from an external CDN.
@@ -21,6 +23,7 @@ export default function Canvas({ note, noteContext }: TypeWidgetProps) {
2123
const documentStyle = window.getComputedStyle(document.documentElement);
2224
return documentStyle.getPropertyValue("--theme-style")?.trim() as AppState["theme"];
2325
}, []);
26+
const [ locale ] = useTriliumOption("locale");
2427
const persistence = useCanvasPersistence(note, noteContext, apiRef, themeStyle, isReadOnly);
2528

2629
/** Use excalidraw's native zoom instead of the global zoom. */
@@ -58,6 +61,7 @@ export default function Canvas({ note, noteContext }: TypeWidgetProps) {
5861
detectScroll={false}
5962
handleKeyboardGlobally={false}
6063
autoFocus={false}
64+
langCode={LANGUAGE_MAPPINGS[locale as DISPLAYABLE_LOCALE_IDS] ?? undefined}
6165
UIOptions={{
6266
canvasActions: {
6367
saveToActiveFile: false,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { LOCALES } from "@triliumnext/commons";
2+
import { readdirSync } from "fs";
3+
import { join } from "path";
4+
import { describe, expect, it } from "vitest";
5+
import { LANGUAGE_MAPPINGS } from "./i18n.js";
6+
7+
const localeDir = join(__dirname, "../../../../../../node_modules/@excalidraw/excalidraw/dist/prod/locales");
8+
9+
describe("Canvas i18n", () => {
10+
it("all languages are mapped correctly", () => {
11+
// Read the node_modules dir to obtain all the supported locales.
12+
const supportedLanguageCodes = new Set<string>();
13+
for (const file of readdirSync(localeDir)) {
14+
if (file.startsWith("percentages")) continue;
15+
const match = file.match("^[a-z]{2,3}(?:-[A-Z]{2,3})?");
16+
if (!match) continue;
17+
supportedLanguageCodes.add(match[0]);
18+
}
19+
20+
// Cross-check the locales.
21+
for (const locale of LOCALES) {
22+
if (locale.contentOnly || locale.devOnly) continue;
23+
const languageCode = LANGUAGE_MAPPINGS[locale.id];
24+
if (!supportedLanguageCodes.has(languageCode)) {
25+
expect.fail(`Unable to find locale for ${locale.id} -> ${languageCode}.`)
26+
}
27+
}
28+
});
29+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
2+
3+
export const LANGUAGE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, string | null> = {
4+
ar: "ar-SA",
5+
cn: "zh-CN",
6+
de: "de-DE",
7+
en: "en",
8+
en_rtl: "en",
9+
es: "es-ES",
10+
fr: "fr-FR",
11+
it: "it-IT",
12+
ja: "ja-JP",
13+
pt: "pt-PT",
14+
pt_br: "pt-BR",
15+
ro: "ro-RO",
16+
ru: "ru-RU",
17+
tw: "zh-TW",
18+
uk: "uk-UA"
19+
};

apps/client/src/widgets/type_widgets/text/CKEditorWithWatchdog.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { HTMLProps, RefObject, useEffect, useImperativeHandle, useRef, useState } from "preact/compat";
22
import { PopupEditor, ClassicEditor, EditorWatchdog, type WatchdogConfig, CKTextEditor, TemplateDefinition } from "@triliumnext/ckeditor5";
33
import { buildConfig, BuildEditorOptions } from "./config";
4-
import { useKeyboardShortcuts, useLegacyImperativeHandlers, useNoteContext, useSyncedRef } from "../../react/hooks";
4+
import { useKeyboardShortcuts, useLegacyImperativeHandlers, useNoteContext, useSyncedRef, useTriliumOption } from "../../react/hooks";
55
import link from "../../../services/link";
66
import froca from "../../../services/froca";
7+
import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
78

89
export type BoxSize = "small" | "medium" | "full";
910

@@ -37,6 +38,7 @@ interface CKEditorWithWatchdogProps extends Pick<HTMLProps<HTMLDivElement>, "cla
3738
export default function CKEditorWithWatchdog({ containerRef: externalContainerRef, content, contentLanguage, className, tabIndex, isClassicEditor, watchdogRef: externalWatchdogRef, watchdogConfig, onNotificationWarning, onWatchdogStateChange, onChange, onEditorInitialized, editorApi, templates }: CKEditorWithWatchdogProps) {
3839
const containerRef = useSyncedRef<HTMLDivElement>(externalContainerRef, null);
3940
const watchdogRef = useRef<EditorWatchdog>(null);
41+
const [ uiLanguage ] = useTriliumOption("locale");
4042
const [ editor, setEditor ] = useState<CKTextEditor>();
4143
const { parentComponent } = useNoteContext();
4244

@@ -156,6 +158,7 @@ export default function CKEditorWithWatchdog({ containerRef: externalContainerRe
156158
const editor = await buildEditor(container, !!isClassicEditor, {
157159
forceGplLicense: false,
158160
isClassicEditor: !!isClassicEditor,
161+
uiLanguage: uiLanguage as DISPLAYABLE_LOCALE_IDS,
159162
contentLanguage: contentLanguage ?? null,
160163
templates
161164
});
@@ -180,7 +183,7 @@ export default function CKEditorWithWatchdog({ containerRef: externalContainerRe
180183
watchdog.create(container);
181184

182185
return () => watchdog.destroy();
183-
}, [ contentLanguage, templates ]);
186+
}, [ contentLanguage, templates, uiLanguage ]);
184187

185188
// React to content changes.
186189
useEffect(() => editor?.setData(content ?? ""), [ editor, content ]);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { DISPLAYABLE_LOCALE_IDS, LOCALES } from "@triliumnext/commons";
2+
import { describe, expect, it, vi } from "vitest";
3+
4+
vi.mock('../../../services/options.js', () => ({
5+
default: {
6+
get(name: string) {
7+
if (name === "allowedHtmlTags") return "[]";
8+
return undefined;
9+
},
10+
getJson: () => []
11+
}
12+
}));
13+
14+
describe("CK config", () => {
15+
it("maps all languages correctly", async () => {
16+
const { buildConfig } = await import("./config.js");
17+
for (const locale of LOCALES) {
18+
if (locale.contentOnly || locale.devOnly) continue;
19+
20+
const config = await buildConfig({
21+
uiLanguage: locale.id as DISPLAYABLE_LOCALE_IDS,
22+
contentLanguage: locale.id,
23+
forceGplLicense: false,
24+
isClassicEditor: false,
25+
templates: []
26+
});
27+
28+
let expectedLocale = locale.id.substring(0, 2);
29+
if (expectedLocale === "cn") expectedLocale = "zh";
30+
if (expectedLocale === "tw") expectedLocale = "zh-tw";
31+
32+
if (locale.id !== "en") {
33+
expect((config.language as any).ui).toMatch(new RegExp(`^${expectedLocale}`));
34+
expect(config.translations, locale.id).toBeDefined();
35+
expect(config.translations, locale.id).toHaveLength(2);
36+
}
37+
}
38+
});
39+
});

apps/client/src/widgets/type_widgets/text/config.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { ALLOWED_PROTOCOLS, MIME_TYPE_AUTO } from "@triliumnext/commons";
2-
import { buildExtraCommands, type EditorConfig, PREMIUM_PLUGINS, TemplateDefinition } from "@triliumnext/ckeditor5";
1+
import { ALLOWED_PROTOCOLS, DISPLAYABLE_LOCALE_IDS, MIME_TYPE_AUTO } from "@triliumnext/commons";
2+
import { buildExtraCommands, type EditorConfig, getCkLocale, PREMIUM_PLUGINS, TemplateDefinition } from "@triliumnext/ckeditor5";
33
import { getHighlightJsNameForMime } from "../../../services/mime_types.js";
44
import options from "../../../services/options.js";
55
import { ensureMimeTypesForHighlighting, isSyntaxHighlightEnabled } from "../../../services/syntax_highlight.js";
@@ -17,6 +17,7 @@ export const OPEN_SOURCE_LICENSE_KEY = "GPL";
1717
export interface BuildEditorOptions {
1818
forceGplLicense: boolean;
1919
isClassicEditor: boolean;
20+
uiLanguage: DISPLAYABLE_LOCALE_IDS;
2021
contentLanguage: string | null;
2122
templates: TemplateDefinition[];
2223
}
@@ -161,9 +162,8 @@ export async function buildConfig(opts: BuildEditorOptions): Promise<EditorConfi
161162
htmlSupport: {
162163
allow: JSON.parse(options.get("allowedHtmlTags"))
163164
},
164-
// This value must be kept in sync with the language defined in webpack.config.js.
165-
language: "en",
166-
removePlugins: getDisabledPlugins()
165+
removePlugins: getDisabledPlugins(),
166+
...await getCkLocale(opts.uiLanguage)
167167
};
168168

169169
// Set up content language.

0 commit comments

Comments
 (0)