mirror of
https://github.com/zadam/trilium.git
synced 2026-02-06 14:39:29 +01:00
Merge branch 'main' into siriusbcd_close_split
This commit is contained in:
@@ -302,7 +302,10 @@
|
||||
"edit_branch_prefix": "Editează prefixul ramurii",
|
||||
"help_on_tree_prefix": "Informații despre prefixe de ierarhie",
|
||||
"prefix": "Prefix: ",
|
||||
"save": "Salvează"
|
||||
"save": "Salvează",
|
||||
"edit_branch_prefix_multiple": "Editează prefixul pentru {{count}} ramuri",
|
||||
"branch_prefix_saved_multiple": "Prefixul a fost modificat pentru {{count}} ramuri.",
|
||||
"affected_branches": "Ramuri afectate ({{count}}):"
|
||||
},
|
||||
"bulk_actions": {
|
||||
"affected_notes": "Notițe afectate",
|
||||
@@ -537,7 +540,8 @@
|
||||
"opml_version_1": "OPML v1.0 - text simplu",
|
||||
"opml_version_2": "OPML v2.0 - permite și HTML",
|
||||
"format_html": "HTML - recomandat deoarece păstrează toata formatarea",
|
||||
"format_pdf": "PDF - cu scopul de printare sau partajare."
|
||||
"format_pdf": "PDF - cu scopul de printare sau partajare.",
|
||||
"share-format": "HTML pentru publicare web - folosește aceeași temă pentru notițele partajate, dar se pot publica într-un website static."
|
||||
},
|
||||
"fast_search": {
|
||||
"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 @@
|
||||
"placeholder": "Introduceți etichetele HTML, câte unul pe linie",
|
||||
"reset_button": "Resetează la lista implicită",
|
||||
"title": "Etichete HTML la importare"
|
||||
}
|
||||
},
|
||||
"importZipRecommendation": "Când importați un fișier ZIP, ierarhia notițelor va reflecta structura subdirectoarelor din arhivă."
|
||||
},
|
||||
"include_archived_notes": {
|
||||
"include_archived_notes": "Include notițele arhivate"
|
||||
@@ -799,7 +804,8 @@
|
||||
"default_description": "În mod implicit Trilium limitează lățimea conținutului pentru a îmbunătăți lizibilitatea pentru ferestrele maximizate pe ecrane late.",
|
||||
"max_width_label": "Lungimea maximă a conținutului",
|
||||
"max_width_unit": "pixeli",
|
||||
"title": "Lățime conținut"
|
||||
"title": "Lățime conținut",
|
||||
"centerContent": "Centrează conținutul"
|
||||
},
|
||||
"mobile_detail_menu": {
|
||||
"delete_this_note": "Șterge această notiță",
|
||||
@@ -856,7 +862,8 @@
|
||||
"convert_into_attachment_failed": "Nu s-a putut converti notița „{{title}}”.",
|
||||
"convert_into_attachment_successful": "Notița „{{title}}” a fost convertită în atașament.",
|
||||
"convert_into_attachment_prompt": "Doriți convertirea notiței „{{title}}” într-un atașament al notiței părinte?",
|
||||
"print_pdf": "Exportare ca PDF..."
|
||||
"print_pdf": "Exportare ca PDF...",
|
||||
"open_note_on_server": "Deschide notița pe server"
|
||||
},
|
||||
"note_erasure_timeout": {
|
||||
"deleted_notes_erased": "Notițele șterse au fost eliminate permanent.",
|
||||
@@ -1246,11 +1253,11 @@
|
||||
"timeout_unit": "milisecunde"
|
||||
},
|
||||
"table_of_contents": {
|
||||
"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:",
|
||||
"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:",
|
||||
"unit": "titluri",
|
||||
"disable_info": "De asemenea se poate dezactiva tabela de conținut setând o valoare foarte mare.",
|
||||
"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”).",
|
||||
"title": "Tabelă de conținut"
|
||||
"disable_info": "De asemenea se poate dezactiva cuprinsul setând o valoare foarte mare.",
|
||||
"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”).",
|
||||
"title": "Cuprins"
|
||||
},
|
||||
"text_auto_read_only_size": {
|
||||
"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 @@
|
||||
"window-on-top": "Menține fereastra mereu vizibilă"
|
||||
},
|
||||
"note_detail": {
|
||||
"could_not_find_typewidget": "Nu s-a putut găsi widget-ul corespunzător tipului „{{type}}”"
|
||||
"could_not_find_typewidget": "Nu s-a putut găsi widget-ul corespunzător tipului „{{type}}”",
|
||||
"printing": "Imprimare în curs...",
|
||||
"printing_pdf": "Exportare ca PDF în curs..."
|
||||
},
|
||||
"note_title": {
|
||||
"placeholder": "introduceți titlul notiței aici..."
|
||||
@@ -2014,7 +2023,8 @@
|
||||
"new-item-placeholder": "Introduceți titlul notiței...",
|
||||
"add-column-placeholder": "Introduceți denumirea coloanei...",
|
||||
"edit-note-title": "Clic pentru a edita titlul notiței",
|
||||
"edit-column-title": "Clic pentru a edita titlul coloanei"
|
||||
"edit-column-title": "Clic pentru a edita titlul coloanei",
|
||||
"column-already-exists": "Această coloană deja există."
|
||||
},
|
||||
"command_palette": {
|
||||
"tree-action-name": "Listă de notițe: {{name}}",
|
||||
@@ -2076,5 +2086,14 @@
|
||||
"edit-slide": "Editați acest slide",
|
||||
"start-presentation": "Începeți prezentarea",
|
||||
"slide-overview": "Afișați o imagine de ansamblu a slide-urilor"
|
||||
},
|
||||
"read-only-info": {
|
||||
"read-only-note": "Vizualizați o notiță în modul doar în citire.",
|
||||
"auto-read-only-note": "Această notiță este afișată în modul doar în citire din motive de performanță.",
|
||||
"auto-read-only-learn-more": "Mai multe detalii",
|
||||
"edit-note": "Editează notița"
|
||||
},
|
||||
"calendar_view": {
|
||||
"delete_note": "Șterge notița..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
|
||||
const [ hideWeekends ] = useNoteLabelBoolean(note, "calendar:hideWeekends");
|
||||
const [ weekNumbers ] = useNoteLabelBoolean(note, "calendar:weekNumbers");
|
||||
const [ calendarView, setCalendarView ] = useNoteLabel(note, "calendar:view");
|
||||
const [ initialDate ] = useNoteLabel(note, "calendar:initialDate");
|
||||
const initialView = useRef(calendarView);
|
||||
const viewSpacedUpdate = useSpacedUpdate(() => setCalendarView(initialView.current));
|
||||
useResizeObserver(containerRef, () => calendarRef.current?.updateSize());
|
||||
@@ -134,6 +135,7 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
|
||||
height="90%"
|
||||
nowIndicator
|
||||
handleWindowResize={false}
|
||||
initialDate={initialDate || undefined}
|
||||
locale={locale}
|
||||
{...editingProps}
|
||||
eventDidMount={eventDidMount}
|
||||
|
||||
@@ -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<DISPLAYABLE_LOCALE_IDS, Options["locale"] | null> = {
|
||||
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<MindElixirInstance>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(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<HTMLDivElement>(externalContainerRef, null);
|
||||
const apiRef = useRef<MindElixirInstance>(null);
|
||||
const [ locale ] = useTriliumOption("locale");
|
||||
|
||||
function reinitialize() {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const mind = new VanillaMindElixir({
|
||||
el: containerRef.current,
|
||||
locale: LOCALE_MAPPINGS[locale as DISPLAYABLE_LOCALE_IDS] ?? undefined,
|
||||
editable
|
||||
});
|
||||
|
||||
@@ -143,7 +164,7 @@ function MindElixir({ containerRef: externalContainerRef, containerProps, apiRef
|
||||
if (data) {
|
||||
apiRef.current?.init(data);
|
||||
}
|
||||
}, [ editable ]);
|
||||
}, [ editable, locale ]);
|
||||
|
||||
// On change listener.
|
||||
useEffect(() => {
|
||||
|
||||
@@ -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,6 +9,8 @@ import "./Canvas.css";
|
||||
import { NonDeletedExcalidrawElement } from "@excalidraw/excalidraw/element/types";
|
||||
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.
|
||||
@@ -21,6 +23,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 +61,7 @@ export default function Canvas({ note, noteContext }: TypeWidgetProps) {
|
||||
detectScroll={false}
|
||||
handleKeyboardGlobally={false}
|
||||
autoFocus={false}
|
||||
langCode={LANGUAGE_MAPPINGS[locale as DISPLAYABLE_LOCALE_IDS] ?? undefined}
|
||||
UIOptions={{
|
||||
canvasActions: {
|
||||
saveToActiveFile: false,
|
||||
|
||||
29
apps/client/src/widgets/type_widgets/canvas/i18n.spec.ts
Normal file
29
apps/client/src/widgets/type_widgets/canvas/i18n.spec.ts
Normal file
@@ -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<string>();
|
||||
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}.`)
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
19
apps/client/src/widgets/type_widgets/canvas/i18n.ts
Normal file
19
apps/client/src/widgets/type_widgets/canvas/i18n.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons";
|
||||
|
||||
export const LANGUAGE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, string | null> = {
|
||||
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"
|
||||
};
|
||||
@@ -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<HTMLProps<HTMLDivElement>, "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<HTMLDivElement>(externalContainerRef, null);
|
||||
const watchdogRef = useRef<EditorWatchdog>(null);
|
||||
const [ uiLanguage ] = useTriliumOption("locale");
|
||||
const [ editor, setEditor ] = useState<CKTextEditor>();
|
||||
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 ]);
|
||||
|
||||
39
apps/client/src/widgets/type_widgets/text/config.spec.ts
Normal file
39
apps/client/src/widgets/type_widgets/text/config.spec.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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<EditorConfi
|
||||
htmlSupport: {
|
||||
allow: JSON.parse(options.get("allowedHtmlTags"))
|
||||
},
|
||||
// This value must be kept in sync with the language defined in webpack.config.js.
|
||||
language: "en",
|
||||
removePlugins: getDisabledPlugins()
|
||||
removePlugins: getDisabledPlugins(),
|
||||
...await getCkLocale(opts.uiLanguage)
|
||||
};
|
||||
|
||||
// Set up content language.
|
||||
|
||||
Reference in New Issue
Block a user