mirror of
https://github.com/zadam/trilium.git
synced 2025-12-24 00:59:55 +01:00
Compare commits
215 Commits
feat/add-o
...
feat/llm-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb2ace41b0 | ||
|
|
778f13e2e6 | ||
|
|
bb3d0f0319 | ||
|
|
cec627a744 | ||
|
|
2958ae4587 | ||
|
|
8da904cf55 | ||
|
|
b37d9b4b3d | ||
|
|
ac415c1007 | ||
|
|
d38ca72e08 | ||
|
|
16622f43e3 | ||
|
|
f89c202fcc | ||
|
|
97ec882528 | ||
|
|
a1e596b81b | ||
|
|
3db145b6e6 | ||
|
|
0d898385f6 | ||
|
|
89fcfabd3c | ||
|
|
eeeecb3988 | ||
|
|
28ababcbb9 | ||
|
|
f382943af3 | ||
|
|
fa38332a6c | ||
|
|
5a58fcde96 | ||
|
|
62d048433b | ||
|
|
db4ba53449 | ||
|
|
da20916767 | ||
|
|
b1e12182ce | ||
|
|
80b2061935 | ||
|
|
8ce92f8c93 | ||
|
|
05cd8cb547 | ||
|
|
6e7d0bc51b | ||
|
|
b9aede23e6 | ||
|
|
1d52988826 | ||
|
|
ebe29f41f9 | ||
|
|
598591a2da | ||
|
|
32c2860b68 | ||
|
|
3e1f74ae93 | ||
|
|
81a8908b98 | ||
|
|
892dfe2340 | ||
|
|
fd175eb8a8 | ||
|
|
c98f6d96d5 | ||
|
|
35b628e799 | ||
|
|
49b79c016d | ||
|
|
25a9a8a724 | ||
|
|
313a61ec48 | ||
|
|
a2eab03ee2 | ||
|
|
a563b1c9a0 | ||
|
|
20018b9c21 | ||
|
|
0a9bd5f6d1 | ||
|
|
911fee0213 | ||
|
|
ffe4b53eee | ||
|
|
cd5a68d230 | ||
|
|
95a2a69e0a | ||
|
|
360b5d6de4 | ||
|
|
bf50883e40 | ||
|
|
8e04690568 | ||
|
|
ae0af8b9c7 | ||
|
|
a03a0f8a75 | ||
|
|
f0f27a9065 | ||
|
|
181d5ee36a | ||
|
|
2758a230ac | ||
|
|
a46d32ed75 | ||
|
|
b2bcae8b74 | ||
|
|
49d662afba | ||
|
|
2a27666c53 | ||
|
|
f2d45cb780 | ||
|
|
c4b91c9777 | ||
|
|
fa06f56f5d | ||
|
|
519b962af3 | ||
|
|
31e6ac2349 | ||
|
|
ed3ba2745f | ||
|
|
f5b7648d6d | ||
|
|
2d537b82f6 | ||
|
|
073354fe04 | ||
|
|
165d093928 | ||
|
|
e8cf3f4a10 | ||
|
|
c36b00994b | ||
|
|
76b856bfe5 | ||
|
|
7b084035a3 | ||
|
|
59fbdaa879 | ||
|
|
1046321117 | ||
|
|
00fc92764b | ||
|
|
dea8bc307e | ||
|
|
18a4fbaa4b | ||
|
|
3efc4b13d5 | ||
|
|
952456a69c | ||
|
|
bde8e17fe6 | ||
|
|
9023ba1d0a | ||
|
|
61f9a86685 | ||
|
|
5520cfed5d | ||
|
|
43df984732 | ||
|
|
3f398c1a00 | ||
|
|
ad35e3b48f | ||
|
|
73ee44e177 | ||
|
|
18414cd155 | ||
|
|
652d78ac68 | ||
|
|
9a3ab05d73 | ||
|
|
fe238b8afd | ||
|
|
94492c7535 | ||
|
|
47caf970a1 | ||
|
|
3e75ab39c2 | ||
|
|
72aacdbf6f | ||
|
|
5461dafe02 | ||
|
|
30f9f66b8b | ||
|
|
19de803142 | ||
|
|
11b247fe07 | ||
|
|
faa40494d8 | ||
|
|
796802aea0 | ||
|
|
06af5cf6d5 | ||
|
|
81a99c1e44 | ||
|
|
1b384f35d2 | ||
|
|
c1259f2ea2 | ||
|
|
92d9c82d97 | ||
|
|
064f0ef921 | ||
|
|
e9a9b462d4 | ||
|
|
98888d5f1d | ||
|
|
6a2a096348 | ||
|
|
bf34ef2009 | ||
|
|
9cddb9ac1d | ||
|
|
d72d3db2a0 | ||
|
|
14b3bea203 | ||
|
|
05c26d17d3 | ||
|
|
51360d855a | ||
|
|
ae7d03e3c7 | ||
|
|
87e1ce64d1 | ||
|
|
f9c7c5637b | ||
|
|
5d55b0b0a8 | ||
|
|
b2d7fbbcad | ||
|
|
fbc6734e08 | ||
|
|
a83172390f | ||
|
|
4b1fd5e4a0 | ||
|
|
51495b282f | ||
|
|
b645d21fcd | ||
|
|
8f99ce7d14 | ||
|
|
6eb650bb22 | ||
|
|
a7f5702221 | ||
|
|
efeb9b90ca | ||
|
|
3361a2e4ab | ||
|
|
425ade5212 | ||
|
|
384ab1d1f3 | ||
|
|
70b1a37285 | ||
|
|
61a878e2a0 | ||
|
|
319cb8384c | ||
|
|
dd7ee05388 | ||
|
|
f740edae91 | ||
|
|
464c2bdf28 | ||
|
|
8007bac8b8 | ||
|
|
7a1ec266ad | ||
|
|
42fedaa241 | ||
|
|
4387bd4c6f | ||
|
|
51e1367b82 | ||
|
|
8bea3f4422 | ||
|
|
0eb2e405ff | ||
|
|
5dbd4a765f | ||
|
|
f6961c7e06 | ||
|
|
8d3ba90072 | ||
|
|
3772412d82 | ||
|
|
84389f467e | ||
|
|
eb41e0f96f | ||
|
|
2d44dff997 | ||
|
|
1483bf3d46 | ||
|
|
064cf6a3ee | ||
|
|
0c0d5eaa0a | ||
|
|
afecb33b5c | ||
|
|
fbb1e3a302 | ||
|
|
8704350359 | ||
|
|
d09e725d98 | ||
|
|
8be5b149c4 | ||
|
|
faeea6af18 | ||
|
|
3fa5ea1010 | ||
|
|
6aa31ae125 | ||
|
|
27f2e9c286 | ||
|
|
67cc36fdd2 | ||
|
|
ef7297e03b | ||
|
|
97a5314cdb | ||
|
|
a1195a2856 | ||
|
|
81419c6fe3 | ||
|
|
b8da793353 | ||
|
|
8140fa79cc | ||
|
|
abff4fe67d | ||
|
|
ec8f737eba | ||
|
|
cc6688ea00 | ||
|
|
c448b29be7 | ||
|
|
61bde294b3 | ||
|
|
acab81c61e | ||
|
|
1dd965973b | ||
|
|
d61981033f | ||
|
|
30197ba7ce | ||
|
|
1b6c957334 | ||
|
|
fb7a397bf9 | ||
|
|
133c9c5a7b | ||
|
|
8a587d4d21 | ||
|
|
29b813fa3b | ||
|
|
d5866a99ec | ||
|
|
5289d41b12 | ||
|
|
030178cad2 | ||
|
|
5d00630452 | ||
|
|
eb805bfa2a | ||
|
|
ee3a8e105e | ||
|
|
97fb273e7f | ||
|
|
2ef9009384 | ||
|
|
4c01d7d8f1 | ||
|
|
42ee351487 | ||
|
|
e0383c49cb | ||
|
|
6fbc5b2b14 | ||
|
|
5562559b0b | ||
|
|
c119ffe478 | ||
|
|
27847ab720 | ||
|
|
755b1ed42f | ||
|
|
4e36dc8e5e | ||
|
|
8bc70a4190 | ||
|
|
d798d29e92 | ||
|
|
6e0fee6cb3 | ||
|
|
e0e1f0796b | ||
|
|
e98954c555 | ||
|
|
87fd6afec6 | ||
|
|
dccd6477d2 |
2
.github/instructions/nx.instructions.md
vendored
2
.github/instructions/nx.instructions.md
vendored
@@ -4,7 +4,7 @@ applyTo: '**'
|
||||
|
||||
// This file is automatically generated by Nx Console
|
||||
|
||||
You are in an nx workspace using Nx 21.3.5 and pnpm as the package manager.
|
||||
You are in an nx workspace using Nx 21.3.9 and pnpm as the package manager.
|
||||
|
||||
You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user:
|
||||
|
||||
|
||||
2
.github/workflows/main-docker.yml
vendored
2
.github/workflows/main-docker.yml
vendored
@@ -223,7 +223,7 @@ jobs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -107,7 +107,7 @@ jobs:
|
||||
docs/Release Notes
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
with:
|
||||
merge-multiple: true
|
||||
pattern: release-*
|
||||
|
||||
11
.github/workflows/unblock_signing.yml
vendored
Normal file
11
.github/workflows/unblock_signing.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
name: Unblock signing
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
unblock-win-signing:
|
||||
runs-on: win-signing
|
||||
steps:
|
||||
- run: |
|
||||
cat ${{ vars.WINDOWS_SIGN_ERROR_LOG }}
|
||||
rm ${{ vars.WINDOWS_SIGN_ERROR_LOG }}
|
||||
17
README.md
17
README.md
@@ -1,10 +1,9 @@
|
||||
# Trilium Notes
|
||||
|
||||
Donate:  
|
||||
|
||||

|
||||

|
||||
[](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp)
|
||||
 
|
||||

|
||||

|
||||
[](https://app.relative-ci.com/projects/Di5q7dz9daNDZ9UXi0Bp) [](https://hosted.weblate.org/engage/trilium/)
|
||||
|
||||
[English](./README.md) | [Chinese](./docs/README-ZH_CN.md) | [Russian](./docs/README.ru.md) | [Japanese](./docs/README.ja.md) | [Italian](./docs/README.it.md) | [Spanish](./docs/README.es.md)
|
||||
|
||||
@@ -116,6 +115,14 @@ To install TriliumNext on your own server (including via Docker from [Dockerhub]
|
||||
|
||||
## 💻 Contribute
|
||||
|
||||
### Translations
|
||||
|
||||
If you are a native speaker, help us translate Trilium by heading over to our [Weblate page](https://hosted.weblate.org/engage/trilium/).
|
||||
|
||||
Here's the language coverage we have so far:
|
||||
|
||||
[](https://hosted.weblate.org/engage/trilium/)
|
||||
|
||||
### Code
|
||||
|
||||
Download the repository, install dependencies using `pnpm` and then run the server (available at http://localhost:8080):
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"chore:generate-openapi": "tsx bin/generate-openapi.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.54.1",
|
||||
"@playwright/test": "1.54.2",
|
||||
"@stylistic/eslint-plugin": "5.2.2",
|
||||
"@types/express": "5.0.3",
|
||||
"@types/node": "22.17.0",
|
||||
@@ -49,7 +49,7 @@
|
||||
"rcedit": "4.0.1",
|
||||
"rimraf": "6.0.1",
|
||||
"tslib": "2.8.1",
|
||||
"typedoc": "0.28.8",
|
||||
"typedoc": "0.28.9",
|
||||
"typedoc-plugin-missing-exports": "4.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/client",
|
||||
"version": "0.97.1",
|
||||
"version": "0.97.2",
|
||||
"description": "JQuery-based client for TriliumNext, used for both web and desktop (via Electron)",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
@@ -39,7 +39,6 @@
|
||||
"i18next": "25.3.2",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"jquery": "3.7.1",
|
||||
"jquery-hotkeys": "0.2.2",
|
||||
"jquery.fancytree": "2.38.5",
|
||||
"jsplumb": "2.15.6",
|
||||
"katex": "0.16.22",
|
||||
@@ -47,7 +46,7 @@
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-gpx": "2.2.0",
|
||||
"mark.js": "8.11.1",
|
||||
"marked": "16.1.1",
|
||||
"marked": "16.1.2",
|
||||
"mermaid": "11.9.0",
|
||||
"mind-elixir": "5.0.4",
|
||||
"normalize.css": "8.0.1",
|
||||
@@ -60,12 +59,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ckeditor/ckeditor5-inspector": "5.0.0",
|
||||
"@preact/preset-vite": "2.10.2",
|
||||
"@types/bootstrap": "5.2.10",
|
||||
"@types/jquery": "3.5.32",
|
||||
"@types/leaflet": "1.9.20",
|
||||
"@types/leaflet-gpx": "1.3.7",
|
||||
"@types/mark.js": "8.11.12",
|
||||
"@types/tabulator-tables": "6.2.8",
|
||||
"@types/tabulator-tables": "6.2.9",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"happy-dom": "18.0.1",
|
||||
"script-loader": "0.7.2",
|
||||
|
||||
@@ -266,6 +266,72 @@ export type CommandMappings = {
|
||||
jumpToNote: CommandData;
|
||||
commandPalette: CommandData;
|
||||
|
||||
// Keyboard shortcuts
|
||||
backInNoteHistory: CommandData;
|
||||
forwardInNoteHistory: CommandData;
|
||||
forceSaveRevision: CommandData;
|
||||
scrollToActiveNote: CommandData;
|
||||
quickSearch: CommandData;
|
||||
collapseTree: CommandData;
|
||||
createNoteAfter: CommandData;
|
||||
createNoteInto: CommandData;
|
||||
addNoteAboveToSelection: CommandData;
|
||||
addNoteBelowToSelection: CommandData;
|
||||
openNewTab: CommandData;
|
||||
activateNextTab: CommandData;
|
||||
activatePreviousTab: CommandData;
|
||||
openNewWindow: CommandData;
|
||||
toggleTray: CommandData;
|
||||
firstTab: CommandData;
|
||||
secondTab: CommandData;
|
||||
thirdTab: CommandData;
|
||||
fourthTab: CommandData;
|
||||
fifthTab: CommandData;
|
||||
sixthTab: CommandData;
|
||||
seventhTab: CommandData;
|
||||
eigthTab: CommandData;
|
||||
ninthTab: CommandData;
|
||||
lastTab: CommandData;
|
||||
showNoteSource: CommandData;
|
||||
showSQLConsole: CommandData;
|
||||
showBackendLog: CommandData;
|
||||
showCheatsheet: CommandData;
|
||||
showHelp: CommandData;
|
||||
addLinkToText: CommandData;
|
||||
followLinkUnderCursor: CommandData;
|
||||
insertDateTimeToText: CommandData;
|
||||
pasteMarkdownIntoText: CommandData;
|
||||
cutIntoNote: CommandData;
|
||||
addIncludeNoteToText: CommandData;
|
||||
editReadOnlyNote: CommandData;
|
||||
toggleRibbonTabClassicEditor: CommandData;
|
||||
toggleRibbonTabBasicProperties: CommandData;
|
||||
toggleRibbonTabBookProperties: CommandData;
|
||||
toggleRibbonTabFileProperties: CommandData;
|
||||
toggleRibbonTabImageProperties: CommandData;
|
||||
toggleRibbonTabOwnedAttributes: CommandData;
|
||||
toggleRibbonTabInheritedAttributes: CommandData;
|
||||
toggleRibbonTabPromotedAttributes: CommandData;
|
||||
toggleRibbonTabNoteMap: CommandData;
|
||||
toggleRibbonTabNoteInfo: CommandData;
|
||||
toggleRibbonTabNotePaths: CommandData;
|
||||
toggleRibbonTabSimilarNotes: CommandData;
|
||||
toggleRightPane: CommandData;
|
||||
printActiveNote: CommandData;
|
||||
exportAsPdf: CommandData;
|
||||
openNoteExternally: CommandData;
|
||||
renderActiveNote: CommandData;
|
||||
unhoist: CommandData;
|
||||
reloadFrontendApp: CommandData;
|
||||
openDevTools: CommandData;
|
||||
findInText: CommandData;
|
||||
toggleLeftPane: CommandData;
|
||||
toggleFullscreen: CommandData;
|
||||
zoomOut: CommandData;
|
||||
zoomIn: CommandData;
|
||||
zoomReset: CommandData;
|
||||
copyWithoutFormatting: CommandData;
|
||||
|
||||
// Geomap
|
||||
deleteFromMap: { noteId: string };
|
||||
|
||||
|
||||
@@ -30,13 +30,6 @@ interface CreateChildrenResponse {
|
||||
export default class Entrypoints extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (jQuery.hotkeys) {
|
||||
// hot keys are active also inside inputs and content editables
|
||||
jQuery.hotkeys.options.filterInputAcceptingElements = false;
|
||||
jQuery.hotkeys.options.filterContentEditable = false;
|
||||
jQuery.hotkeys.options.filterTextInputs = false;
|
||||
}
|
||||
}
|
||||
|
||||
openDevToolsCommand() {
|
||||
|
||||
@@ -13,7 +13,6 @@ import type ElectronRemote from "@electron/remote";
|
||||
import type Electron from "electron";
|
||||
import "./stylesheets/bootstrap.scss";
|
||||
import "boxicons/css/boxicons.min.css";
|
||||
import "jquery-hotkeys";
|
||||
import "autocomplete.js/index_jquery.js";
|
||||
|
||||
await appContext.earlyInit();
|
||||
|
||||
@@ -13,8 +13,8 @@ let openTooltipElements: JQuery<HTMLElement>[] = [];
|
||||
let dismissTimer: ReturnType<typeof setTimeout>;
|
||||
|
||||
function setupGlobalTooltip() {
|
||||
$(document).on("mouseenter", "a", mouseEnterHandler);
|
||||
$(document).on("mouseenter", "[data-href]", mouseEnterHandler);
|
||||
$(document).on("mouseenter", "a:not(.no-tooltip-preview)", mouseEnterHandler);
|
||||
$(document).on("mouseenter", "[data-href]:not(.no-tooltip-preview)", mouseEnterHandler);
|
||||
|
||||
// close any note tooltip after click, this fixes the problem that sometimes tooltips remained on the screen
|
||||
$(document).on("click", (e) => {
|
||||
|
||||
323
apps/client/src/services/shortcuts.spec.ts
Normal file
323
apps/client/src/services/shortcuts.spec.ts
Normal file
@@ -0,0 +1,323 @@
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||
import shortcuts, { keyMatches, matchesShortcut } from "./shortcuts.js";
|
||||
|
||||
// Mock utils module
|
||||
vi.mock("./utils.js", () => ({
|
||||
default: {
|
||||
isDesktop: () => true
|
||||
}
|
||||
}));
|
||||
|
||||
// Mock jQuery globally since it's used in the shortcuts module
|
||||
const mockElement = {
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn()
|
||||
};
|
||||
|
||||
const mockJQuery = vi.fn(() => [mockElement]);
|
||||
(mockJQuery as any).length = 1;
|
||||
mockJQuery[0] = mockElement;
|
||||
|
||||
(global as any).$ = mockJQuery as any;
|
||||
global.document = mockElement as any;
|
||||
|
||||
describe("shortcuts", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Clean up any active bindings after each test
|
||||
shortcuts.removeGlobalShortcut("test-namespace");
|
||||
});
|
||||
|
||||
describe("normalizeShortcut", () => {
|
||||
it("should normalize shortcut to lowercase and remove whitespace", () => {
|
||||
expect(shortcuts.normalizeShortcut("Ctrl + A")).toBe("ctrl+a");
|
||||
expect(shortcuts.normalizeShortcut(" SHIFT + F1 ")).toBe("shift+f1");
|
||||
expect(shortcuts.normalizeShortcut("Alt+Space")).toBe("alt+space");
|
||||
});
|
||||
|
||||
it("should handle empty or null shortcuts", () => {
|
||||
expect(shortcuts.normalizeShortcut("")).toBe("");
|
||||
expect(shortcuts.normalizeShortcut(null as any)).toBe(null);
|
||||
expect(shortcuts.normalizeShortcut(undefined as any)).toBe(undefined);
|
||||
});
|
||||
|
||||
it("should handle shortcuts with multiple spaces", () => {
|
||||
expect(shortcuts.normalizeShortcut("Ctrl + Shift + A")).toBe("ctrl+shift+a");
|
||||
});
|
||||
|
||||
it("should warn about malformed shortcuts", () => {
|
||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
shortcuts.normalizeShortcut("ctrl+");
|
||||
shortcuts.normalizeShortcut("+a");
|
||||
shortcuts.normalizeShortcut("ctrl++a");
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalledTimes(3);
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("keyMatches", () => {
|
||||
const createKeyboardEvent = (key: string, code?: string) => ({
|
||||
key,
|
||||
code: code || `Key${key.toUpperCase()}`
|
||||
} as KeyboardEvent);
|
||||
|
||||
it("should match regular letter keys using key code", () => {
|
||||
const event = createKeyboardEvent("a", "KeyA");
|
||||
expect(keyMatches(event, "a")).toBe(true);
|
||||
expect(keyMatches(event, "A")).toBe(true);
|
||||
});
|
||||
|
||||
it("should match number keys using digit codes", () => {
|
||||
const event = createKeyboardEvent("1", "Digit1");
|
||||
expect(keyMatches(event, "1")).toBe(true);
|
||||
});
|
||||
|
||||
it("should match special keys using key mapping", () => {
|
||||
expect(keyMatches({ key: "Enter" } as KeyboardEvent, "return")).toBe(true);
|
||||
expect(keyMatches({ key: "Enter" } as KeyboardEvent, "enter")).toBe(true);
|
||||
expect(keyMatches({ key: "Delete" } as KeyboardEvent, "del")).toBe(true);
|
||||
expect(keyMatches({ key: "Escape" } as KeyboardEvent, "esc")).toBe(true);
|
||||
expect(keyMatches({ key: " " } as KeyboardEvent, "space")).toBe(true);
|
||||
expect(keyMatches({ key: "ArrowUp" } as KeyboardEvent, "up")).toBe(true);
|
||||
});
|
||||
|
||||
it("should match function keys", () => {
|
||||
expect(keyMatches({ key: "F1" } as KeyboardEvent, "f1")).toBe(true);
|
||||
expect(keyMatches({ key: "F12" } as KeyboardEvent, "f12")).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle undefined or null keys", () => {
|
||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
expect(keyMatches({} as KeyboardEvent, null as any)).toBe(false);
|
||||
expect(keyMatches({} as KeyboardEvent, undefined as any)).toBe(false);
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalled();
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("matchesShortcut", () => {
|
||||
const createKeyboardEvent = (options: {
|
||||
key: string;
|
||||
code?: string;
|
||||
ctrlKey?: boolean;
|
||||
altKey?: boolean;
|
||||
shiftKey?: boolean;
|
||||
metaKey?: boolean;
|
||||
}) => ({
|
||||
key: options.key,
|
||||
code: options.code || `Key${options.key.toUpperCase()}`,
|
||||
ctrlKey: options.ctrlKey || false,
|
||||
altKey: options.altKey || false,
|
||||
shiftKey: options.shiftKey || false,
|
||||
metaKey: options.metaKey || false
|
||||
} as KeyboardEvent);
|
||||
|
||||
it("should match simple key shortcuts", () => {
|
||||
const event = createKeyboardEvent({ key: "a", code: "KeyA" });
|
||||
expect(matchesShortcut(event, "a")).toBe(true);
|
||||
});
|
||||
|
||||
it("should match shortcuts with modifiers", () => {
|
||||
const event = createKeyboardEvent({ key: "a", code: "KeyA", ctrlKey: true });
|
||||
expect(matchesShortcut(event, "ctrl+a")).toBe(true);
|
||||
|
||||
const shiftEvent = createKeyboardEvent({ key: "a", code: "KeyA", shiftKey: true });
|
||||
expect(matchesShortcut(shiftEvent, "shift+a")).toBe(true);
|
||||
});
|
||||
|
||||
it("should match complex modifier combinations", () => {
|
||||
const event = createKeyboardEvent({
|
||||
key: "a",
|
||||
code: "KeyA",
|
||||
ctrlKey: true,
|
||||
shiftKey: true
|
||||
});
|
||||
expect(matchesShortcut(event, "ctrl+shift+a")).toBe(true);
|
||||
});
|
||||
|
||||
it("should not match when modifiers don't match", () => {
|
||||
const event = createKeyboardEvent({ key: "a", code: "KeyA", ctrlKey: true });
|
||||
expect(matchesShortcut(event, "alt+a")).toBe(false);
|
||||
expect(matchesShortcut(event, "a")).toBe(false);
|
||||
});
|
||||
|
||||
it("should handle alternative modifier names", () => {
|
||||
const ctrlEvent = createKeyboardEvent({ key: "a", code: "KeyA", ctrlKey: true });
|
||||
expect(matchesShortcut(ctrlEvent, "control+a")).toBe(true);
|
||||
|
||||
const metaEvent = createKeyboardEvent({ key: "a", code: "KeyA", metaKey: true });
|
||||
expect(matchesShortcut(metaEvent, "cmd+a")).toBe(true);
|
||||
expect(matchesShortcut(metaEvent, "command+a")).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle empty or invalid shortcuts", () => {
|
||||
const event = createKeyboardEvent({ key: "a", code: "KeyA" });
|
||||
expect(matchesShortcut(event, "")).toBe(false);
|
||||
expect(matchesShortcut(event, null as any)).toBe(false);
|
||||
});
|
||||
|
||||
it("should handle invalid events", () => {
|
||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
expect(matchesShortcut(null as any, "a")).toBe(false);
|
||||
expect(matchesShortcut({} as KeyboardEvent, "a")).toBe(false);
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalled();
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("should warn about invalid shortcut formats", () => {
|
||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
const event = createKeyboardEvent({ key: "a", code: "KeyA" });
|
||||
|
||||
matchesShortcut(event, "ctrl+");
|
||||
matchesShortcut(event, "+");
|
||||
|
||||
expect(consoleSpy).toHaveBeenCalled();
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("bindGlobalShortcut", () => {
|
||||
it("should bind a global shortcut", () => {
|
||||
const handler = vi.fn();
|
||||
shortcuts.bindGlobalShortcut("ctrl+a", handler, "test-namespace");
|
||||
|
||||
expect(mockElement.addEventListener).toHaveBeenCalledWith("keydown", expect.any(Function));
|
||||
});
|
||||
|
||||
it("should not bind shortcuts when handler is null", () => {
|
||||
shortcuts.bindGlobalShortcut("ctrl+a", null, "test-namespace");
|
||||
|
||||
expect(mockElement.addEventListener).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should remove previous bindings when namespace is reused", () => {
|
||||
const handler1 = vi.fn();
|
||||
const handler2 = vi.fn();
|
||||
|
||||
shortcuts.bindGlobalShortcut("ctrl+a", handler1, "test-namespace");
|
||||
expect(mockElement.addEventListener).toHaveBeenCalledTimes(1);
|
||||
|
||||
shortcuts.bindGlobalShortcut("ctrl+b", handler2, "test-namespace");
|
||||
expect(mockElement.removeEventListener).toHaveBeenCalledTimes(1);
|
||||
expect(mockElement.addEventListener).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bindElShortcut", () => {
|
||||
it("should bind shortcut to specific element", () => {
|
||||
const mockEl = { addEventListener: vi.fn(), removeEventListener: vi.fn() };
|
||||
const mockJQueryEl = [mockEl] as any;
|
||||
mockJQueryEl.length = 1;
|
||||
|
||||
const handler = vi.fn();
|
||||
shortcuts.bindElShortcut(mockJQueryEl, "ctrl+a", handler, "test-namespace");
|
||||
|
||||
expect(mockEl.addEventListener).toHaveBeenCalledWith("keydown", expect.any(Function));
|
||||
});
|
||||
|
||||
it("should fall back to document when element is empty", () => {
|
||||
const emptyJQuery = [] as any;
|
||||
emptyJQuery.length = 0;
|
||||
|
||||
const handler = vi.fn();
|
||||
shortcuts.bindElShortcut(emptyJQuery, "ctrl+a", handler, "test-namespace");
|
||||
|
||||
expect(mockElement.addEventListener).toHaveBeenCalledWith("keydown", expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeGlobalShortcut", () => {
|
||||
it("should remove shortcuts for a specific namespace", () => {
|
||||
const handler = vi.fn();
|
||||
shortcuts.bindGlobalShortcut("ctrl+a", handler, "test-namespace");
|
||||
|
||||
shortcuts.removeGlobalShortcut("test-namespace");
|
||||
|
||||
expect(mockElement.removeEventListener).toHaveBeenCalledWith("keydown", expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe("event handling", () => {
|
||||
it.skip("should call handler when shortcut matches", () => {
|
||||
const handler = vi.fn();
|
||||
shortcuts.bindGlobalShortcut("ctrl+a", handler, "test-namespace");
|
||||
|
||||
// Get the listener that was registered
|
||||
expect(mockElement.addEventListener.mock.calls).toHaveLength(1);
|
||||
const [, listener] = mockElement.addEventListener.mock.calls[0];
|
||||
|
||||
// First verify that matchesShortcut works directly
|
||||
const testEvent = {
|
||||
type: "keydown",
|
||||
key: "a",
|
||||
code: "KeyA",
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
metaKey: false,
|
||||
preventDefault: vi.fn(),
|
||||
stopPropagation: vi.fn()
|
||||
} as any;
|
||||
|
||||
// Test matchesShortcut directly first
|
||||
expect(matchesShortcut(testEvent, "ctrl+a")).toBe(true);
|
||||
|
||||
// Now test the actual listener
|
||||
listener(testEvent);
|
||||
|
||||
expect(handler).toHaveBeenCalled();
|
||||
expect(testEvent.preventDefault).toHaveBeenCalled();
|
||||
expect(testEvent.stopPropagation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not call handler for non-keyboard events", () => {
|
||||
const handler = vi.fn();
|
||||
shortcuts.bindGlobalShortcut("ctrl+a", handler, "test-namespace");
|
||||
|
||||
const [, listener] = mockElement.addEventListener.mock.calls[0];
|
||||
|
||||
// Simulate a non-keyboard event
|
||||
const event = {
|
||||
type: "click"
|
||||
} as any;
|
||||
|
||||
listener(event);
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not call handler when shortcut doesn't match", () => {
|
||||
const handler = vi.fn();
|
||||
shortcuts.bindGlobalShortcut("ctrl+a", handler, "test-namespace");
|
||||
|
||||
const [, listener] = mockElement.addEventListener.mock.calls[0];
|
||||
|
||||
// Simulate a non-matching keydown event
|
||||
const event = {
|
||||
type: "keydown",
|
||||
key: "b",
|
||||
code: "KeyB",
|
||||
ctrlKey: true,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
metaKey: false,
|
||||
preventDefault: vi.fn(),
|
||||
stopPropagation: vi.fn()
|
||||
} as any;
|
||||
|
||||
listener(event);
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,18 @@
|
||||
import utils from "./utils.js";
|
||||
|
||||
type ElementType = HTMLElement | Document;
|
||||
type Handler = (e: JQuery.TriggeredEvent<ElementType | Element, string, ElementType | Element, ElementType | Element>) => void;
|
||||
type Handler = (e: KeyboardEvent) => void;
|
||||
|
||||
interface ShortcutBinding {
|
||||
element: HTMLElement | Document;
|
||||
shortcut: string;
|
||||
handler: Handler;
|
||||
namespace: string | null;
|
||||
listener: (evt: Event) => void;
|
||||
}
|
||||
|
||||
// Store all active shortcut bindings for management
|
||||
const activeBindings: Map<string, ShortcutBinding[]> = new Map();
|
||||
|
||||
function removeGlobalShortcut(namespace: string) {
|
||||
bindGlobalShortcut("", null, namespace);
|
||||
@@ -15,38 +26,167 @@ function bindElShortcut($el: JQuery<ElementType | Element>, keyboardShortcut: st
|
||||
if (utils.isDesktop()) {
|
||||
keyboardShortcut = normalizeShortcut(keyboardShortcut);
|
||||
|
||||
let eventName = "keydown";
|
||||
|
||||
// If namespace is provided, remove all previous bindings for this namespace
|
||||
if (namespace) {
|
||||
eventName += `.${namespace}`;
|
||||
|
||||
// if there's a namespace, then we replace the existing event handler with the new one
|
||||
$el.off(eventName);
|
||||
removeNamespaceBindings(namespace);
|
||||
}
|
||||
|
||||
// method can be called to remove the shortcut (e.g. when keyboardShortcut label is deleted)
|
||||
if (keyboardShortcut) {
|
||||
$el.bind(eventName, keyboardShortcut, (e) => {
|
||||
if (handler) {
|
||||
handler(e);
|
||||
// Method can be called to remove the shortcut (e.g. when keyboardShortcut label is deleted)
|
||||
if (keyboardShortcut && handler) {
|
||||
const element = $el.length > 0 ? $el[0] as (HTMLElement | Document) : document;
|
||||
|
||||
const listener = (evt: Event) => {
|
||||
// Only handle keyboard events
|
||||
if (evt.type !== 'keydown' || !(evt instanceof KeyboardEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
const e = evt as KeyboardEvent;
|
||||
if (matchesShortcut(e, keyboardShortcut)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handler(e);
|
||||
}
|
||||
};
|
||||
|
||||
// Add the event listener
|
||||
element.addEventListener('keydown', listener);
|
||||
|
||||
// Store the binding for later cleanup
|
||||
const binding: ShortcutBinding = {
|
||||
element,
|
||||
shortcut: keyboardShortcut,
|
||||
handler,
|
||||
namespace,
|
||||
listener
|
||||
};
|
||||
|
||||
const key = namespace || 'global';
|
||||
if (!activeBindings.has(key)) {
|
||||
activeBindings.set(key, []);
|
||||
}
|
||||
activeBindings.get(key)!.push(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeNamespaceBindings(namespace: string) {
|
||||
const bindings = activeBindings.get(namespace);
|
||||
if (bindings) {
|
||||
// Remove all event listeners for this namespace
|
||||
bindings.forEach(binding => {
|
||||
binding.element.removeEventListener('keydown', binding.listener);
|
||||
});
|
||||
activeBindings.delete(namespace);
|
||||
}
|
||||
}
|
||||
|
||||
export function matchesShortcut(e: KeyboardEvent, shortcut: string): boolean {
|
||||
if (!shortcut) return false;
|
||||
|
||||
// Ensure we have a proper KeyboardEvent with key property
|
||||
if (!e || typeof e.key !== 'string') {
|
||||
console.warn('matchesShortcut called with invalid event:', e);
|
||||
return false;
|
||||
}
|
||||
|
||||
const parts = shortcut.toLowerCase().split('+');
|
||||
const key = parts[parts.length - 1]; // Last part is the actual key
|
||||
const modifiers = parts.slice(0, -1); // Everything before is modifiers
|
||||
|
||||
// Defensive check - ensure we have a valid key
|
||||
if (!key || key.trim() === '') {
|
||||
console.warn('Invalid shortcut format:', shortcut);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the main key matches
|
||||
if (!keyMatches(e, key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check modifiers
|
||||
const expectedCtrl = modifiers.includes('ctrl') || modifiers.includes('control');
|
||||
const expectedAlt = modifiers.includes('alt');
|
||||
const expectedShift = modifiers.includes('shift');
|
||||
const expectedMeta = modifiers.includes('meta') || modifiers.includes('cmd') || modifiers.includes('command');
|
||||
|
||||
return e.ctrlKey === expectedCtrl &&
|
||||
e.altKey === expectedAlt &&
|
||||
e.shiftKey === expectedShift &&
|
||||
e.metaKey === expectedMeta;
|
||||
}
|
||||
|
||||
export function keyMatches(e: KeyboardEvent, key: string): boolean {
|
||||
// Defensive check for undefined/null key
|
||||
if (!key) {
|
||||
console.warn('keyMatches called with undefined/null key');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle special key mappings and aliases
|
||||
const keyMap: { [key: string]: string[] } = {
|
||||
'return': ['Enter'],
|
||||
'enter': ['Enter'], // alias for return
|
||||
'del': ['Delete'],
|
||||
'delete': ['Delete'], // alias for del
|
||||
'esc': ['Escape'],
|
||||
'escape': ['Escape'], // alias for esc
|
||||
'space': [' ', 'Space'],
|
||||
'tab': ['Tab'],
|
||||
'backspace': ['Backspace'],
|
||||
'home': ['Home'],
|
||||
'end': ['End'],
|
||||
'pageup': ['PageUp'],
|
||||
'pagedown': ['PageDown'],
|
||||
'up': ['ArrowUp'],
|
||||
'down': ['ArrowDown'],
|
||||
'left': ['ArrowLeft'],
|
||||
'right': ['ArrowRight']
|
||||
};
|
||||
|
||||
// Function keys
|
||||
for (let i = 1; i <= 19; i++) {
|
||||
keyMap[`f${i}`] = [`F${i}`];
|
||||
}
|
||||
|
||||
const mappedKeys = keyMap[key.toLowerCase()];
|
||||
if (mappedKeys) {
|
||||
return mappedKeys.includes(e.key) || mappedKeys.includes(e.code);
|
||||
}
|
||||
|
||||
// For number keys, use the physical key code regardless of modifiers
|
||||
// This works across all keyboard layouts
|
||||
if (key >= '0' && key <= '9') {
|
||||
return e.code === `Digit${key}`;
|
||||
}
|
||||
|
||||
// For letter keys, use the physical key code for consistency
|
||||
if (key.length === 1 && key >= 'a' && key <= 'z') {
|
||||
return e.code === `Key${key.toUpperCase()}`;
|
||||
}
|
||||
|
||||
// For regular keys, check both key and code as fallback
|
||||
return e.key.toLowerCase() === key.toLowerCase() ||
|
||||
e.code.toLowerCase() === key.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize to the form expected by the jquery.hotkeys.js
|
||||
* Simple normalization - just lowercase and trim whitespace
|
||||
*/
|
||||
function normalizeShortcut(shortcut: string): string {
|
||||
if (!shortcut) {
|
||||
return shortcut;
|
||||
}
|
||||
|
||||
return shortcut.toLowerCase().replace("enter", "return").replace("delete", "del").replace("ctrl+alt", "alt+ctrl").replace("meta+alt", "alt+meta"); // alt needs to be first;
|
||||
const normalized = shortcut.toLowerCase().trim().replace(/\s+/g, '');
|
||||
|
||||
// Warn about potentially problematic shortcuts
|
||||
if (normalized.endsWith('+') || normalized.startsWith('+') || normalized.includes('++')) {
|
||||
console.warn('Potentially malformed shortcut:', shortcut, '-> normalized to:', normalized);
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
@@ -36,7 +36,9 @@ export function applyCopyToClipboardButton($codeBlock: JQuery<HTMLElement>) {
|
||||
const $copyButton = $("<button>")
|
||||
.addClass("bx component icon-action tn-tool-button bx-copy copy-button")
|
||||
.attr("title", t("code_block.copy_title"))
|
||||
.on("click", () => {
|
||||
.on("click", (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (!isShare) {
|
||||
copyTextWithToast($codeBlock.text());
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import "jquery";
|
||||
import "jquery-hotkeys";
|
||||
import utils from "./services/utils.js";
|
||||
import ko from "knockout";
|
||||
import "./stylesheets/bootstrap.scss";
|
||||
|
||||
214
apps/client/src/translations/ca/translation.json
Normal file
214
apps/client/src/translations/ca/translation.json
Normal file
@@ -0,0 +1,214 @@
|
||||
{
|
||||
"about": {
|
||||
"title": "Sobre Trilium Notes",
|
||||
"close": "Tanca",
|
||||
"homepage": "Pàgina principal:"
|
||||
},
|
||||
"add_link": {
|
||||
"note": "Nota",
|
||||
"close": "Tanca"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"close": "Tanca",
|
||||
"prefix": "Prefix: ",
|
||||
"save": "Desa"
|
||||
},
|
||||
"bulk_actions": {
|
||||
"close": "Tanca",
|
||||
"labels": "Etiquetes",
|
||||
"relations": "Relacions",
|
||||
"notes": "Notes",
|
||||
"other": "Altres"
|
||||
},
|
||||
"clone_to": {
|
||||
"close": "Tanca"
|
||||
},
|
||||
"confirm": {
|
||||
"confirmation": "Confirmació",
|
||||
"close": "Tanca",
|
||||
"cancel": "Cancel·la",
|
||||
"ok": "OK"
|
||||
},
|
||||
"delete_notes": {
|
||||
"close": "Tanca",
|
||||
"cancel": "Cancel·la",
|
||||
"ok": "OK"
|
||||
},
|
||||
"export": {
|
||||
"close": "Tanca",
|
||||
"export": "Exporta"
|
||||
},
|
||||
"help": {
|
||||
"close": "Tanca",
|
||||
"troubleshooting": "Solució de problemes",
|
||||
"other": "Altres"
|
||||
},
|
||||
"import": {
|
||||
"close": "Tanca",
|
||||
"options": "Opcions",
|
||||
"import": "Importa"
|
||||
},
|
||||
"include_note": {
|
||||
"close": "Tanca",
|
||||
"label_note": "Nota"
|
||||
},
|
||||
"info": {
|
||||
"closeButton": "Tanca",
|
||||
"okButton": "OK"
|
||||
},
|
||||
"jump_to_note": {
|
||||
"close": "Tanca"
|
||||
},
|
||||
"markdown_import": {
|
||||
"close": "Tanca"
|
||||
},
|
||||
"move_to": {
|
||||
"close": "Tanca"
|
||||
},
|
||||
"note_type_chooser": {
|
||||
"close": "Tanca",
|
||||
"templates": "Plantilles:"
|
||||
},
|
||||
"password_not_set": {
|
||||
"close": "Tanca"
|
||||
},
|
||||
"prompt": {
|
||||
"title": "Sol·licitud",
|
||||
"close": "Tanca",
|
||||
"defaultTitle": "Sol·licitud"
|
||||
},
|
||||
"protected_session_password": {
|
||||
"close_label": "Tanca"
|
||||
},
|
||||
"recent_changes": {
|
||||
"close": "Tanca",
|
||||
"undelete_link": "recuperar"
|
||||
},
|
||||
"revisions": {
|
||||
"close": "Tanca",
|
||||
"restore_button": "Restaura",
|
||||
"delete_button": "Suprimeix",
|
||||
"download_button": "Descarrega",
|
||||
"mime": "MIME: ",
|
||||
"preview": "Vista prèvia:"
|
||||
},
|
||||
"sort_child_notes": {
|
||||
"close": "Tanca",
|
||||
"title": "títol",
|
||||
"ascending": "ascendent",
|
||||
"descending": "descendent",
|
||||
"folders": "Carpetes"
|
||||
},
|
||||
"upload_attachments": {
|
||||
"close": "Tanca",
|
||||
"options": "Opcions",
|
||||
"upload": "Puja"
|
||||
},
|
||||
"attribute_detail": {
|
||||
"name": "Nom",
|
||||
"value": "Valor",
|
||||
"promoted": "Destacat",
|
||||
"promoted_alias": "Àlies",
|
||||
"multiplicity": "Multiplicitat",
|
||||
"label_type": "Tipus",
|
||||
"text": "Text",
|
||||
"number": "Número",
|
||||
"boolean": "Booleà",
|
||||
"date": "Data",
|
||||
"time": "Hora",
|
||||
"url": "URL",
|
||||
"precision": "Precisió",
|
||||
"digits": "dígits",
|
||||
"inheritable": "Heretable",
|
||||
"delete": "Suprimeix",
|
||||
"color_type": "Color"
|
||||
},
|
||||
"rename_label": {
|
||||
"to": "Per"
|
||||
},
|
||||
"move_note": {
|
||||
"to": "a"
|
||||
},
|
||||
"add_relation": {
|
||||
"to": "a"
|
||||
},
|
||||
"rename_relation": {
|
||||
"to": "Per"
|
||||
},
|
||||
"update_relation_target": {
|
||||
"to": "a"
|
||||
},
|
||||
"attachments_actions": {
|
||||
"download": "Descarrega"
|
||||
},
|
||||
"calendar": {
|
||||
"mon": "Dl",
|
||||
"tue": "Dt",
|
||||
"wed": "dc",
|
||||
"thu": "Dj",
|
||||
"fri": "Dv",
|
||||
"sat": "Ds",
|
||||
"sun": "Dg",
|
||||
"january": "Gener",
|
||||
"febuary": "Febrer",
|
||||
"march": "Març",
|
||||
"april": "Abril",
|
||||
"may": "Maig",
|
||||
"june": "Juny",
|
||||
"july": "Juliol",
|
||||
"august": "Agost",
|
||||
"september": "Setembre",
|
||||
"october": "Octubre",
|
||||
"november": "Novembre",
|
||||
"december": "Desembre"
|
||||
},
|
||||
"global_menu": {
|
||||
"menu": "Menú",
|
||||
"options": "Opcions",
|
||||
"zoom": "Zoom",
|
||||
"advanced": "Avançat",
|
||||
"logout": "Tanca la sessió"
|
||||
},
|
||||
"zpetne_odkazy": {
|
||||
"relation": "relació"
|
||||
},
|
||||
"note_icon": {
|
||||
"category": "Categoria:",
|
||||
"search": "Cerca:"
|
||||
},
|
||||
"basic_properties": {
|
||||
"editable": "Editable",
|
||||
"language": "Llengua"
|
||||
},
|
||||
"book_properties": {
|
||||
"grid": "Graella",
|
||||
"list": "Llista",
|
||||
"collapse": "Replega",
|
||||
"expand": "Desplega",
|
||||
"calendar": "Calendari",
|
||||
"table": "Taula",
|
||||
"board": "Tauler"
|
||||
},
|
||||
"edited_notes": {
|
||||
"deleted": "(suprimit)"
|
||||
},
|
||||
"file_properties": {
|
||||
"download": "Descarrega",
|
||||
"open": "Obre",
|
||||
"title": "Fitxer"
|
||||
},
|
||||
"image_properties": {
|
||||
"download": "Descarrega",
|
||||
"open": "Obre",
|
||||
"title": "Imatge"
|
||||
},
|
||||
"note_info_widget": {
|
||||
"created": "Creat",
|
||||
"modified": "Modificat",
|
||||
"type": "Tipus",
|
||||
"calculate": "calcula"
|
||||
},
|
||||
"note_paths": {
|
||||
"archived": "Arxivat"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
19
apps/client/src/translations/el/translation.json
Normal file
19
apps/client/src/translations/el/translation.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"about": {
|
||||
"title": "Πληροφορίες για το Trilium Notes",
|
||||
"close": "Κλείσιμο",
|
||||
"homepage": "Αρχική Σελίδα:",
|
||||
"app_version": "Έκδοση εφαρμογής:",
|
||||
"db_version": "Έκδοση βάσης δεδομένων:",
|
||||
"sync_version": "Έκδοση πρωτοκόλου συγχρονισμού:",
|
||||
"build_date": "Ημερομηνία χτισίματος εφαρμογής:",
|
||||
"build_revision": "Αριθμός αναθεώρησης χτισίματος:",
|
||||
"data_directory": "Φάκελος δεδομένων:"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "Κρίσιμο σφάλμα",
|
||||
"message": "Συνέβη κάποιο κρίσιμο σφάλμα, το οποίο δεν επιτρέπει στην εφαρμογή χρήστη να ξεκινήσει:\n\n{{message}}\n\nΤο πιθανότερο είναι να προκλήθηκε από κάποιο script που απέτυχε απρόοπτα. Δοκιμάστε να ξεκινήσετε την εφαρμογή σε ασφαλή λειτουργία για να λύσετε το πρόβλημα."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2005,5 +2005,8 @@
|
||||
},
|
||||
"content_renderer": {
|
||||
"open_externally": "Open externally"
|
||||
},
|
||||
"modal": {
|
||||
"close": "Close"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
379
apps/client/src/translations/it/translation.json
Normal file
379
apps/client/src/translations/it/translation.json
Normal file
@@ -0,0 +1,379 @@
|
||||
{
|
||||
"about": {
|
||||
"close": "Chiudi",
|
||||
"app_version": "Versione dell'app:",
|
||||
"db_version": "Versione DB:",
|
||||
"sync_version": "Versione Sync:",
|
||||
"data_directory": "Cartella dati:",
|
||||
"title": "Informazioni su Trilium Notes",
|
||||
"build_date": "Data della build:",
|
||||
"build_revision": "Revisione della build:",
|
||||
"homepage": "Homepage:"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "Errore critico",
|
||||
"message": "Si è verificato un errore critico che impedisce l'avvio dell'applicazione client:\n\n{{message}}\n\nQuesto è probabilmente causato da un errore di script inaspettato. Prova a avviare l'applicazione in modo sicuro e controlla il problema."
|
||||
},
|
||||
"bundle-error": {
|
||||
"title": "Non si è riusciti a caricare uno script personalizzato",
|
||||
"message": "Lo script della nota con ID \"{{id}}\", dal titolo \"{{title}}\" non è stato inizializzato a causa di:\n\n{{message}}"
|
||||
},
|
||||
"widget-error": {
|
||||
"title": "Impossibile inizializzare un widget",
|
||||
"message-custom": "Il widget personalizzato della nota con ID \"{{id}}\", dal titolo \"{{title}}\" non è stato inizializzato a causa di:\n\n{{message}}",
|
||||
"message-unknown": "Un widget sconosciuto non è stato inizializzato a causa di:\n\n{{message}}"
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Aggiungi un collegamento",
|
||||
"close": "Chiudi",
|
||||
"note": "Nota",
|
||||
"search_note": "cerca una nota per nome",
|
||||
"link_title_mirrors": "il titolo del collegamento rispecchia il titolo della nota corrente",
|
||||
"link_title_arbitrary": "il titolo del collegamento può essere modificato arbitrariamente",
|
||||
"link_title": "Titolo del collegamento",
|
||||
"button_add_link": "Aggiungi il collegamento <kbd>invio</kbd>",
|
||||
"help_on_links": "Aiuto sui collegamenti"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"edit_branch_prefix": "Modifica il prefisso del ramo",
|
||||
"help_on_tree_prefix": "Aiuto sui prefissi dell'Albero",
|
||||
"close": "Chiudi",
|
||||
"prefix": "Prefisso: ",
|
||||
"save": "Salva",
|
||||
"branch_prefix_saved": "Il prefisso del ramo è stato salvato."
|
||||
},
|
||||
"bulk_actions": {
|
||||
"bulk_actions": "Azioni massive",
|
||||
"close": "Chiudi",
|
||||
"affected_notes": "Note influenzate",
|
||||
"include_descendants": "Includi i discendenti della nota selezionata",
|
||||
"available_actions": "Azioni disponibili",
|
||||
"chosen_actions": "Azioni scelte",
|
||||
"execute_bulk_actions": "Esegui le azioni massive",
|
||||
"bulk_actions_executed": "Le azioni massive sono state eseguite con successo.",
|
||||
"none_yet": "Ancora nessuna... aggiungi una azione cliccando su una di quelle disponibili sopra.",
|
||||
"labels": "Etichette",
|
||||
"relations": "Relazioni",
|
||||
"notes": "Note",
|
||||
"other": "Altro"
|
||||
},
|
||||
"clone_to": {
|
||||
"clone_notes_to": "Clona note in...",
|
||||
"close": "Chiudi",
|
||||
"help_on_links": "Aiuto sui collegamenti",
|
||||
"notes_to_clone": "Note da clonare",
|
||||
"target_parent_note": "Nodo padre obiettivo",
|
||||
"search_for_note_by_its_name": "cerca una nota per nome",
|
||||
"cloned_note_prefix_title": "Le note clonate saranno mostrate nell'albero delle note con il dato prefisso",
|
||||
"prefix_optional": "Prefisso (opzionale)",
|
||||
"clone_to_selected_note": "Clona sotto la nota selezionata <kbd>invio</kbd>",
|
||||
"no_path_to_clone_to": "Nessun percorso per clonare dentro.",
|
||||
"note_cloned": "La nota \"{{clonedTitle}}\" è stata clonata in \"{{targetTitle}}\""
|
||||
},
|
||||
"confirm": {
|
||||
"close": "Chiudi",
|
||||
"cancel": "Annulla",
|
||||
"ok": "OK",
|
||||
"confirmation": "Conferma",
|
||||
"are_you_sure_remove_note": "Sei sicuro di voler rimuovere la nota \"{{title}}\" dalla mappa delle relazioni? ",
|
||||
"if_you_dont_check": "Se non lo selezioni, la nota sarà rimossa solamente dalla mappa delle relazioni.",
|
||||
"also_delete_note": "Rimuove anche la nota"
|
||||
},
|
||||
"delete_notes": {
|
||||
"ok": "OK",
|
||||
"close": "Chiudi",
|
||||
"delete_notes_preview": "Anteprima di eliminazione delle note",
|
||||
"delete_all_clones_description": "Elimina anche tutti i cloni (può essere disfatto tramite i cambiamenti recenti)",
|
||||
"erase_notes_description": "L'eliminazione normale (soft) marca le note come eliminate e potranno essere recuperate entro un certo lasso di tempo (dalla finestra dei cambiamenti recenti). Selezionando questa opzione le note si elimineranno immediatamente e non sarà possibile recuperarle.",
|
||||
"erase_notes_warning": "Elimina le note in modo permanente (non potrà essere disfatto), compresi tutti i cloni. Ciò forzerà un nuovo caricamento dell'applicazione.",
|
||||
"cancel": "Annulla",
|
||||
"notes_to_be_deleted": "Le seguenti note saranno eliminate ({{- noteCount}})",
|
||||
"no_note_to_delete": "Nessuna nota sarà eliminata (solo i cloni).",
|
||||
"broken_relations_to_be_deleted": "Le seguenti relazioni saranno interrotte ed eliminate ({{- relationCount}})",
|
||||
"deleted_relation_text": "La nota {{- note}} (da eliminare) è referenziata dalla relazione {{- relation}} originata da {{- source}}."
|
||||
},
|
||||
"info": {
|
||||
"okButton": "OK",
|
||||
"closeButton": "Chiudi"
|
||||
},
|
||||
"export": {
|
||||
"close": "Chiudi",
|
||||
"export_note_title": "Esporta la nota",
|
||||
"export_status": "Stato dell'esportazione",
|
||||
"export": "Esporta",
|
||||
"choose_export_type": "Scegli prima il tipo di esportazione, per favore",
|
||||
"export_in_progress": "Esportazione in corso: {{progressCount}}",
|
||||
"export_finished_successfully": "Esportazione terminata con successo.",
|
||||
"format_pdf": "PDF- allo scopo di stampa o esportazione."
|
||||
},
|
||||
"help": {
|
||||
"close": "Chiudi",
|
||||
"fullDocumentation": "Aiuto (la documentazione completa è disponibile <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">online</a>)"
|
||||
},
|
||||
"import": {
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"include_note": {
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"jump_to_note": {
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"markdown_import": {
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"move_to": {
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"note_type_chooser": {
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"password_not_set": {
|
||||
"close": "Chiudi",
|
||||
"body1": "Le note protette sono crittografate utilizzando una password utente, ma la password non è stata ancora impostata.",
|
||||
"body2": "Per proteggere le note, fare clic su <a class=\"open-password-options-button\" href=\"javascript:\">qui</a> per aprire la finestra di dialogo Opzioni e impostare la password."
|
||||
},
|
||||
"protected_session_password": {
|
||||
"close_label": "Chiudi"
|
||||
},
|
||||
"prompt": {
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"recent_changes": {
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"revisions": {
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"abstract_bulk_action": {
|
||||
"remove_this_search_action": "Rimuovi questa azione di ricerca"
|
||||
},
|
||||
"etapi": {
|
||||
"new_token_title": "Nuovo token ETAPI",
|
||||
"new_token_message": "Inserire il nuovo nome del token"
|
||||
},
|
||||
"electron_integration": {
|
||||
"zoom-factor": "Fattore di ingrandimento",
|
||||
"desktop-application": "Applicazione Desktop"
|
||||
},
|
||||
"note_autocomplete": {
|
||||
"search-for": "Cerca \"{{term}}\"",
|
||||
"create-note": "Crea e collega la nota figlia \"{{term}}\"",
|
||||
"insert-external-link": "Inserisci il collegamento esterno a \"{{term}}\"",
|
||||
"clear-text-field": "Pulisci il campo di testo",
|
||||
"show-recent-notes": "Mostra le note recenti",
|
||||
"full-text-search": "Ricerca full text"
|
||||
},
|
||||
"note_tooltip": {
|
||||
"note-has-been-deleted": "La nota è stata eliminata.",
|
||||
"quick-edit": "Modifica veloce"
|
||||
},
|
||||
"geo-map": {
|
||||
"create-child-note-title": "Crea una nota figlia e aggiungila alla mappa",
|
||||
"create-child-note-instruction": "Clicca sulla mappa per creare una nuova nota qui o premi Escape per uscire.",
|
||||
"unable-to-load-map": "Impossibile caricare la mappa."
|
||||
},
|
||||
"geo-map-context": {
|
||||
"open-location": "Apri la posizione",
|
||||
"remove-from-map": "Rimuovi dalla mappa",
|
||||
"add-note": "Aggiungi un marcatore in questa posizione"
|
||||
},
|
||||
"debug": {
|
||||
"debug": "Debug"
|
||||
},
|
||||
"database_anonymization": {
|
||||
"light_anonymization": "Anonimizzazione parziale",
|
||||
"title": "Anonimizzazione del Database",
|
||||
"full_anonymization": "Anonimizzazione completa",
|
||||
"full_anonymization_description": "Questa azione creerà una nuova copia del database e lo anonimizzerà (rimuove tutti i contenuti delle note, lasciando solo la struttura e qualche metadato non sensibile) per condividerlo online allo scopo di debugging, senza paura di far trapelare i tuoi dati personali.",
|
||||
"save_fully_anonymized_database": "Salva il database completamente anonimizzato",
|
||||
"light_anonymization_description": "Questa azione creerà una nuova copia del database e lo anonimizzerà in parzialmente — in particolare, solo il contenuto delle note sarà rimosso, ma i titoli e gli attributi rimarranno. Inoltre, note con script personalizzati JS di frontend/backend e widget personalizzati lasciando rimarranno. Ciò mette a disposizione più contesto per il debug dei problemi.",
|
||||
"choose_anonymization": "Puoi decidere da solo se fornire un database completamente o parzialmente anonimizzato. Anche un database completamente anonimizzato è molto utile, sebbene in alcuni casi i database parzialmente anonimizzati possono accelerare il processo di identificazione dei bug e la loro correzione.",
|
||||
"no_anonymized_database_yet": "Nessun database ancora anonimizzato.",
|
||||
"save_lightly_anonymized_database": "Salva il database parzialmente anonimizzato",
|
||||
"successfully_created_fully_anonymized_database": "Database completamente anonimizzato creato in {{anonymizedFilePath}}",
|
||||
"successfully_created_lightly_anonymized_database": "Database parzialmente anonimizzato creato in {{anonymizedFilePath}}"
|
||||
},
|
||||
"cpu_arch_warning": {
|
||||
"title": "Per favore scarica la versione ARM64",
|
||||
"continue_anyway": "Continua Comunque",
|
||||
"dont_show_again": "Non mostrare più questo avviso",
|
||||
"download_link": "Scarica la Versione Nativa"
|
||||
},
|
||||
"editorfeatures": {
|
||||
"title": "Caratteristiche",
|
||||
"emoji_completion_enabled": "Abilita il completamento automatico delle Emoji",
|
||||
"note_completion_enabled": "Abilita il completamento automatico delle note"
|
||||
},
|
||||
"table_view": {
|
||||
"new-row": "Nuova riga",
|
||||
"new-column": "Nuova colonna",
|
||||
"sort-column-by": "Ordina per \"{{title}}\"",
|
||||
"sort-column-ascending": "Ascendente",
|
||||
"sort-column-descending": "Discendente",
|
||||
"sort-column-clear": "Cancella l'ordinamento",
|
||||
"hide-column": "Nascondi la colonna \"{{title}}\"",
|
||||
"show-hide-columns": "Mostra/nascondi le colonne",
|
||||
"row-insert-above": "Inserisci una riga sopra",
|
||||
"row-insert-below": "Inserisci una riga sotto"
|
||||
},
|
||||
"abstract_search_option": {
|
||||
"remove_this_search_option": "Rimuovi questa opzione di ricerca",
|
||||
"failed_rendering": "Opzione di ricerca di rendering non riuscita: {{dto}} con errore: {{error}} {{stack}}"
|
||||
},
|
||||
"ancestor": {
|
||||
"label": "Antenato"
|
||||
},
|
||||
"add_label": {
|
||||
"add_label": "Aggiungi etichetta",
|
||||
"label_name_placeholder": "nome dell'etichetta",
|
||||
"new_value_placeholder": "nuovo valore",
|
||||
"to_value": "al valore"
|
||||
},
|
||||
"update_label_value": {
|
||||
"to_value": "al valore",
|
||||
"label_name_placeholder": "nome dell'etichetta"
|
||||
},
|
||||
"delete_label": {
|
||||
"delete_label": "Elimina etichetta",
|
||||
"label_name_placeholder": "nome dell'etichetta",
|
||||
"label_name_title": "Sono ammessi i caratteri alfanumerici, il carattere di sottolineato e i due punti."
|
||||
},
|
||||
"tree-context-menu": {
|
||||
"move-to": "Muovi in...",
|
||||
"cut": "Taglia"
|
||||
},
|
||||
"electron_context_menu": {
|
||||
"cut": "Taglia",
|
||||
"copy": "Copia",
|
||||
"paste": "Incolla",
|
||||
"copy-link": "Copia collegamento",
|
||||
"paste-as-plain-text": "Incolla come testo semplice"
|
||||
},
|
||||
"editing": {
|
||||
"editor_type": {
|
||||
"multiline-toolbar": "Mostra la barra degli strumenti su più linee se non entra."
|
||||
}
|
||||
},
|
||||
"edit_button": {
|
||||
"edit_this_note": "Modifica questa nota"
|
||||
},
|
||||
"shortcuts": {
|
||||
"shortcuts": "Scorciatoie"
|
||||
},
|
||||
"shared_switch": {
|
||||
"toggle-on-title": "Condividi la nota",
|
||||
"toggle-off-title": "Non condividere la nota"
|
||||
},
|
||||
"search_string": {
|
||||
"search_prefix": "Cerca:"
|
||||
},
|
||||
"attachment_detail": {
|
||||
"open_help_page": "Apri la pagina di aiuto sugli allegati"
|
||||
},
|
||||
"search_definition": {
|
||||
"ancestor": "antenato",
|
||||
"debug": "debug",
|
||||
"action": "azione",
|
||||
"add_search_option": "Aggiungi un opzione di ricerca:",
|
||||
"search_string": "cerca la stringa",
|
||||
"limit": "limite"
|
||||
},
|
||||
"modal": {
|
||||
"close": "Chiudi"
|
||||
},
|
||||
"board_view": {
|
||||
"insert-below": "Inserisci sotto",
|
||||
"delete-column": "Elimina la colonna",
|
||||
"delete-column-confirmation": "Sei sicuro di vole eliminare questa colonna? Il corrispondente attributo sarà eliminato anche nelle note sotto questa colonna."
|
||||
},
|
||||
"backup": {
|
||||
"enable_weekly_backup": "Abilita le archiviazioni settimanali",
|
||||
"enable_monthly_backup": "Abilita le archiviazioni mensili",
|
||||
"backup_recommendation": "Si raccomanda di mantenere attive le archiviazioni, sebbene ciò possa rendere l'avvio dell'applicazione lento con database grandi e/o dispositivi di archiviazione lenti.",
|
||||
"backup_now": "Archivia adesso",
|
||||
"backup_database_now": "Archivia il database adesso",
|
||||
"existing_backups": "Backup esistenti",
|
||||
"date-and-time": "Data e ora",
|
||||
"path": "Percorso",
|
||||
"database_backed_up_to": "Il database è stato archiviato in {{backupFilePath}}",
|
||||
"enable_daily_backup": "Abilita le archiviazioni giornaliere",
|
||||
"no_backup_yet": "Ancora nessuna archiviazione"
|
||||
},
|
||||
"backend_log": {
|
||||
"refresh": "Aggiorna"
|
||||
},
|
||||
"consistency_checks": {
|
||||
"find_and_fix_button": "Trova e correggi i problemi di coerenza",
|
||||
"finding_and_fixing_message": "In cerca e correzione dei problemi di coerenza...",
|
||||
"issues_fixed_message": "Qualsiasi problema di coerenza che possa essere stato trovato ora è corretto."
|
||||
},
|
||||
"database_integrity_check": {
|
||||
"check_button": "Controllo dell'integrità del database",
|
||||
"checking_integrity": "Controllo dell'integrità del database in corso...",
|
||||
"title": "Controllo di Integrità del database",
|
||||
"description": "Controllerà che il database non sia corrotto a livello SQLite. Può durare un po' di tempo, a seconda della grandezza del DB.",
|
||||
"integrity_check_failed": "Controllo di integrità fallito: {{results}}"
|
||||
},
|
||||
"sync": {
|
||||
"title": "Sincronizza",
|
||||
"force_full_sync_button": "Forza una sincronizzazione completa",
|
||||
"failed": "Sincronizzazione fallita: {{message}}"
|
||||
},
|
||||
"sync_2": {
|
||||
"config_title": "Configurazione per la Sincronizzazione",
|
||||
"proxy_label": "Server Proxy per la sincronizzazione (opzionale)",
|
||||
"test_title": "Test di sincronizzazione",
|
||||
"timeout": "Timeout per la sincronizzazione",
|
||||
"timeout_unit": "millisecondi",
|
||||
"save": "Salva",
|
||||
"help": "Aiuto"
|
||||
},
|
||||
"search_engine": {
|
||||
"save_button": "Salva"
|
||||
},
|
||||
"sql_table_schemas": {
|
||||
"tables": "Tabelle"
|
||||
},
|
||||
"tab_row": {
|
||||
"close_tab": "Chiudi la scheda",
|
||||
"add_new_tab": "Aggiungi una nuova scheda",
|
||||
"close": "Chiudi",
|
||||
"close_other_tabs": "Chiudi le altre schede",
|
||||
"close_right_tabs": "Chiudi le schede a destra",
|
||||
"close_all_tabs": "Chiudi tutte le schede",
|
||||
"reopen_last_tab": "Riapri l'ultima scheda chiusa",
|
||||
"move_tab_to_new_window": "Sposta questa scheda in una nuova finestra",
|
||||
"copy_tab_to_new_window": "Copia questa scheda in una nuova finestra",
|
||||
"new_tab": "Nuova scheda"
|
||||
},
|
||||
"toc": {
|
||||
"table_of_contents": "Sommario"
|
||||
},
|
||||
"table_of_contents": {
|
||||
"title": "Sommario"
|
||||
},
|
||||
"tray": {
|
||||
"title": "Vassoio di Sistema",
|
||||
"enable_tray": "Abilita il vassoio (Trilium necessita di essere riavviato affinché la modifica abbia effetto)"
|
||||
},
|
||||
"heading_style": {
|
||||
"title": "Stile dell'Intestazione",
|
||||
"plain": "Normale",
|
||||
"underline": "Sottolineato",
|
||||
"markdown": "Stile Markdown"
|
||||
},
|
||||
"highlights_list": {
|
||||
"title": "Punti salienti"
|
||||
},
|
||||
"highlights_list_2": {
|
||||
"title": "Punti salienti",
|
||||
"options": "Opzioni"
|
||||
},
|
||||
"quick-search": {
|
||||
"placeholder": "Ricerca rapida",
|
||||
"searching": "Ricerca in corso..."
|
||||
}
|
||||
}
|
||||
23
apps/client/src/translations/ja/translation.json
Normal file
23
apps/client/src/translations/ja/translation.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"about": {
|
||||
"title": "Trilium Notesについて",
|
||||
"close": "閉じる",
|
||||
"homepage": "ホームページ:",
|
||||
"app_version": "アプリのヴァージョン:",
|
||||
"db_version": "データベースのヴァージョン:",
|
||||
"sync_version": "同期のヴァージョン:",
|
||||
"build_date": "Build の日時:",
|
||||
"build_revision": "Build のヴァージョン:",
|
||||
"data_directory": "データの場所:"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "致命的なエラー",
|
||||
"message": "致命的なエラーのせいでアプリをスタートできません:\n\n{{message}}\n\nおそらくスクリプトが予期しないバグを含んでいると思われます。アプリをセーフモードでスタートしてみて下さい。"
|
||||
},
|
||||
"widget-error": {
|
||||
"title": "ウィジェットを初期化できませんでした",
|
||||
"message-custom": "ノートID”{{id}}”, ノートタイトル “{{title}}” のカスタムウィジェットを初期化できませんでした:\n\n{{message}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,416 @@
|
||||
{
|
||||
"revisions": {
|
||||
"delete_button": ""
|
||||
},
|
||||
"code_block": {
|
||||
"theme_none": "Sem destaque de sintaxe",
|
||||
"theme_group_light": "Temas claros",
|
||||
"theme_group_dark": "Temas escuros"
|
||||
}
|
||||
"code_block": {
|
||||
"theme_none": "Sem destaque de sintaxe",
|
||||
"theme_group_light": "Temas claros",
|
||||
"theme_group_dark": "Temas escuros"
|
||||
},
|
||||
"about": {
|
||||
"title": "Sobre o Trilium Notes",
|
||||
"close": "Fechar",
|
||||
"homepage": "Página inicial:",
|
||||
"app_version": "Versão do App:",
|
||||
"db_version": "Versão do db:",
|
||||
"sync_version": "Versão de sincronização:",
|
||||
"build_date": "Data de compilação:",
|
||||
"build_revision": "Revisão da compilação:",
|
||||
"data_directory": "Diretório de dados:"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "Erro crítico",
|
||||
"message": "Ocorreu um erro crítico que impede a inicialização do aplicativo cliente:\n\n{{message}}\n\nIsso provavelmente foi causado por um script que falhou de maneira inesperada. Tente iniciar o aplicativo no modo seguro e resolva o problema."
|
||||
},
|
||||
"widget-error": {
|
||||
"title": "Falha ao inicializar um widget",
|
||||
"message-custom": "O widget personalizado da nota com ID \"{{id}}\", intitulada \"{{title}}\", não pôde ser inicializado devido a:\n\n{{message}}",
|
||||
"message-unknown": "Widget desconhecido não pôde ser inicializado devido a:\n\n{{message}}"
|
||||
},
|
||||
"bundle-error": {
|
||||
"title": "Falha para carregar o script customizado",
|
||||
"message": "O script da nota com ID \"{{id}}\", intitulada \"{{title}}\", não pôde ser executado devido a:\n\n{{message}}"
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Adicionar link",
|
||||
"help_on_links": "Ajuda sobre links",
|
||||
"close": "Fechar",
|
||||
"note": "Nota",
|
||||
"search_note": "pesquisar nota pelo nome",
|
||||
"link_title_mirrors": "o título do link reflete o título atual da nota",
|
||||
"link_title_arbitrary": "o título do link pode ser alterado livremente",
|
||||
"link_title": "Titulo do link",
|
||||
"button_add_link": "Adicionar link <kbd>enter</kbd>"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"close": "Fechar",
|
||||
"prefix": "Prefixo: ",
|
||||
"save": "Salvar",
|
||||
"edit_branch_prefix": "Editar Prefixo do Branch",
|
||||
"help_on_tree_prefix": "Ajuda sobre o prefixo da árvore de notas",
|
||||
"branch_prefix_saved": "O prefixo de ramificação foi salvo."
|
||||
},
|
||||
"bulk_actions": {
|
||||
"bulk_actions": "Ações em massa",
|
||||
"close": "Fechar",
|
||||
"affected_notes": "Notas Afetadas",
|
||||
"include_descendants": "Incluir notas filhas das notas selecionadas",
|
||||
"available_actions": "Ações disponíveis",
|
||||
"chosen_actions": "Ações selecionadas",
|
||||
"execute_bulk_actions": "Executar ações em massa",
|
||||
"bulk_actions_executed": "As ações em massa foram concluídas com sucesso.",
|
||||
"none_yet": "Nenhuma até agora... adicione uma ação clicando em uma das disponíveis acima.",
|
||||
"labels": "Etiquetas",
|
||||
"relations": "Relações",
|
||||
"notes": "Notas",
|
||||
"other": "Outros"
|
||||
},
|
||||
"clone_to": {
|
||||
"clone_notes_to": "Clonar notas para...",
|
||||
"close": "Fechar",
|
||||
"help_on_links": "Ajuda sobre links",
|
||||
"notes_to_clone": "Notas para clonar",
|
||||
"search_for_note_by_its_name": "pesquisar nota pelo nome",
|
||||
"cloned_note_prefix_title": "A nota clonada aparecerá na árvore de notas com o prefixo fornecido",
|
||||
"prefix_optional": "Prefixo (opcional)",
|
||||
"no_path_to_clone_to": "Nenhum caminho para clonar.",
|
||||
"target_parent_note": "Nota pai-alvo",
|
||||
"clone_to_selected_note": "Clonar para a nota selecionada <kbd>enter</kbd>",
|
||||
"note_cloned": "A nota \"{{clonedTitle}}\" foi clonada para \"{{targetTitle}}\""
|
||||
},
|
||||
"ai_llm": {
|
||||
"n_notes_queued_0": "{{ count }} nota enfileirada para indexação",
|
||||
"n_notes_queued_1": "{{ count }} notas enfileiradas para indexação",
|
||||
"n_notes_queued_2": "{{ count }} notas enfileiradas para indexação",
|
||||
"notes_indexed_0": "{{ count }} nota indexada",
|
||||
"notes_indexed_1": "{{ count }} notas indexadas",
|
||||
"notes_indexed_2": "{{ count }} notas indexadas"
|
||||
},
|
||||
"confirm": {
|
||||
"confirmation": "Confirmação",
|
||||
"close": "Fechar",
|
||||
"cancel": "Cancelar",
|
||||
"ok": "OK",
|
||||
"are_you_sure_remove_note": "Tem certeza de que deseja remover a nota '{{title}}' do mapa de relações? ",
|
||||
"if_you_dont_check": "Se você não marcar isso, a nota será removida apenas do mapa de relações.",
|
||||
"also_delete_note": "Também excluir a nota"
|
||||
},
|
||||
"delete_notes": {
|
||||
"delete_notes_preview": "Excluir pré-visualização de notas",
|
||||
"close": "Fechar",
|
||||
"delete_all_clones_description": "Excluir também todos os clones (pode ser desfeito em alterações recentes)",
|
||||
"erase_notes_description": "A exclusão normal (suave) apenas marca as notas como excluídas, permitindo que sejam recuperadas (no diálogo de alterações recentes) dentro de um período de tempo. Se esta opção for marcada, as notas serão apagadas imediatamente e não será possível restaurá-las.",
|
||||
"erase_notes_warning": "Apagar notas permanentemente (não pode ser desfeito), incluindo todos os clones. Isso forçará o recarregamento do aplicativo.",
|
||||
"notes_to_be_deleted": "As seguintes notas serão excluídas ({{- noteCount}})",
|
||||
"no_note_to_delete": "Nenhuma nota será excluída (apenas os clones).",
|
||||
"broken_relations_to_be_deleted": "As seguintes relações serão quebradas e excluídas ({{- relationCount}})",
|
||||
"cancel": "Cancelar",
|
||||
"ok": "OK",
|
||||
"deleted_relation_text": "A nota {{- note}} (a ser excluída) está referenciada pela relação {{- relation}} originada de {{- source}}."
|
||||
},
|
||||
"export": {
|
||||
"export_note_title": "Exportar nota",
|
||||
"close": "Fechar",
|
||||
"export_type_subtree": "Esta nota e todos os seus descendentes",
|
||||
"format_html": "HTML – recomendado, pois mantém toda a formatação",
|
||||
"format_html_zip": "HTML em arquivo ZIP – recomendado, pois isso preserva toda a formatação.",
|
||||
"format_markdown": "Markdown – isso preserva a maior parte da formatação.",
|
||||
"format_opml": "OPML - formato de intercâmbio de outliners apenas para texto. Formatação, imagens e arquivos não estão incluídos.",
|
||||
"opml_version_1": "OPML v1.0 – apenas texto simples",
|
||||
"opml_version_2": "OPML v2.0 – permite também HTML",
|
||||
"export_type_single": "Apenas esta nota, sem seus descendentes",
|
||||
"export": "Exportar",
|
||||
"choose_export_type": "Por favor, escolha primeiro o tipo de exportação",
|
||||
"export_status": "Status da exportação",
|
||||
"export_in_progress": "Exportação em andamento: {{progressCount}}",
|
||||
"export_finished_successfully": "Exportação concluída com sucesso.",
|
||||
"format_pdf": "PDF – para impressão ou compartilhamento."
|
||||
},
|
||||
"help": {
|
||||
"fullDocumentation": "Ajuda (a documentação completa está disponível <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">online</a>)",
|
||||
"close": "Fechar",
|
||||
"noteNavigation": "Navegação de notas",
|
||||
"goUpDown": "<kbd>UP</kbd>, <kbd>DOWN</kbd> – subir/descer na lista de notas",
|
||||
"collapseExpand": "<kbd>ESQUERDA</kbd>, <kbd>DIREITA</kbd> – recolher/expandir nó",
|
||||
"notSet": "não definido",
|
||||
"goBackForwards": "voltar / avançar no histórico",
|
||||
"showJumpToNoteDialog": "mostrar diálogo <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Ir para\"</a>",
|
||||
"scrollToActiveNote": "rolar até a nota atual",
|
||||
"jumpToParentNote": "<kbd>Backspace</kbd> – ir para a nota pai",
|
||||
"collapseWholeTree": "recolher toda a árvore de notas",
|
||||
"collapseSubTree": "recolher subárvore",
|
||||
"tabShortcuts": "Atalhos de abas",
|
||||
"newTabNoteLink": "<kbd>Ctrl+clique</kbd> – (ou <kbd>clique com o botão do meio do mouse</kbd>) em um link de nota abre a nota em uma nova aba",
|
||||
"newTabWithActivationNoteLink": "<kbd>Ctrl+Shift+clique</kbd> – (ou <kbd>Shift+clique com o botão do meio do mouse</kbd>) em um link de nota abre e ativa a nota em uma nova aba",
|
||||
"onlyInDesktop": "Apenas na versão para desktop (compilação Electron)",
|
||||
"openEmptyTab": "abrir aba vazia",
|
||||
"closeActiveTab": "fechar aba ativa",
|
||||
"activateNextTab": "ativar próxima aba",
|
||||
"activatePreviousTab": "ativar aba anterior",
|
||||
"creatingNotes": "Criando notas",
|
||||
"createNoteAfter": "criar nova nota após a nota atual",
|
||||
"createNoteInto": "criar nova subnota dentro da nota atual",
|
||||
"editBranchPrefix": "editar <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/tree-concepts.html#prefix\">prefixo</a> do clone da nota ativa",
|
||||
"movingCloningNotes": "Movendo / clonando notas",
|
||||
"moveNoteUpDown": "mover nota para cima/baixo na lista de notas",
|
||||
"moveNoteUpHierarchy": "mover nota para cima na hierarquia",
|
||||
"multiSelectNote": "selecionar múltiplas notas acima/abaixo",
|
||||
"selectAllNotes": "selecionar todas as notas no nível atual",
|
||||
"selectNote": "<kbd>Shift+clique</kbd> - selecionar nota",
|
||||
"copyNotes": "copiar nota ativa (ou seleção atual) para a área de transferência (usado para <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">clonar</a>)",
|
||||
"cutNotes": "recortar nota atual (ou seleção atual) para a área de transferência (usado para mover notas)",
|
||||
"pasteNotes": "colar nota(s) como subnota dentro da nota ativa (o que pode ser mover ou clonar dependendo se foi copiado ou recortado para a área de transferência)",
|
||||
"deleteNotes": "excluir nota / subárvore",
|
||||
"editingNotes": "Edição de notas",
|
||||
"editNoteTitle": "no painel de árvore, a navegação mudará do painel de árvore para o título da nota. Pressionar Enter no título da nota mudará o foco para o editor de texto. <kbd>Ctrl+.</kbd> mudará o foco de volta do editor para o painel de árvore.",
|
||||
"createEditLink": "<kbd>Ctrl+K</kbd> - criar / editar link externo",
|
||||
"createInternalLink": "criar link interno",
|
||||
"followLink": "seguir link sob o cursor",
|
||||
"insertDateTime": "inserir data e hora atual na posição do cursor",
|
||||
"jumpToTreePane": "ir para a árvore de notas e rolar até a nota ativa",
|
||||
"markdownAutoformat": "Autoformatação estilo Markdown",
|
||||
"headings": "<code>##</code>, <code>###</code>, <code>####</code> etc. seguidos de espaço para títulos",
|
||||
"bulletList": "<code>*</code> ou <code>-</code> seguidos de espaço para lista com marcadores",
|
||||
"numberedList": "<code>1.</code> ou <code>1)</code> seguidos de espaço para lista numerada",
|
||||
"blockQuote": "comece uma linha com <code>></code> seguido de espaço para citação em bloco",
|
||||
"troubleshooting": "Solução de problemas",
|
||||
"reloadFrontend": "recarregar o frontend do Trilium",
|
||||
"showDevTools": "mostrar ferramentas de desenvolvedor",
|
||||
"showSQLConsole": "mostrar console SQL",
|
||||
"other": "Outros",
|
||||
"quickSearch": "focar no campo de pesquisa rápida",
|
||||
"inPageSearch": "pesquisa na página"
|
||||
},
|
||||
"import": {
|
||||
"importIntoNote": "Importar para a nota",
|
||||
"close": "Fechar",
|
||||
"chooseImportFile": "Escolher arquivo para importar",
|
||||
"importDescription": "O conteúdo do(s) arquivo(s) selecionado(s) será importado como nota(s) filha(s) em",
|
||||
"options": "Opções",
|
||||
"safeImportTooltip": "Arquivos de exportação Trilium<code> .zip</code> podem conter scripts executáveis que podem apresentar comportamentos prejudiciais. A importação segura desativará a execução automática de todos os scripts importados. Desmarque “Importação segura” apenas se o arquivo de importação contiver scripts executáveis e você confiar totalmente no conteúdo do arquivo importado.",
|
||||
"safeImport": "Importação segura",
|
||||
"explodeArchivesTooltip": "Se esta opção estiver marcada, o Trilium irá ler arquivos <code>.zip</code>, <code>.enex</code> e <code>.opml</code> e criar notas a partir dos arquivos contidos nesses arquivos compactados. Se estiver desmarcada, o Trilium irá anexar os próprios arquivos compactados à nota.",
|
||||
"explodeArchives": "Ler conteúdos de arquivos <code>.zip</code>, <code>.enex</code> e <code>.opml</code>.",
|
||||
"shrinkImagesTooltip": "<p>Se você marcar esta opção, o Trilium tentará reduzir o tamanho das imagens importadas por meio de escala e otimização, o que pode afetar a qualidade visual das imagens. Se desmarcada, as imagens serão importadas sem alterações.</p><p>Isso não se aplica a importações de arquivos <code>.zip</code> com metadados, pois presume-se que esses arquivos já estejam otimizados.</p>",
|
||||
"shrinkImages": "Reduzir imagens",
|
||||
"textImportedAsText": "Importar arquivos HTML, Markdown e TXT como notas de texto caso não esteja claro pelos metadados",
|
||||
"codeImportedAsCode": "Importar arquivos de código reconhecidos (por exemplo, <code>.json</code>) como notas de código caso não esteja claro pelos metadados",
|
||||
"replaceUnderscoresWithSpaces": "Substituir sublinhados por espaços nos nomes das notas importadas",
|
||||
"import": "Importar",
|
||||
"failed": "Falha na importação: {{message}}.",
|
||||
"html_import_tags": {
|
||||
"title": "Tags de importação HTML",
|
||||
"description": "Configurar quais tags HTML devem ser preservadas ao importar notas. As tags que não estiverem nesta lista serão removidas durante a importação. Algumas tags (como 'script') são sempre removidas por motivos de segurança.",
|
||||
"placeholder": "Digite as tags HTML, uma por linha",
|
||||
"reset_button": "Redefinir para lista padrão"
|
||||
},
|
||||
"import-status": "Status da importação",
|
||||
"in-progress": "Importação em andamento: {{progress}}",
|
||||
"successful": "Importação concluída com sucesso."
|
||||
},
|
||||
"include_note": {
|
||||
"dialog_title": "Incluir nota",
|
||||
"close": "Fechar",
|
||||
"label_note": "Nota",
|
||||
"placeholder_search": "pesquisar nota pelo nome",
|
||||
"box_size_prompt": "Dimensão da caixa da nota incluída:",
|
||||
"box_size_small": "pequeno (~ 10 linhas)",
|
||||
"box_size_medium": "médio (~ 30 linhas)",
|
||||
"box_size_full": "completo (a caixa exibe o texto completo)",
|
||||
"button_include": "Incluir nota <kbd>enter</kbd>"
|
||||
},
|
||||
"info": {
|
||||
"modalTitle": "Mensagem informativa",
|
||||
"closeButton": "Fechar",
|
||||
"okButton": "OK"
|
||||
},
|
||||
"jump_to_note": {
|
||||
"search_placeholder": "Pesquise uma nota pelo nome ou digite > para comandos...",
|
||||
"close": "Fechar",
|
||||
"search_button": "Pesquisar em texto completo <kbd>Ctrl+Enter</kbd>"
|
||||
},
|
||||
"markdown_import": {
|
||||
"dialog_title": "Importar Markdown",
|
||||
"close": "Fechar",
|
||||
"modal_body_text": "Por motivos de segurança (sandbox do navegador), o JavaScript não pode acessar diretamente a área de transferência. Por favor, cole o conteúdo Markdown na área de texto abaixo e clique em Importar",
|
||||
"import_button": "Importar Ctrl+Enter",
|
||||
"import_success": "O conteúdo Markdown foi importado para o documento."
|
||||
},
|
||||
"move_to": {
|
||||
"dialog_title": "Mover notas para...",
|
||||
"close": "Fechar",
|
||||
"notes_to_move": "Notas para mover",
|
||||
"target_parent_note": "Nota pai-alvo",
|
||||
"search_placeholder": "pesquisar nota pelo nome",
|
||||
"move_button": "Mover para a nota selecionada <kbd>enter</kbd>",
|
||||
"error_no_path": "Nenhum caminho para mover.",
|
||||
"move_success_message": "As notas selecionadas foram movidas para "
|
||||
},
|
||||
"note_type_chooser": {
|
||||
"change_path_prompt": "Alterar onde criar a nova nota:",
|
||||
"search_placeholder": "buscar caminho pelo nome (valor padrão se não for preenchido)",
|
||||
"modal_title": "Escolher tipo de nota",
|
||||
"close": "Fechar",
|
||||
"modal_body": "Escolha o tipo/modelo da nova nota:",
|
||||
"templates": "Modelos:"
|
||||
},
|
||||
"password_not_set": {
|
||||
"title": "A senha não está definida",
|
||||
"close": "Fechar",
|
||||
"body1": "Notas protegidas são criptografadas usando uma senha do usuário, mas a senha ainda não foi definida.",
|
||||
"body2": "Para poder proteger notas, clique <a class=\"open-password-options-button\" href=\"javascript:\">aqui</a> para abrir a caixa de diálogo de Opções e definir sua senha."
|
||||
},
|
||||
"prompt": {
|
||||
"title": "Prompt",
|
||||
"close": "Fechar",
|
||||
"ok": "OK <kbd>enter</kbd>",
|
||||
"defaultTitle": "Prompt"
|
||||
},
|
||||
"protected_session_password": {
|
||||
"modal_title": "Sessão Protegida",
|
||||
"help_title": "Ajuda sobre notas protegidas",
|
||||
"close_label": "Fechar",
|
||||
"form_label": "Para prosseguir com a ação solicitada, você precisa iniciar uma sessão protegida digitando a senha:",
|
||||
"start_button": "Iniciar sessão protegida <kbd>enter</kbd>"
|
||||
},
|
||||
"recent_changes": {
|
||||
"title": "Alterações recentes",
|
||||
"erase_notes_button": "Remover permanentemente as notas excluídas agora",
|
||||
"close": "Fechar",
|
||||
"deleted_notes_message": "As notas excluídas foram removidas permanentemente.",
|
||||
"no_changes_message": "Nenhuma alteração ainda...",
|
||||
"undelete_link": "Restaurar",
|
||||
"confirm_undelete": "Você deseja restaurar esta nota e suas subnotas?"
|
||||
},
|
||||
"revisions": {
|
||||
"note_revisions": "Versões da nota",
|
||||
"delete_all_revisions": "Excluir todas as versões desta nota",
|
||||
"delete_all_button": "Excluir todas as versões",
|
||||
"help_title": "Ajuda sobre as versões da nota",
|
||||
"close": "Fechar",
|
||||
"revision_last_edited": "Esta versão foi editada pela última vez em {{date}}",
|
||||
"confirm_delete_all": "Você quer excluir todas as versões desta nota?",
|
||||
"no_revisions": "Ainda não há versões para esta nota...",
|
||||
"restore_button": "Recuperar",
|
||||
"confirm_restore": "Deseja restaurar esta versão? Isso irá substituir o título e o conteúdo atuais da nota por esta versão.",
|
||||
"delete_button": "Excluir",
|
||||
"confirm_delete": "Deseja excluir esta versão?",
|
||||
"revisions_deleted": "As versões da nota foram removidas.",
|
||||
"revision_restored": "A versão da nota foi restaurada.",
|
||||
"revision_deleted": "A versão da nota foi excluída.",
|
||||
"snapshot_interval": "Intervalo de captura das versões da nota: {{seconds}}s.",
|
||||
"maximum_revisions": "Limite de capturas das versões da nota: {{number}}.",
|
||||
"settings": "Configurações de versões da nota",
|
||||
"download_button": "Download",
|
||||
"mime": "MIME: ",
|
||||
"file_size": "Tamanho do arquivo:",
|
||||
"preview": "Visualizar:",
|
||||
"preview_not_available": "A visualização não está disponível para este tipo de nota."
|
||||
},
|
||||
"sort_child_notes": {
|
||||
"sort_children_by": "Ordenar notas filhas por...",
|
||||
"close": "Fechar",
|
||||
"sorting_criteria": "Critérios de ordenação",
|
||||
"title": "título",
|
||||
"date_created": "data de criação",
|
||||
"date_modified": "data de modificação",
|
||||
"sorting_direction": "Direção de ordenação",
|
||||
"ascending": "crescente",
|
||||
"descending": "decrescente",
|
||||
"folders": "Pastas",
|
||||
"sort_folders_at_top": "colocar pastas no topo",
|
||||
"natural_sort": "Ordenação Natural",
|
||||
"sort_with_respect_to_different_character_sorting": "classificar de acordo com diferentes regras de ordenação de caracteres e colação em diferentes idiomas ou regiões.",
|
||||
"natural_sort_language": "Linguagem da ordenação natural",
|
||||
"the_language_code_for_natural_sort": "O código do idioma para ordenação natural, por exemplo, \"zh-CN\" para chinês.",
|
||||
"sort": "Ordenar <kbd>enter</kbd>"
|
||||
},
|
||||
"upload_attachments": {
|
||||
"upload_attachments_to_note": "Enviar anexos para a nota",
|
||||
"close": "Fechar",
|
||||
"choose_files": "Escolher arquivos",
|
||||
"files_will_be_uploaded": "Os arquivos serão enviados como anexos para",
|
||||
"options": "Opções",
|
||||
"shrink_images": "Reduzir imagens",
|
||||
"upload": "Enviar",
|
||||
"tooltip": "Se você marcar esta opção, o Trilium tentará reduzir as imagens enviadas redimensionando e otimizando, o que pode afetar a qualidade visual percebida. Se desmarcada, as imagens serão enviadas sem alterações."
|
||||
},
|
||||
"attribute_detail": {
|
||||
"attr_detail_title": "Título Detalhado do Atributo",
|
||||
"close_button_title": "Cancelar alterações e fechar",
|
||||
"attr_is_owned_by": "O atributo pertence a",
|
||||
"attr_name_title": "O nome do atributo pode ser composto apenas por caracteres alfanuméricos, dois-pontos e sublinhado",
|
||||
"name": "Nome",
|
||||
"value": "Valor",
|
||||
"target_note_title": "Relação é uma conexão nomeada entre a nota de origem e a nota de destino.",
|
||||
"target_note": "Nota de destino",
|
||||
"promoted_title": "O atributo promovido é exibido de forma destacada na nota.",
|
||||
"promoted": "Promovido",
|
||||
"promoted_alias_title": "O nome a ser exibido na interface de atributos promovidos.",
|
||||
"promoted_alias": "Alias",
|
||||
"multiplicity_title": "Multiplicidade define quantos atributos com o mesmo nome podem ser criados — no máximo 1 ou mais de 1.",
|
||||
"multiplicity": "Multiplicidade",
|
||||
"single_value": "Valor único",
|
||||
"multi_value": "Valor múltiplo",
|
||||
"label_type_title": "O tipo do rótulo ajudará o Trilium a escolher a interface adequada para inserir o valor do rótulo.",
|
||||
"label_type": "Tipo",
|
||||
"text": "Texto",
|
||||
"number": "Número",
|
||||
"boolean": "Booleano",
|
||||
"date": "Data",
|
||||
"date_time": "Data e Hora",
|
||||
"time": "Hora",
|
||||
"url": "URL",
|
||||
"precision_title": "Qual número de dígitos após o ponto decimal deve estar disponível na interface de configuração de valor.",
|
||||
"precision": "Precisão",
|
||||
"digits": "dígitos",
|
||||
"inverse_relation_title": "Configuração opcional para definir a qual relação esta é oposta. Exemplo: Pai - Filho são relações inversas entre si.",
|
||||
"inverse_relation": "Relação inversa",
|
||||
"inheritable_title": "O atributo herdável será transmitido para todos os descendentes deste ramo.",
|
||||
"inheritable": "Herdável",
|
||||
"save_and_close": "Salvar e fechar <kbd>Ctrl+Enter</kbd>",
|
||||
"delete": "Excluir",
|
||||
"related_notes_title": "Outras notas com este rótulo",
|
||||
"more_notes": "Mais notas",
|
||||
"label": "Detalhe do rótulo",
|
||||
"label_definition": "Detalhe da definição do rótulo",
|
||||
"relation": "Detalhe da relação",
|
||||
"relation_definition": "Detalhe da definição da relação",
|
||||
"disable_versioning": "desativa a versão automática. Útil, por exemplo, para notas grandes, mas sem importância – como grandes bibliotecas JS usadas para scripts",
|
||||
"calendar_root": "marca a nota que deve ser usada como raiz para notas diárias. Apenas uma deve ser marcada assim.",
|
||||
"archived": "notas com este rótulo não serão exibidas por padrão nos resultados de busca (também nos diálogos Ir para, Adicionar link, etc).",
|
||||
"exclude_from_export": "notas (junto com sua subárvore) não serão incluídas em nenhuma exportação de notas",
|
||||
"run": "define em quais eventos o script deve ser executado. Os valores possíveis são:\n<ul>\n<li>frontendStartup - quando o frontend do Trilium inicia (ou é atualizado), mas não no celular.</li>\n<li>mobileStartup - quando o frontend do Trilium inicia (ou é atualizado), no celular.</li>\n<li>backendStartup - quando o backend do Trilium inicia</li>\n<li>hourly - executa uma vez por hora. Você pode usar o rótulo adicional <code>runAtHour</code> para especificar em qual hora.</li>\n<li>daily - executa uma vez por dia</li>\n</ul>",
|
||||
"run_on_instance": "Define em qual instância do Trilium isso deve ser executado. Por padrão, todas as instâncias.",
|
||||
"run_at_hour": "Em qual hora isso deve ser executado. Deve ser usado junto com <code>#run=hourly</code>. Pode ser definido várias vezes para executar mais de uma vez ao dia.",
|
||||
"disable_inclusion": "scripts com este rótulo não serão incluídos na execução do script pai.",
|
||||
"sorted": "mantém as notas filhas ordenadas alfabeticamente pelo título",
|
||||
"sort_direction": "ASC (padrão) ou DESC",
|
||||
"sort_folders_first": "Pastas (notas com filhos) devem ser ordenadas no topo",
|
||||
"top": "mantenha a nota fornecida no topo em seu pai (aplica-se apenas a pais ordenados)",
|
||||
"hide_promoted_attributes": "Ocultar atributos promovidos nesta nota",
|
||||
"read_only": "o editor está em modo somente leitura. Funciona apenas para notas de texto e código.",
|
||||
"auto_read_only_disabled": "notas de texto/código podem ser automaticamente configuradas para modo de leitura quando são muito grandes. Você pode desabilitar esse comportamento por nota adicionando este rótulo à nota",
|
||||
"app_css": "marca notas CSS que são carregadas no aplicativo Trilium e, portanto, podem ser usadas para modificar a aparência do Trilium.",
|
||||
"app_theme": "marca notas CSS que são temas completos do Trilium e, portanto, estão disponíveis nas opções do Trilium.",
|
||||
"app_theme_base": "defina como \"next\", \"next-light\" ou \"next-dark\" para usar o tema TriliumNext correspondente (auto, claro ou escuro) como base para um tema personalizado, em vez do tema legado.",
|
||||
"css_class": "o valor deste rótulo é então adicionado como classe CSS ao nó que representa a nota específica na árvore. Isso pode ser útil para temas avançados. Pode ser usado em notas de modelo.",
|
||||
"icon_class": "o valor deste rótulo é adicionado como uma classe CSS ao ícone na árvore, o que pode ajudar a distinguir visualmente as notas na árvore. Um exemplo pode ser bx bx-home – os ícones são retirados do boxicons. Pode ser usado em notas de modelo.",
|
||||
"page_size": "número de itens por página na listagem de notas",
|
||||
"custom_request_handler": "veja <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Manipulador de requisição personalizada</a>",
|
||||
"custom_resource_provider": "veja <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Manipulador de requisição personalizada</a>",
|
||||
"widget": "marca esta nota como um widget personalizado que será adicionado à árvore de componentes do Trilium",
|
||||
"workspace": "marca esta nota como um espaço de trabalho, o que permite fácil hoisting",
|
||||
"workspace_icon_class": "define a classe CSS do ícone box que será usada na aba quando esta nota for hoisted",
|
||||
"workspace_tab_background_color": "cor CSS usada na aba da nota quando esta nota é hoisted",
|
||||
"workspace_calendar_root": "Define a raiz do calendário por espaço de trabalho",
|
||||
"workspace_template": "Esta nota aparecerá na seleção de modelos disponíveis ao criar uma nova nota, mas apenas quando estiver destacada em um espaço de trabalho que contenha este modelo",
|
||||
"search_home": "novas notas de pesquisa serão criadas como filhas desta nota",
|
||||
"workspace_search_home": "novas notas de pesquisa serão criadas como filhas desta nota quando ela for destacada para algum ancestral desta nota de área de trabalho",
|
||||
"inbox": "localização padrão da caixa de entrada para novas notas – quando você cria uma nota usando o botão \"nova nota\" na barra lateral, as notas serão criadas como notas filhas na nota marcada com o rótulo <code>#inbox</code>.",
|
||||
"workspace_inbox": "local padrão da caixa de entrada para novas notas quando esta nota for destacada para algum ancestral desta nota de área de trabalho",
|
||||
"sql_console_home": "localização padrão das notas do console SQL",
|
||||
"bookmark_folder": "nota com este rótulo aparecerá nos favoritos como uma pasta (permitindo acesso aos seus filhos)",
|
||||
"share_hidden_from_tree": "esta nota está oculta na árvore de navegação à esquerda, mas ainda pode ser acessada via sua URL",
|
||||
"share_external_link": "a nota funcionará como um link para um site externo na árvore de compartilhamento"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1260
apps/client/src/translations/ru/translation.json
Normal file
1260
apps/client/src/translations/ru/translation.json
Normal file
File diff suppressed because it is too large
Load Diff
517
apps/client/src/translations/sr/translation.json
Normal file
517
apps/client/src/translations/sr/translation.json
Normal file
@@ -0,0 +1,517 @@
|
||||
{
|
||||
"about": {
|
||||
"title": "O Trilium Belеškama",
|
||||
"close": "Zatvori",
|
||||
"homepage": "Početna stranica:",
|
||||
"app_version": "Verzija aplikacije:",
|
||||
"db_version": "Verzija baze podataka:",
|
||||
"sync_version": "Verzija sinhronizacije:",
|
||||
"build_date": "Datum izgradnje:",
|
||||
"build_revision": "Revizija izgradnje:",
|
||||
"data_directory": "Direktorijum sa podacima:"
|
||||
},
|
||||
"toast": {
|
||||
"critical-error": {
|
||||
"title": "Kritična greška",
|
||||
"message": "Došlo je do kritične greške koja sprečava pokretanje klijentske aplikacije.\n\n{{message}}\n\nOva greška je najverovatnije izazvana neočekivanim problemom prilikom izvršavanja skripte. Pokušajte da pokrenete aplikaciju u bezbednom režimu i da pronađete šta izaziva grešku."
|
||||
},
|
||||
"widget-error": {
|
||||
"title": "Pokretanje vidžeta nije uspelo",
|
||||
"message-custom": "Prilagođeni viđet sa beleške sa ID-jem \"{{id}}\", nazivom \"{{title}}\" nije uspeo da se pokrene zbog:\n\n{{message}}",
|
||||
"message-unknown": "Nepoznati vidžet nije mogao da se pokrene zbog:\n\n{{message}}"
|
||||
},
|
||||
"bundle-error": {
|
||||
"title": "Pokretanje prilagođene skripte neuspešno",
|
||||
"message": "Skripta iz beleške sa ID-jem \"{{id}}\", naslovom \"{{title}}\" nije mogla da se izvrši zbog:\n\n{{message}}"
|
||||
}
|
||||
},
|
||||
"add_link": {
|
||||
"add_link": "Dodaj link",
|
||||
"help_on_links": "Pomoć na linkovima",
|
||||
"close": "Zatvori",
|
||||
"note": "Beleška",
|
||||
"search_note": "potražite belešku po njenom imenu",
|
||||
"link_title_mirrors": "naziv linka preslikava trenutan naziv beleške",
|
||||
"link_title_arbitrary": "naziv linka se može proizvoljno menjati",
|
||||
"link_title": "Naziv linka",
|
||||
"button_add_link": "Dodaj link <kbd>enter</kbd>"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"edit_branch_prefix": "Izmeni prefiks grane",
|
||||
"help_on_tree_prefix": "Pomoć na prefiksu Drveta",
|
||||
"close": "Zatvori",
|
||||
"prefix": "Prefiks: ",
|
||||
"save": "Sačuvaj",
|
||||
"branch_prefix_saved": "Prefiks grane je sačuvan."
|
||||
},
|
||||
"bulk_actions": {
|
||||
"bulk_actions": "Grupne akcije",
|
||||
"close": "Zatvori",
|
||||
"affected_notes": "Pogođene beleške",
|
||||
"include_descendants": "Obuhvati potomke izabranih beleški",
|
||||
"available_actions": "Dostupne akcije",
|
||||
"chosen_actions": "Izabrane akcije",
|
||||
"execute_bulk_actions": "Izvrši grupne akcije",
|
||||
"bulk_actions_executed": "Grupne akcije su uspešno izvršene.",
|
||||
"none_yet": "Nijedna za sad... dodajte akciju tako što ćete pritisnuti na neku od dostupnih akcija iznad.",
|
||||
"labels": "Oznake",
|
||||
"relations": "Odnosi",
|
||||
"notes": "Beleške",
|
||||
"other": "Ostalo"
|
||||
},
|
||||
"clone_to": {
|
||||
"clone_notes_to": "Klonirajte beleške u...",
|
||||
"close": "Zatvori",
|
||||
"help_on_links": "Pomoć na linkovima",
|
||||
"notes_to_clone": "Beleške za kloniranje",
|
||||
"target_parent_note": "Ciljna nadređena beleška",
|
||||
"search_for_note_by_its_name": "potražite belešku po njenom imenu",
|
||||
"cloned_note_prefix_title": "Klonirana beleška će biti prikazana u drvetu beleški sa datim prefiksom",
|
||||
"prefix_optional": "Prefiks (opciono)",
|
||||
"clone_to_selected_note": "Kloniranje u izabranu belešku <kbd>enter</kbd>",
|
||||
"no_path_to_clone_to": "Nema putanje za kloniranje.",
|
||||
"note_cloned": "Beleška \"{{clonedTitle}}\" je klonirana u \"{{targetTitle}}\""
|
||||
},
|
||||
"confirm": {
|
||||
"confirmation": "Potvrda",
|
||||
"close": "Zatvori",
|
||||
"cancel": "Otkaži",
|
||||
"ok": "U redu",
|
||||
"are_you_sure_remove_note": "Da li ste sigurni da želite da uklonite belešku \"{{title}}\" iz mape odnosa? ",
|
||||
"if_you_dont_check": "Ako ne izaberete ovo, beleška će biti uklonjena samo sa mape odnosa.",
|
||||
"also_delete_note": "Takođe obriši belešku"
|
||||
},
|
||||
"delete_notes": {
|
||||
"delete_notes_preview": "Obriši pregled beleške",
|
||||
"close": "Zatvori",
|
||||
"delete_all_clones_description": "Obriši i sve klonove (može biti poništeno u skorašnjim izmenama)",
|
||||
"erase_notes_description": "Normalno (blago) brisanje samo označava beleške kao obrisane i one mogu biti vraćene (u dijalogu skorašnjih izmena) u određenom vremenskom periodu. Biranje ove opcije će momentalno obrisati beleške i ove beleške neće biti moguće vratiti.",
|
||||
"erase_notes_warning": "Trajno obriši beleške (ne može se opozvati), uključujući sve klonove. Ovo će prisiliti aplikaciju da se ponovo pokrene.",
|
||||
"notes_to_be_deleted": "Sledeće beleške će biti obrisane ({{- noteCount}})",
|
||||
"no_note_to_delete": "Nijedna beleška neće biti obrisana (samo klonovi).",
|
||||
"broken_relations_to_be_deleted": "Sledeći odnosi će biti prekinuti i obrisani ({{- relationCount}})",
|
||||
"cancel": "Otkaži",
|
||||
"ok": "U redu",
|
||||
"deleted_relation_text": "Beleška {{- note}} (za brisanje) je referencirana sa odnosom {{- relation}} koji potiče iz {{- source}}."
|
||||
},
|
||||
"export": {
|
||||
"export_note_title": "Izvezi belešku",
|
||||
"close": "Zatvori",
|
||||
"export_type_subtree": "Ova beleška i svi njeni potomci",
|
||||
"format_html": "HTML - preporučuje se jer čuva formatiranje",
|
||||
"format_html_zip": "HTML u ZIP arhivi - ovo se preporučuje jer se na taj način čuva celokupno formatiranje.",
|
||||
"format_markdown": "Markdown - ovo čuva većinu formatiranja.",
|
||||
"format_opml": "OPML - format za razmenu okvira samo za tekst. Formatiranje, slike i datoteke nisu uključeni.",
|
||||
"opml_version_1": "OPML v1.0 - samo običan tekst",
|
||||
"opml_version_2": "OPML v2.0 - dozvoljava i HTML",
|
||||
"export_type_single": "Samo ovu belešku bez njenih potomaka",
|
||||
"export": "Izvoz",
|
||||
"choose_export_type": "Molimo vas da prvo izaberete tip izvoza",
|
||||
"export_status": "Status izvoza",
|
||||
"export_in_progress": "Izvoz u toku: {{progressCount}}",
|
||||
"export_finished_successfully": "Izvoz je uspešno završen.",
|
||||
"format_pdf": "PDF - za namene štampanja ili deljenja."
|
||||
},
|
||||
"help": {
|
||||
"fullDocumentation": "Pomoć (puna dokumentacija je dostupna <a class=\"external\" href=\"https://triliumnext.github.io/Docs/\">online</a>)",
|
||||
"close": "Zatvori",
|
||||
"noteNavigation": "Navigacija beleški",
|
||||
"goUpDown": "<kbd>UP</kbd>, <kbd>DOWN</kbd> - kretanje gore/dole u listi sa beleškama",
|
||||
"collapseExpand": "<kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - sakupi/proširi čvor",
|
||||
"notSet": "nije podešeno",
|
||||
"goBackForwards": "idi u nazad/napred kroz istoriju",
|
||||
"showJumpToNoteDialog": "prikaži <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/note-navigation.html#jump-to-note\">\"Idi na\" dijalog</a>",
|
||||
"scrollToActiveNote": "skroluj do aktivne beleške",
|
||||
"jumpToParentNote": "<kbd>Backspace</kbd> - idi do nadređene beleške",
|
||||
"collapseWholeTree": "sakupi celo drvo beleški",
|
||||
"collapseSubTree": "sakupi pod-drvo",
|
||||
"tabShortcuts": "Prečice na karticama",
|
||||
"newTabNoteLink": "<kbd>Ctrl+click</kbd> - (ili <kbd>middle mouse click</kbd>) na link beleške otvara belešku u novoj kartici",
|
||||
"newTabWithActivationNoteLink": "<kbd>Ctrl+Shift+click</kbd> - (ili <kbd>Shift+middle mouse click</kbd>) na link beleške otvara i aktivira belešku u novoj kartici",
|
||||
"onlyInDesktop": "Samo na dektop-u (Electron verzija)",
|
||||
"openEmptyTab": "otvori praznu karticu",
|
||||
"closeActiveTab": "zatvori aktivnu karticu",
|
||||
"activateNextTab": "aktiviraj narednu karticu",
|
||||
"activatePreviousTab": "aktiviraj prethodnu karticu",
|
||||
"creatingNotes": "Pravljenje beleški",
|
||||
"createNoteAfter": "napravi novu belešku nakon aktivne beleške",
|
||||
"createNoteInto": "napravi novu pod-belešku u aktivnoj belešci",
|
||||
"editBranchPrefix": "izmeni <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/tree-concepts.html#prefix\">prefiks</a> klona aktivne beleške",
|
||||
"movingCloningNotes": "Premeštanje / kloniranje beleški",
|
||||
"moveNoteUpDown": "pomeri belešku gore/dole u listi beleški",
|
||||
"moveNoteUpHierarchy": "pomeri belešku na gore u hijerarhiji",
|
||||
"multiSelectNote": "višestruki izbor beleški iznad/ispod",
|
||||
"selectAllNotes": "izaberi sve beleške u trenutnom nivou",
|
||||
"selectNote": "<kbd>Shift+click</kbd> - izaberi belešku",
|
||||
"copyNotes": "kopiraj aktivnu belešku (ili trenutni izbor) u privremenu memoriju (koristi se za <a class=\"external\" href=\"https://triliumnext.github.io/Docs/Wiki/cloning-notes.html#cloning-notes\">kloniranje</a>)",
|
||||
"cutNotes": "iseci trenutnu belešku (ili trenutni izbor) u privremenu memoriju (koristi se za premeštanje beleški)",
|
||||
"pasteNotes": "nalepi belešku/e kao podbelešku u aktivnoj belešci (koja se ili premešta ili klonira u zavisnosti od toga da li je beleška kopirana ili isečena u privremenu memoriju)",
|
||||
"deleteNotes": "obriši belešku / podstablo",
|
||||
"editingNotes": "Izmena beleški",
|
||||
"editNoteTitle": "u ravni drveta će se prebaciti sa ravni drveta na naslov beleške. Ulaz sa naslova beleške će prebaciti fokus na uređivač teksta. <kbd>Ctrl+.</kbd> će se vratiti sa uređivača na ravan drveta.",
|
||||
"createEditLink": "<kbd>Ctrl+K</kbd> - napravi / izmeni spoljašnji link",
|
||||
"createInternalLink": "napravi unutrašnji link",
|
||||
"followLink": "prati link ispod kursora",
|
||||
"insertDateTime": "ubaci trenutan datum i vreme na poziciju kursora",
|
||||
"jumpToTreePane": "idi na ravan stabla i pomeri se do aktivne beleške",
|
||||
"markdownAutoformat": "Autoformatiranje kao u Markdown-u",
|
||||
"headings": "<code>##</code>, <code>###</code>, <code>####</code> itd. praćeno razmakom za naslove",
|
||||
"bulletList": "<code>*</code> ili <code>-</code> praćeno razmakom za listu sa tačkama",
|
||||
"numberedList": "<code>1.</code> ili <code>1)</code> praćeno razmakom za numerisanu listu",
|
||||
"blockQuote": "započnite liniju sa <code>></code> praćeno sa razmakom za blok citat",
|
||||
"troubleshooting": "Rešavanje problema",
|
||||
"reloadFrontend": "ponovo učitaj Trilium frontend",
|
||||
"showDevTools": "prikaži alate za programere",
|
||||
"showSQLConsole": "prikaži SQL konzolu",
|
||||
"other": "Ostalo",
|
||||
"quickSearch": "fokus na unos za brzu pretragu",
|
||||
"inPageSearch": "pretraga unutar stranice"
|
||||
},
|
||||
"import": {
|
||||
"importIntoNote": "Uvezi u belešku",
|
||||
"close": "Zatvori",
|
||||
"chooseImportFile": "Izaberi datoteku za uvoz",
|
||||
"importDescription": "Sadržaj izabranih datoteka će biti uvezen kao podbeleške u",
|
||||
"options": "Opcije",
|
||||
"safeImportTooltip": "Trilium <code>.zip</code> izvozne datoteke mogu da sadrže izvršne skripte koje mogu imati štetno ponašanje. Bezbedan uvoz će deaktivirati automatsko izvršavanje svih uvezenih skripti. Isključite \"Bezbedan uvoz\" samo ako uvezena arhiva treba da sadrži izvršne skripte i ako potpuno verujete sadržaju uvezene datoteke.",
|
||||
"safeImport": "Bezbedan uvoz",
|
||||
"explodeArchivesTooltip": "Ako je ovo označeno onda će Trilium pročitati <code>.zip</code>, <code>.enex</code> i <code>.opml</code> datoteke i napraviti beleške od datoteka unutar tih arhiva. Ako nije označeno, Trilium će same arhive priložiti belešci.",
|
||||
"explodeArchives": "Pročitaj sadržaj <code>.zip</code>, <code>.enex</code> i <code>.opml</code> arhiva.",
|
||||
"shrinkImagesTooltip": "<p>Ako označite ovu opciju, Trilium će pokušati da smanji uvezene slike skaliranjem i optimizacijom što će možda uticati na kvalitet slike. Ako nije označeno, slike će biti uvezene bez promena.</p><p>Ovo se ne primenjuje na <code>.zip</code> uvoze sa metapodacima jer se tada podrazumeva da su te datoteke već optimizovane.</p>",
|
||||
"shrinkImages": "Smanji slike",
|
||||
"textImportedAsText": "Uvezi HTML, Markdown i TXT kao tekstualne beleške ako je nejasno iz metapodataka",
|
||||
"codeImportedAsCode": "Uvezi prepoznate datoteke sa kodom (poput <code>.json</code>) ako beleške sa kodom ako nije jasno iz metapodataka",
|
||||
"replaceUnderscoresWithSpaces": "Zameni podvlake sa razmacima u nazivima uvezenih beleški",
|
||||
"import": "Uvezi",
|
||||
"failed": "Uvoz nije uspeo: {{message}}.",
|
||||
"html_import_tags": {
|
||||
"title": "HTML oznake za uvoz",
|
||||
"description": "Podesite koje HTML oznake trebaju biti sačuvane kada se uvoze beleške. Oznake koje se ne nalaze na listi će biti uklonjene tokom uvoza. Pojedine oznake (poput 'script') se uvek uklanjaju zbog bezbednosti.",
|
||||
"placeholder": "Unesite HTML oznake, po jednu u svaki red",
|
||||
"reset_button": "Vrati na podrazumevanu listu"
|
||||
},
|
||||
"import-status": "Status uvoza",
|
||||
"in-progress": "Uvoz u toku: {{progress}}",
|
||||
"successful": "Uvoz je uspešno završen."
|
||||
},
|
||||
"include_note": {
|
||||
"dialog_title": "Uključi belešku",
|
||||
"close": "Zatvori",
|
||||
"label_note": "Beleška",
|
||||
"placeholder_search": "pretraži belešku po njenom imenu",
|
||||
"box_size_prompt": "Veličina kutije priložene beleške:",
|
||||
"box_size_small": "mala (~ 10 redova)",
|
||||
"box_size_medium": "srednja (~ 30 redova)",
|
||||
"box_size_full": "puna (kutija prikazuje ceo tekst)",
|
||||
"button_include": "Uključi belešku <kbd>enter</kbd>"
|
||||
},
|
||||
"info": {
|
||||
"modalTitle": "Informativna poruka",
|
||||
"closeButton": "Zatvori",
|
||||
"okButton": "U redu"
|
||||
},
|
||||
"jump_to_note": {
|
||||
"search_placeholder": "Pretraži belešku po njenom imenu ili unesi > za komande...",
|
||||
"close": "Zatvori",
|
||||
"search_button": "Pretraga u punom tekstu <kbd>Ctrl+Enter</kbd>"
|
||||
},
|
||||
"markdown_import": {
|
||||
"dialog_title": "Uvoz za Markdown",
|
||||
"close": "Zatvori",
|
||||
"modal_body_text": "Zbog Sandbox-a pretraživača nije moguće direktno učitati privremenu memoriju iz JavaScript-a. Molimo vas da nalepite Markdown za uvoz u tekstualno polje ispod i kliknete na dugme za uvoz",
|
||||
"import_button": "Uvoz Ctrl+Enter",
|
||||
"import_success": "Markdown sadržaj je učitan u dokument."
|
||||
},
|
||||
"move_to": {
|
||||
"dialog_title": "Premesti beleške u ...",
|
||||
"close": "Zatvori",
|
||||
"notes_to_move": "Beleške za premeštanje",
|
||||
"target_parent_note": "Ciljana nadbeleška",
|
||||
"search_placeholder": "potraži belešku po njenom imenu",
|
||||
"move_button": "Pređi na izabranu belešku <kbd>enter</kbd>",
|
||||
"error_no_path": "Nema putanje za premeštanje.",
|
||||
"move_success_message": "Izabrane beleške su premeštene u "
|
||||
},
|
||||
"note_type_chooser": {
|
||||
"change_path_prompt": "Promenite gde će se napraviti nova beleška:",
|
||||
"search_placeholder": "pretraži putanju po njenom imenu (podrazumevano ako je prazno)",
|
||||
"modal_title": "Izaberite tip beleške",
|
||||
"close": "Zatvori",
|
||||
"modal_body": "Izaberite tip beleške / šablon za novu belešku:",
|
||||
"templates": "Šabloni:"
|
||||
},
|
||||
"password_not_set": {
|
||||
"title": "Lozinka nije podešena",
|
||||
"close": "Zatvori",
|
||||
"body1": "Zaštićene beleške su enkriptovane sa korisničkom lozinkom, ali lozinka još uvek nije podešena.",
|
||||
"body2": "Za biste mogli da sačuvate beleške, kliknite <a class=\"open-password-options-button\" href=\"javascript:\">ovde</a> da otvorite dijalog sa Opcijama i podesite svoju lozinku."
|
||||
},
|
||||
"prompt": {
|
||||
"title": "Upit",
|
||||
"close": "Zatvori",
|
||||
"ok": "U redu <kbd>enter</kbd>",
|
||||
"defaultTitle": "Upit"
|
||||
},
|
||||
"protected_session_password": {
|
||||
"modal_title": "Zaštićena sesija",
|
||||
"help_title": "Pomoć za Zaštićene beleške",
|
||||
"close_label": "Zatvori",
|
||||
"form_label": "Da biste nastavili sa traženom akcijom moraćete započeti zaštićenu sesiju tako što ćete uneti lozinku:",
|
||||
"start_button": "Započni zaštićenu sesiju <kbd>enter</kbd>"
|
||||
},
|
||||
"recent_changes": {
|
||||
"title": "Nedavne promene",
|
||||
"erase_notes_button": "Obriši izabrane beleške odmah",
|
||||
"close": "Zatvori",
|
||||
"deleted_notes_message": "Obrisane beleške su uklonjene.",
|
||||
"no_changes_message": "Još uvek nema izmena...",
|
||||
"undelete_link": "poništi brisanje",
|
||||
"confirm_undelete": "Da li želite da poništite brisanje ove beleške i njenih podbeleški?"
|
||||
},
|
||||
"revisions": {
|
||||
"note_revisions": "Revizije beleški",
|
||||
"delete_all_revisions": "Obriši sve revizije ove beleške",
|
||||
"delete_all_button": "Obriši sve revizije",
|
||||
"help_title": "Pomoć za Revizije beleški",
|
||||
"close": "Zatvori",
|
||||
"revision_last_edited": "Ova revizija je poslednji put izmenjena {{date}}",
|
||||
"confirm_delete_all": "Da li želite da obrišete sve revizije ove beleške?",
|
||||
"no_revisions": "Još uvek nema revizija za ovu belešku...",
|
||||
"restore_button": "Vrati",
|
||||
"confirm_restore": "Da li želite da vratite ovu reviziju? Ovo će prepisati trenutan naslov i sadržaj beleške sa ovom revizijom.",
|
||||
"delete_button": "Obriši",
|
||||
"confirm_delete": "Da li želite da obrišete ovu reviziju?",
|
||||
"revisions_deleted": "Revizije beleške su obrisane.",
|
||||
"revision_restored": "Revizija beleške je vraćena.",
|
||||
"revision_deleted": "Revizija beleške je obrisana.",
|
||||
"snapshot_interval": "Interval snimanja revizije beleške: {{seconds}}s.",
|
||||
"maximum_revisions": "Ograničenje broja slika revizije beleške: {{number}}.",
|
||||
"settings": "Podešavanja revizija beleški",
|
||||
"download_button": "Preuzmi",
|
||||
"mime": "MIME: ",
|
||||
"file_size": "Veličina datoteke:",
|
||||
"preview": "Pregled:",
|
||||
"preview_not_available": "Pregled nije dostupan za ovaj tip beleške."
|
||||
},
|
||||
"sort_child_notes": {
|
||||
"sort_children_by": "Sortiranje podbeleški po...",
|
||||
"close": "Zatvori",
|
||||
"sorting_criteria": "Kriterijum za sortiranje",
|
||||
"title": "naslov",
|
||||
"date_created": "datum kreiranja",
|
||||
"date_modified": "datum izmene",
|
||||
"sorting_direction": "Smer sortiranja",
|
||||
"ascending": "uzlazni",
|
||||
"descending": "silazni",
|
||||
"folders": "Fascikle",
|
||||
"sort_folders_at_top": "sortiraj fascikle na vrh",
|
||||
"natural_sort": "Prirodno sortiranje",
|
||||
"sort_with_respect_to_different_character_sorting": "sortiranje sa poštovanjem različitih pravila sortiranja karaktera i kolacija u različitim jezicima ili regionima.",
|
||||
"natural_sort_language": "Jezik za prirodno sortiranje",
|
||||
"the_language_code_for_natural_sort": "Kod jezika za prirodno sortiranje, npr. \"zh-CN\" za Kineski.",
|
||||
"sort": "Sortiraj <kbd>enter</kbd>"
|
||||
},
|
||||
"upload_attachments": {
|
||||
"upload_attachments_to_note": "Otpremite priloge uz belešku",
|
||||
"close": "Zatvori",
|
||||
"choose_files": "Izaberite datoteke",
|
||||
"files_will_be_uploaded": "Datoteke će biti otpremljene kao prilozi u",
|
||||
"options": "Opcije",
|
||||
"shrink_images": "Smanji slike",
|
||||
"upload": "Otpremi",
|
||||
"tooltip": "Ako je označeno, Trilium će pokušati da smanji otpremljene slike skaliranjem i optimizacijom što može uticati na kvalitet slike. Ako nije označeno, slike će biti otpremljene bez izmena."
|
||||
},
|
||||
"attribute_detail": {
|
||||
"attr_detail_title": "Naslov detalja atributa",
|
||||
"close_button_title": "Otkaži izmene i zatvori",
|
||||
"attr_is_owned_by": "Atribut je u vlasništvu",
|
||||
"attr_name_title": "Naziv atributa može biti sastavljen samo od alfanumeričkih znakova, dvotačke i donje crte",
|
||||
"name": "Naziv",
|
||||
"value": "Vrednost",
|
||||
"target_note_title": "Relacija je imenovana veza između izvorne beleške i ciljne beleške.",
|
||||
"target_note": "Ciljna beleška",
|
||||
"promoted_title": "Promovisani atribut je istaknut na belešci.",
|
||||
"promoted": "Promovisan",
|
||||
"promoted_alias_title": "Naziv koji će biti prikazan u korisničkom interfejsu promovisanih atributa.",
|
||||
"promoted_alias": "Pseudonim",
|
||||
"multiplicity_title": "Multiplicitet definiše koliko atributa sa istim nazivom se može napraviti - najviše 1 ili više od 1.",
|
||||
"multiplicity": "Multiplicitet",
|
||||
"single_value": "Jednostruka vrednost",
|
||||
"multi_value": "Višestruka vrednost",
|
||||
"label_type_title": "Tip oznake će pomoći Triliumu da izabere odgovarajući interfejs za unos vrednosti oznake.",
|
||||
"label_type": "Tip",
|
||||
"text": "Tekst",
|
||||
"number": "Broj",
|
||||
"boolean": "Boolean",
|
||||
"date": "Datum",
|
||||
"date_time": "Datum i vreme",
|
||||
"time": "Vreme",
|
||||
"url": "URL",
|
||||
"precision_title": "Broj cifara posle zareza treba biti dostupan u interfejsu za postavljanje vrednosti.",
|
||||
"precision": "Preciznost",
|
||||
"digits": "cifre",
|
||||
"inverse_relation_title": "Opciono podešavanje za definisanje kojoj relaciji je ova suprotna. Primer: Otac - Sin su inverzne relacije jedna drugoj.",
|
||||
"inverse_relation": "Inverzna relacija",
|
||||
"inheritable_title": "Atributi koji mogu da se nasleđuju će biti nasleđeni od strane svih potomaka unutar ovog stabla.",
|
||||
"inheritable": "Nasledno",
|
||||
"save_and_close": "Sačuvaj i zatvori <kbd>Ctrl+Enter</kbd>",
|
||||
"delete": "Obriši",
|
||||
"related_notes_title": "Druge beleške sa ovom oznakom",
|
||||
"more_notes": "Još beleški",
|
||||
"label": "Detalji oznake",
|
||||
"label_definition": "Detalji definicije oznake",
|
||||
"relation": "Detalji relacije",
|
||||
"relation_definition": "Detalji definicije relacije",
|
||||
"disable_versioning": "onemogućava auto-verzionisanje. Korisno za npr. velike, ali nebitne beleške - poput velikih JS biblioteka koje se koriste za skripte",
|
||||
"calendar_root": "obeležava belešku koju treba koristiti kao osnova za dnevne beleške. Samo jedna beleška treba da bude označena kao takva.",
|
||||
"archived": "beleške sa ovom oznakom neće biti podrazumevano vidljive u rezultatima pretrage (kao ni u dijalozima za Idi na, Dodaj link, itd.).",
|
||||
"exclude_from_export": "beleške (sa svojim podstablom) neće biti uključene u bilo koji izvoz beleški",
|
||||
"run": "definiše u kojim događajima se skripta pokreće. Moguće vrednosti su:\n<ul>\n<li>frontendStartup - kada se pokrene Trilium frontend (ili se osveži), ali ne na mobilnom uređaju.</li>\n<li>mobileStartup - kada se pokrene Trilium frontend (ili se osveži), na mobilnom uređaju..</li>\n<li>backendStartup - kada se Trilium backend pokrene</li>\n<li>hourly - pokreće se svaki sat. Može se koristiti dodatna oznaka <code>runAtHour</code> da se označi u kom satu.</li>\n<li>daily - pokreće se jednom dnevno</li>\n</ul>",
|
||||
"run_on_instance": "Definiše u kojoj instanci Trilium-a ovo treba da se pokreće. Podrazumevano podešavanje je na svim instancama.",
|
||||
"run_at_hour": "U kom satu ovo treba da se pokreće. Treba se koristiti zajedno sa <code>#run=hourly</code>. Može biti definisano više puta za više pokretanja u toku dana.",
|
||||
"disable_inclusion": "skripte sa ovom oznakom neće biti uključene u izvršavanju nadskripte.",
|
||||
"sorted": "čuva podbeleške sortirane alfabetski po naslovu",
|
||||
"sort_direction": "Uzlazno (podrazumevano) ili silazno",
|
||||
"sort_folders_first": "Fascikle (beleške sa podbeleškama) treba da budu sortirane na vrhu",
|
||||
"top": "zadrži datu belešku na vrhu njene nadbeleške (primenjuje se samo na sortiranim nadbeleškama)",
|
||||
"hide_promoted_attributes": "Sakrij promovisane atribute na ovoj belešci",
|
||||
"read_only": "uređivač je u režimu samo za čitanje. Radi samo za tekst i beleške sa kodom.",
|
||||
"auto_read_only_disabled": "beleške sa tekstom/kodom se mogu automatski podesiti u režim za čitanje kada su prevelike. Ovo ponašanje možete onemogućiti pojedinačno za belešku dodavanjem ove oznake na belešku",
|
||||
"app_css": "označava CSS beleške koje nisu učitane u Trilium aplikaciju i zbog toga se mogu koristiti za menjanje izgleda Triliuma.",
|
||||
"app_theme": "označava CSS beleške koje su pune Trilium teme i stoga su dostupne u Trilium podešavanjima.",
|
||||
"app_theme_base": "podesite na „sledeće“, „sledeće-svetlo“ ili „sledeće-tamno“ da biste koristili odgovarajuću TriliumNext temu (automatsku, svetlu ili tamnu) kao osnovu za prilagođenu temu, umesto podrazumevane teme.",
|
||||
"css_class": "vrednost ove oznake se zatim dodaje kao CSS klasa čvoru koji predstavlja datu belešku u stablu. Ovo može biti korisno za napredno temiranje. Može se koristiti u šablonima beleški.",
|
||||
"workspace": "označava ovu belešku kao radni prostor što omogućava lako podizanje",
|
||||
"workspace_icon_class": "definiše CSS klasu ikone okvira koja će se koristiti u kartici kada se podigne na ovoj belešci",
|
||||
"workspace_tab_background_color": "CSS boja korišćena u kartici beleške kada se prebaci na ovu belešku",
|
||||
"workspace_calendar_root": "Definiše koren kalendara za svaki radni prostor",
|
||||
"workspace_template": "Ova beleška će se pojaviti u izboru dostupnih šablona prilikom kreiranja nove beleške, ali samo kada se podigne u radni prostor koji sadrži ovaj šablon",
|
||||
"search_home": "nove beleške o pretrazi biće kreirane kao podređeni delovi ove beleške",
|
||||
"workspace_search_home": "nove beleške o pretrazi biće kreirane kao podređeni delovi ove beleške kada se podignu na nekog pretka ove beleške iz radnog prostora",
|
||||
"inbox": "podrazumevana lokacija u prijemnom sandučetu za nove beleške - kada kreirate belešku pomoću dugmeta „nova beleška“ u bočnoj traci, beleške će biti kreirane kao podbeleške u belešci označenoj sa oznakom <code>#inbox</code>.",
|
||||
"workspace_inbox": "podrazumevana lokacija prijemnog sandučeta za nove beleške kada se prebace na nekog pretka ove beleške iz radnog prostora",
|
||||
"sql_console_home": "podrazmevana lokacija beleški SQL konzole",
|
||||
"bookmark_folder": "beleška sa ovom oznakom će se pojaviti u obeleživačima kao fascikla (omogućavajući pristup njenim podređenim fasciklama)",
|
||||
"share_hidden_from_tree": "ova beleška je skrivena u levom navigacionom stablu, ali je i dalje dostupna preko svoje URL adrese",
|
||||
"share_external_link": "beleška će služiti kao veza ka eksternoj veb stranici u stablu deljenja",
|
||||
"share_alias": "definišite alias pomoću kog će beleška biti dostupna na https://your_trilium_host/share/[your_alias]",
|
||||
"share_omit_default_css": "CSS kod podrazumevane stranice za deljenje će biti izostavljen. Koristite ga kada pravite opsežne promene stila.",
|
||||
"share_root": "obeležava belešku koja se prikazuje na /share korenu.",
|
||||
"share_description": "definišite tekst koji će se dodati HTML meta oznaci za opis",
|
||||
"share_raw": "beleška će biti prikazana u svom sirovom (raw) formatu, bez HTML omotača",
|
||||
"share_disallow_robot_indexing": "zabraniće robotsko indeksiranje ove beleške putem zaglavlja <code>X-Robots-Tag: noindex</code>",
|
||||
"share_credentials": "potrebni su kredencijali za pristup ovoj deljenoj belešci. Očekuje se da vrednost bude u formatu „korisničko ime:lozinka“. Ne zaboravite da ovo označite kao nasledno da bi se primenilo na podbeleške/slike.",
|
||||
"share_index": "beleška sa ovom oznakom će izlistati sve korene deljenih beleški",
|
||||
"display_relations": "imena relacija razdvojenih zarezima koja treba da budu prikazana. Sva ostala će biti skrivena.",
|
||||
"hide_relations": "imena relacija razdvojenih zarezima koja treba da budu skrivena. Sva ostala će biti prikazana.",
|
||||
"title_template": "podrazumevani naslov beleški kreiranih kao deca ove beleške. Vrednost se procenjuje kao JavaScript string \n i stoga se može obogatiti dinamičkim sadržajem putem ubrizganih promenljivih <code>now</code> and <code>parentNote</code>. Primeri:\n \n <ul>\n <li><code>${parentNote.getLabelValue('authorName')}'s literary works</code></li>\n <li><code>Log for ${now.format('YYYY-MM-DD HH:mm:ss')}</code></li>\n </ul>\n \n Pogledati <a href=\"https://triliumnext.github.io/Docs/Wiki/default-note-title.html\">wiki sa detaljima</a>, API dokumentacija za <a href=\"https://zadam.github.io/trilium/backend_api/Note.html\">parentNote</a> i <a href=\"https://day.js.org/docs/en/display/format\">now</a> za detalje.",
|
||||
"template": "Ova beleška će biti prikazana u izboru dostupnih šablona prilikom pravljenja nove beleške",
|
||||
"toc": "<code>#toc</code> ili <code>#toc=show</code> će pristiliti Sadržaj (Table of Contents) da bude prikazan, <code>#toc=hide</code> prisiliti njegovo sakrivanje. Ako oznaka ne postoji, ponašanje će biti usklađeno sa globalnim podešavanjem",
|
||||
"color": "definiše boju beleške u stablu beleški, linkovima itd. Koristite bilo koju važeću CSS vrednost boje kao što je „crvena“ ili #a13d5f",
|
||||
"keyboard_shortcut": "Definiše prečicu na tastaturi koja će odmah preći na ovu belešku. Primer: „ctrl+alt+e“. Potrebno je ponovno učitavanje frontenda da bi promena stupila na snagu.",
|
||||
"keep_current_hoisting": "Otvaranje ove veze neće promeniti podizanje čak i ako beleška nije prikazana u trenutno podignutom podstablu.",
|
||||
"execute_button": "Naslov dugmeta koje će izvršiti trenutnu belešku sa kodom",
|
||||
"execute_description": "Duži opis trenutne beleške sa kodom prikazan je zajedno sa dugmetom za izvršavanje",
|
||||
"exclude_from_note_map": "Beleške sa ovom oznakom biće skrivene sa mape beleški",
|
||||
"new_notes_on_top": "Nove beleške će biti napravljene na vrhu matične beleške, a ne na dnu.",
|
||||
"hide_highlight_widget": "Sakrij vidžet sa listom istaknutih",
|
||||
"run_on_note_creation": "izvršava se kada se beleška napravi na serverskoj strani. Koristite ovu relaciju ako želite da pokrenete skriptu za sve beleške napravljene u okviru određenog podstabla. U tom slučaju, kreirajte je na korenu beleške podstabla i učinite je naslednom. Nova beleška napravljena unutar podstabla (bilo koje dubine) pokrenuće skriptu.",
|
||||
"run_on_child_note_creation": "izvršava se kada se napravi nova beleška ispod beleške gde je ova relacija definisana",
|
||||
"run_on_note_title_change": "izvršava se kada se promeni naslov beleške (uključuje i pravljenje beleške)",
|
||||
"run_on_note_content_change": "izvršava se kada se promeni sadržaj beleške (uključuje i pravljenje beleške).",
|
||||
"run_on_note_change": "izvršava se kada se promeni beleška (uključuje i pravljenje beleške). Ne uključuje promene sadržaja",
|
||||
"icon_class": "vrednost ove oznake se dodaje kao CSS klasa ikoni na stablu što može pomoći u vizuelnom razlikovanju beleški u stablu. Primer može biti bx bx-home - ikone su preuzete iz boxicons. Može se koristiti u šablonima beleški.",
|
||||
"page_size": "broj stavki po stranici u listi beleški",
|
||||
"custom_request_handler": "pogledajte <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Prilagođeni obrađivač zahteva</a>",
|
||||
"custom_resource_provider": "pogledajte <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Prilagođeni obrađivač zahteva</a>",
|
||||
"widget": "označava ovu belešku kao prilagođeni vidžet koji će biti dodat u stablo komponenti Trilijuma",
|
||||
"run_on_note_deletion": "izvršava se kada se beleška briše",
|
||||
"run_on_branch_creation": "izvršava se kada se pravi grana. Grana je veza između matične i podređene beleške i pravi se npr. prilikom kloniranja ili premeštanja beleške.",
|
||||
"run_on_branch_change": "izvršava se kada se grana ažurira.",
|
||||
"run_on_branch_deletion": "izvršava se kada se grana briše. Grana je veza između nadređene beleške i podređene beleške i briše se npr. prilikom premeštanja beleške (stara grana/veza se briše).",
|
||||
"run_on_attribute_creation": "izvršava se kada se pravi novi atribut za belešku koji definiše ovu relaciju",
|
||||
"run_on_attribute_change": " izvršava se kada se promeni atribut beleške koja definiše ovu relaciju. Ovo se pokreće i kada se atribut obriše",
|
||||
"relation_template": "atributi beleške će biti nasleđeni čak i bez odnosa roditelj-dete, sadržaj i podstablo beleške će biti dodati instanci beleške ako je prazna. Pogledajte dokumentaciju za detalje.",
|
||||
"inherit": "Atributi beleške će biti nasleđeni čak i bez odnosa roditelj-dete. Pogledajte relaciju šablona za sličan koncept. Pogledajte nasleđivanje atributa u dokumentaciji.",
|
||||
"render_note": "Beleške tipa „render HTML note“ će biti prikazane korišćenjem beleške za kod (HTML ili skripte) i potrebno je pomoću ove relacije ukazati na to koja beleška treba da se prikaže",
|
||||
"widget_relation": "meta ove relacije će biti izvršena i prikazana kao vidžet u bočnoj traci",
|
||||
"share_css": "CSS napomena koja će biti ubrizgana na stranicu za deljenje. CSS napomena mora biti i u deljenom podstablu. Razmotrite i korišćenje „share_hidden_from_tree“ i „share_omit_default_css“.",
|
||||
"share_js": "JavaScript beleška koja će biti ubrizgana na stranicu za deljenje. JS beleška takođe mora biti u deljenom podstablu. Razmislite o korišćenju „share_hidden_from_tree“.",
|
||||
"share_template": "Ugrađena JavaScript beleška koja će se koristiti kao šablon za prikazivanje deljene beleške. U slučaju neuspeha vraća se na podrazumevani šablon. Razmislite o korišćenju „share_hidden_from_tree“.",
|
||||
"share_favicon": "Favicon beleška koju treba postaviti na deljenu stranicu. Obično je potrebno da je podesite da deli koren i učinite je naslednom. Favicon beleška takođe mora biti u deljenom podstablu. Razmislite o korišćenju „share_hidden_from_tree“.",
|
||||
"is_owned_by_note": "je u vlasništvu beleške",
|
||||
"other_notes_with_name": "Ostale beleške sa {{attributeType}} nazivom „{{attributeName}}“",
|
||||
"and_more": "... i još {{count}}.",
|
||||
"print_landscape": "Prilikom izvoza u PDF, menja orijentaciju stranice u pejzažnu umesto uspravne.",
|
||||
"print_page_size": "Prilikom izvoza u PDF, menja veličinu stranice. Podržane vrednosti: <code>A0</code>, <code>A1</code>, <code>A2</code>, <code>A3</code>, <code>A4</code>, <code>A5</code>, <code>A6</code>, <code>Legal</code>, <code>Letter</code>, <code>Tabloid</code>, <code>Ledger</code>.",
|
||||
"color_type": "Boja"
|
||||
},
|
||||
"ai_llm": {
|
||||
"n_notes_queued_0": "{{ count }} beleška stavljena u red za indeksiranje",
|
||||
"n_notes_queued_1": "{{ count }} beleški stavljeno u red za indeksiranje",
|
||||
"n_notes_queued_2": "{{ count }} beleški stavljeno u red za indeksiranje",
|
||||
"notes_indexed_0": "{{ count }} beleška je indeksirana",
|
||||
"notes_indexed_1": "{{ count }} beleški je indeksirano",
|
||||
"notes_indexed_2": "{{ count }} beleški je indeksirano"
|
||||
},
|
||||
"attribute_editor": {
|
||||
"help_text_body1": "Da biste dodali oznaku, samo unesite npr. <code>#rock</code> ili ako želite da dodate i vrednost, onda npr. <code>#year = 2020</code>",
|
||||
"help_text_body2": "Za relaciju, unesite <code>~author = @</code> što bi trebalo da otvori automatsko dovršavanje gde možete potražiti željenu belešku.",
|
||||
"help_text_body3": "Alternativno, možete dodati oznaku i relaciju pomoću dugmeta <code>+</code> sa desne strane.",
|
||||
"save_attributes": "Sačuvaj atribute <enter>",
|
||||
"add_a_new_attribute": "Dodajte novi atribut",
|
||||
"add_new_label": "Dodajte novu oznaku <kbd data-command=\"addNewLabel\"></kbd>",
|
||||
"add_new_relation": "Dodajte novu relaciju <kbd data-command=\"addNewRelation\"></kbd>",
|
||||
"add_new_label_definition": "Dodajte novu definiciju oznake",
|
||||
"add_new_relation_definition": "Dodajte novu definiciju relacije",
|
||||
"placeholder": "Ovde unesite oznake i relacije"
|
||||
},
|
||||
"abstract_bulk_action": {
|
||||
"remove_this_search_action": "Ukloni ovu radnju pretrage"
|
||||
},
|
||||
"execute_script": {
|
||||
"execute_script": "Izvrši skriptu",
|
||||
"help_text": "Možete izvršiti jednostavne skripte na podudarnim beleškama.",
|
||||
"example_1": "Na primer, da biste dodali string u naslov beleške, koristite ovu malu skriptu:",
|
||||
"example_2": "Složeniji primer bi bio brisanje svih atributa podudarnih beleški:"
|
||||
},
|
||||
"add_label": {
|
||||
"add_label": "Dodaj oznaku",
|
||||
"label_name_placeholder": "ime oznake",
|
||||
"label_name_title": "Alfanumerički znakovi, donja crta i dvotačka su dozvoljeni znakovi.",
|
||||
"to_value": "za vrednost",
|
||||
"new_value_placeholder": "nova vrednost",
|
||||
"help_text": "Na svim podudarnim beleškama:",
|
||||
"help_text_item1": "dodajte datu oznaku ako beleška još uvek nema jednu",
|
||||
"help_text_item2": "ili izmenite vrednost postojeće oznake",
|
||||
"help_text_note": "Takođe možete pozvati ovu metodu bez vrednosti, u tom slučaju će oznaka biti dodeljena belešci bez vrednosti."
|
||||
},
|
||||
"delete_label": {
|
||||
"delete_label": "Obriši oznaku",
|
||||
"label_name_placeholder": "ime oznake",
|
||||
"label_name_title": "Alfanumerički znakovi, donja crtica i dvotačka su dozvoljeni znakovi."
|
||||
},
|
||||
"rename_label": {
|
||||
"rename_label": "Preimenuj oznaku",
|
||||
"rename_label_from": "Preimenuj oznaku iz",
|
||||
"old_name_placeholder": "stari naziv",
|
||||
"to": "U",
|
||||
"new_name_placeholder": "novi naziv",
|
||||
"name_title": "Alfanumerički znakovi, donja crtica i dvotačka su dozvoljeni znakovi."
|
||||
},
|
||||
"update_label_value": {
|
||||
"update_label_value": "Ažuriraj vrednost oznake",
|
||||
"label_name_placeholder": "ime oznake",
|
||||
"label_name_title": "Alfanumerički znakovi, donja crtica i dvotačka su dozvoljeni znakovi.",
|
||||
"to_value": "u vrednost",
|
||||
"new_value_placeholder": "nova vrednost",
|
||||
"help_text": "Na svim podudarnim beleškama, promenite vrednost postojeće oznake.",
|
||||
"help_text_note": "Takođe možete pozvati ovu metodu bez vrednosti, u tom slučaju će oznaka biti dodeljena belešci bez vrednosti."
|
||||
},
|
||||
"delete_note": {
|
||||
"delete_note": "Obriši belešku",
|
||||
"delete_matched_notes": "Obriši podudarne beleške",
|
||||
"delete_matched_notes_description": "Ovo će obrisati podudarne beleške.",
|
||||
"undelete_notes_instruction": "Nakon brisanja, moguće ga je poništiti iz dijaloga Nedavne izmene."
|
||||
}
|
||||
}
|
||||
71
apps/client/src/translations/tr/translation.json
Normal file
71
apps/client/src/translations/tr/translation.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"about": {
|
||||
"close": "Kapat",
|
||||
"homepage": "Giriş sayfası:",
|
||||
"app_version": "Uygulama versiyonu:",
|
||||
"db_version": "Veritabanı versiyonu:"
|
||||
},
|
||||
"add_link": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"branch_prefix": {
|
||||
"close": "Kapat",
|
||||
"save": "Kaydet"
|
||||
},
|
||||
"bulk_actions": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"clone_to": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"confirm": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"recent_changes": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"delete_notes": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"export": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"help": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"include_note": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"import": {
|
||||
"close": "Kapat",
|
||||
"chooseImportFile": "İçe aktarım dosyası",
|
||||
"importDescription": "Seçilen dosya(lar) alt not olarak içe aktarılacaktır"
|
||||
},
|
||||
"info": {
|
||||
"closeButton": "Kapat"
|
||||
},
|
||||
"jump_to_note": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"markdown_import": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"move_to": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"note_type_chooser": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"password_not_set": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"prompt": {
|
||||
"close": "Kapat"
|
||||
},
|
||||
"protected_session_password": {
|
||||
"close_label": "Kapat"
|
||||
},
|
||||
"revisions": {
|
||||
"close": "Kapat"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
10
apps/client/src/types.d.ts
vendored
10
apps/client/src/types.d.ts
vendored
@@ -97,16 +97,6 @@ declare global {
|
||||
setNote(noteId: string);
|
||||
}
|
||||
|
||||
interface JQueryStatic {
|
||||
hotkeys: {
|
||||
options: {
|
||||
filterInputAcceptingElements: boolean;
|
||||
filterContentEditable: boolean;
|
||||
filterTextInputs: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var logError: (message: string, e?: Error | string) => void;
|
||||
var logInfo: (message: string) => void;
|
||||
var glob: CustomGlobals;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ActionKeyboardShortcut } from "@triliumnext/commons";
|
||||
import type { CommandNames } from "../../components/app_context.js";
|
||||
import keyboardActionsService, { type Action } from "../../services/keyboard_actions.js";
|
||||
import keyboardActionsService from "../../services/keyboard_actions.js";
|
||||
import AbstractButtonWidget, { type AbstractButtonWidgetSettings } from "./abstract_button.js";
|
||||
import type { ButtonNoteIdProvider } from "./button_from_note.js";
|
||||
|
||||
let actions: Action[];
|
||||
let actions: ActionKeyboardShortcut[];
|
||||
|
||||
keyboardActionsService.getActions().then((as) => (actions = as));
|
||||
|
||||
@@ -49,7 +50,7 @@ export default class CommandButtonWidget extends AbstractButtonWidget<CommandBut
|
||||
|
||||
const action = actions.find((act) => act.actionName === this._command);
|
||||
|
||||
if (action && action.effectiveShortcuts.length > 0) {
|
||||
if (action?.effectiveShortcuts && action.effectiveShortcuts.length > 0) {
|
||||
return `${title} (${action.effectiveShortcuts.join(", ")})`;
|
||||
} else {
|
||||
return title;
|
||||
|
||||
@@ -186,7 +186,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
|
||||
|
||||
this.$convertNoteIntoAttachmentButton.toggle(note.isEligibleForConversionToAttachment());
|
||||
|
||||
this.toggleDisabled(this.$findInTextButton, ["text", "code", "book", "mindMap"].includes(note.type));
|
||||
this.toggleDisabled(this.$findInTextButton, ["text", "code", "book", "mindMap", "doc"].includes(note.type));
|
||||
|
||||
this.toggleDisabled(this.$showAttachmentsButton, !isInOptions);
|
||||
this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type));
|
||||
|
||||
@@ -268,7 +268,7 @@ export default class RibbonContainer extends NoteContextAwareWidget {
|
||||
const action = actions.find((act) => act.actionName === toggleCommandName);
|
||||
const title = $(this).attr("data-title");
|
||||
|
||||
if (action && action.effectiveShortcuts.length > 0) {
|
||||
if (action?.effectiveShortcuts && action.effectiveShortcuts.length > 0) {
|
||||
return `${title} (${action.effectiveShortcuts.join(", ")})`;
|
||||
} else {
|
||||
return title ?? "";
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import { formatDateTime } from "../../utils/formatters.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import openService from "../../services/open.js";
|
||||
import server from "../../services/server.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
|
||||
interface AppInfo {
|
||||
appVersion: string;
|
||||
dbVersion: number;
|
||||
syncVersion: number;
|
||||
buildDate: string;
|
||||
buildRevision: string;
|
||||
dataDirectory: string;
|
||||
}
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="about-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">${t("about.title")}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="${t("about.close")}"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table class="table table-borderless">
|
||||
<tr>
|
||||
<th>${t("about.homepage")}</th>
|
||||
<td><a class="tn-link" href="https://github.com/TriliumNext/Trilium" class="external">https://github.com/TriliumNext/Trilium</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${t("about.app_version")}</th>
|
||||
<td class="app-version"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${t("about.db_version")}</th>
|
||||
<td class="db-version"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${t("about.sync_version")}</th>
|
||||
<td class="sync-version"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${t("about.build_date")}</th>
|
||||
<td class="build-date"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${t("about.build_revision")}</th>
|
||||
<td><a class="tn-link build-revision external" href="" target="_blank"></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>${t("about.data_directory")}</th>
|
||||
<td class="data-directory"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.about-dialog a {
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
export default class AboutDialog extends BasicWidget {
|
||||
private $appVersion!: JQuery<HTMLElement>;
|
||||
private $dbVersion!: JQuery<HTMLElement>;
|
||||
private $syncVersion!: JQuery<HTMLElement>;
|
||||
private $buildDate!: JQuery<HTMLElement>;
|
||||
private $buildRevision!: JQuery<HTMLElement>;
|
||||
private $dataDirectory!: JQuery<HTMLElement>;
|
||||
|
||||
doRender(): void {
|
||||
this.$widget = $(TPL);
|
||||
this.$appVersion = this.$widget.find(".app-version");
|
||||
this.$dbVersion = this.$widget.find(".db-version");
|
||||
this.$syncVersion = this.$widget.find(".sync-version");
|
||||
this.$buildDate = this.$widget.find(".build-date");
|
||||
this.$buildRevision = this.$widget.find(".build-revision");
|
||||
this.$dataDirectory = this.$widget.find(".data-directory");
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
const appInfo = await server.get<AppInfo>("app-info");
|
||||
|
||||
this.$appVersion.text(appInfo.appVersion);
|
||||
this.$dbVersion.text(appInfo.dbVersion.toString());
|
||||
this.$syncVersion.text(appInfo.syncVersion.toString());
|
||||
this.$buildDate.text(formatDateTime(appInfo.buildDate));
|
||||
this.$buildRevision.text(appInfo.buildRevision);
|
||||
this.$buildRevision.attr("href", `https://github.com/TriliumNext/Trilium/commit/${appInfo.buildRevision}`);
|
||||
if (utils.isElectron()) {
|
||||
this.$dataDirectory.html(
|
||||
$("<a></a>", {
|
||||
href: "#",
|
||||
class: "tn-link",
|
||||
text: appInfo.dataDirectory
|
||||
}).prop("outerHTML")
|
||||
);
|
||||
this.$dataDirectory.find("a").on("click", (event: JQuery.ClickEvent) => {
|
||||
event.preventDefault();
|
||||
openService.openDirectory(appInfo.dataDirectory);
|
||||
});
|
||||
} else {
|
||||
this.$dataDirectory.text(appInfo.dataDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
async openAboutDialogEvent() {
|
||||
await this.refresh();
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
}
|
||||
91
apps/client/src/widgets/dialogs/about.tsx
Normal file
91
apps/client/src/widgets/dialogs/about.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { openDialog } from "../../services/dialog.js";
|
||||
import ReactBasicWidget from "../react/ReactBasicWidget.js";
|
||||
import Modal from "../react/Modal.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import { formatDateTime } from "../../utils/formatters.js";
|
||||
import server from "../../services/server.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import openService from "../../services/open.js";
|
||||
import { useState } from "preact/hooks";
|
||||
import type { CSSProperties } from "preact/compat";
|
||||
import type { AppInfo } from "@triliumnext/commons";
|
||||
|
||||
function AboutDialogComponent() {
|
||||
let [appInfo, setAppInfo] = useState<AppInfo | null>(null);
|
||||
|
||||
async function onShown() {
|
||||
const appInfo = await server.get<AppInfo>("app-info");
|
||||
setAppInfo(appInfo);
|
||||
}
|
||||
|
||||
const forceWordBreak: CSSProperties = { wordBreak: "break-all" };
|
||||
|
||||
return (
|
||||
<Modal className="about-dialog" size="lg" title={t("about.title")} onShown={onShown}>
|
||||
{(appInfo !== null) ? (
|
||||
<table className="table table-borderless">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{t("about.homepage")}</th>
|
||||
<td><a className="tn-link external" href="https://github.com/TriliumNext/Trilium" style={forceWordBreak}>https://github.com/TriliumNext/Trilium</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("about.app_version")}</th>
|
||||
<td className="app-version">{appInfo.appVersion}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("about.db_version")}</th>
|
||||
<td className="db-version">{appInfo.dbVersion}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("about.sync_version")}</th>
|
||||
<td className="sync-version">{appInfo.syncVersion}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("about.build_date")}</th>
|
||||
<td className="build-date">{formatDateTime(appInfo.buildDate)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("about.build_revision")}</th>
|
||||
<td>
|
||||
<a className="tn-link build-revision external" href={`https://github.com/TriliumNext/Trilium/commit/${appInfo.buildRevision}`} target="_blank" style={forceWordBreak}>{appInfo.buildRevision}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{t("about.data_directory")}</th>
|
||||
<td className="data-directory">
|
||||
<DirectoryLink directory={appInfo.dataDirectory} style={forceWordBreak} />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div className="loading-spinner"></div>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function DirectoryLink({ directory, style }: { directory: string, style?: CSSProperties }) {
|
||||
if (utils.isElectron()) {
|
||||
const onClick = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
openService.openDirectory(directory);
|
||||
};
|
||||
|
||||
return <a className="tn-link" href="#" onClick={onClick} style={style}></a>
|
||||
} else {
|
||||
return <span style={style}>{directory}</span>;
|
||||
}
|
||||
}
|
||||
|
||||
export default class AboutDialog extends ReactBasicWidget {
|
||||
|
||||
get component() {
|
||||
return <AboutDialogComponent />;
|
||||
}
|
||||
|
||||
async openAboutDialogEvent() {
|
||||
openDialog(this.$widget);
|
||||
}
|
||||
}
|
||||
@@ -187,7 +187,7 @@ export default class JumpToNoteDialog extends BasicWidget {
|
||||
}
|
||||
}
|
||||
|
||||
showInFullText(e: JQuery.TriggeredEvent) {
|
||||
showInFullText(e: JQuery.TriggeredEvent | KeyboardEvent) {
|
||||
// stop from propagating upwards (dangerous, especially with ctrl+enter executable javascript notes)
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
@@ -97,6 +97,7 @@ const TPL = /*html*/`
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
const SUPPORTED_NOTE_TYPES = ["text", "code", "render", "mindMap", "doc"];
|
||||
export default class FindWidget extends NoteContextAwareWidget {
|
||||
|
||||
private searchTerm: string | null;
|
||||
@@ -188,7 +189,7 @@ export default class FindWidget extends NoteContextAwareWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!["text", "code", "render", "mindMap"].includes(this.note?.type ?? "")) {
|
||||
if (!SUPPORTED_NOTE_TYPES.includes(this.note?.type ?? "")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -251,6 +252,7 @@ export default class FindWidget extends NoteContextAwareWidget {
|
||||
const readOnly = await this.noteContext?.isReadOnly();
|
||||
return readOnly ? this.htmlHandler : this.textHandler;
|
||||
case "mindMap":
|
||||
case "doc":
|
||||
return this.htmlHandler;
|
||||
default:
|
||||
console.warn("FindWidget: Unsupported note type for find widget", this.note?.type);
|
||||
@@ -354,7 +356,7 @@ export default class FindWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled() && ["text", "code", "render", "mindMap"].includes(this.note?.type ?? "");
|
||||
return super.isEnabled() && SUPPORTED_NOTE_TYPES.includes(this.note?.type ?? "");
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
|
||||
@@ -48,6 +48,9 @@ export async function checkSessionExists(noteId: string): Promise<boolean> {
|
||||
* @param onContentUpdate - Callback for content updates
|
||||
* @param onThinkingUpdate - Callback for thinking updates
|
||||
* @param onToolExecution - Callback for tool execution
|
||||
* @param onProgressUpdate - Callback for progress updates
|
||||
* @param onUserInteraction - Callback for user interaction requests
|
||||
* @param onErrorRecovery - Callback for error recovery options
|
||||
* @param onComplete - Callback for completion
|
||||
* @param onError - Callback for errors
|
||||
*/
|
||||
@@ -57,6 +60,9 @@ export async function setupStreamingResponse(
|
||||
onContentUpdate: (content: string, isDone?: boolean) => void,
|
||||
onThinkingUpdate: (thinking: string) => void,
|
||||
onToolExecution: (toolData: any) => void,
|
||||
onProgressUpdate: (progressData: any) => void,
|
||||
onUserInteraction: (interactionData: any) => Promise<any>,
|
||||
onErrorRecovery: (errorData: any) => Promise<any>,
|
||||
onComplete: () => void,
|
||||
onError: (error: Error) => void
|
||||
): Promise<void> {
|
||||
@@ -66,9 +72,14 @@ export async function setupStreamingResponse(
|
||||
let timeoutId: number | null = null;
|
||||
let initialTimeoutId: number | null = null;
|
||||
let cleanupTimeoutId: number | null = null;
|
||||
let heartbeatTimeoutId: number | null = null;
|
||||
let receivedAnyMessage = false;
|
||||
let eventListener: ((event: Event) => void) | null = null;
|
||||
let lastMessageTimestamp = 0;
|
||||
|
||||
// Configuration for timeouts
|
||||
const HEARTBEAT_TIMEOUT_MS = 30000; // 30 seconds between messages
|
||||
const MAX_IDLE_TIME_MS = 60000; // 60 seconds max idle time
|
||||
|
||||
// Create a unique identifier for this response process
|
||||
const responseId = `llm-stream-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
|
||||
@@ -101,12 +112,43 @@ export async function setupStreamingResponse(
|
||||
}
|
||||
})();
|
||||
|
||||
// Function to reset heartbeat timeout
|
||||
const resetHeartbeatTimeout = () => {
|
||||
if (heartbeatTimeoutId) {
|
||||
window.clearTimeout(heartbeatTimeoutId);
|
||||
}
|
||||
|
||||
heartbeatTimeoutId = window.setTimeout(() => {
|
||||
const idleTime = Date.now() - lastMessageTimestamp;
|
||||
console.warn(`[${responseId}] No message received for ${idleTime}ms`);
|
||||
|
||||
if (idleTime > MAX_IDLE_TIME_MS) {
|
||||
console.error(`[${responseId}] Connection appears to be stalled (idle for ${idleTime}ms)`);
|
||||
performCleanup();
|
||||
reject(new Error('Connection lost: The AI service stopped responding. Please try again.'));
|
||||
} else {
|
||||
// Send a warning but continue waiting
|
||||
console.warn(`[${responseId}] Connection may be slow, continuing to wait...`);
|
||||
resetHeartbeatTimeout(); // Reset for another check
|
||||
}
|
||||
}, HEARTBEAT_TIMEOUT_MS);
|
||||
};
|
||||
|
||||
// Function to safely perform cleanup
|
||||
const performCleanup = () => {
|
||||
// Clear all timeouts
|
||||
if (cleanupTimeoutId) {
|
||||
window.clearTimeout(cleanupTimeoutId);
|
||||
cleanupTimeoutId = null;
|
||||
}
|
||||
if (heartbeatTimeoutId) {
|
||||
window.clearTimeout(heartbeatTimeoutId);
|
||||
heartbeatTimeoutId = null;
|
||||
}
|
||||
if (initialTimeoutId) {
|
||||
window.clearTimeout(initialTimeoutId);
|
||||
initialTimeoutId = null;
|
||||
}
|
||||
|
||||
console.log(`[${responseId}] Performing final cleanup of event listener`);
|
||||
cleanupEventListener(eventListener);
|
||||
@@ -115,13 +157,15 @@ export async function setupStreamingResponse(
|
||||
};
|
||||
|
||||
// Set initial timeout to catch cases where no message is received at all
|
||||
// Increased timeout and better error messaging
|
||||
const INITIAL_TIMEOUT_MS = 15000; // 15 seconds for initial response
|
||||
initialTimeoutId = window.setTimeout(() => {
|
||||
if (!receivedAnyMessage) {
|
||||
console.error(`[${responseId}] No initial message received within timeout`);
|
||||
console.error(`[${responseId}] No initial message received within ${INITIAL_TIMEOUT_MS}ms timeout`);
|
||||
performCleanup();
|
||||
reject(new Error('No response received from server'));
|
||||
reject(new Error('Connection timeout: The AI service is taking longer than expected to respond. Please check your connection and try again.'));
|
||||
}
|
||||
}, 10000);
|
||||
}, INITIAL_TIMEOUT_MS);
|
||||
|
||||
// Create a message handler for CustomEvents
|
||||
eventListener = (event: Event) => {
|
||||
@@ -155,6 +199,12 @@ export async function setupStreamingResponse(
|
||||
window.clearTimeout(initialTimeoutId);
|
||||
initialTimeoutId = null;
|
||||
}
|
||||
|
||||
// Start heartbeat monitoring
|
||||
resetHeartbeatTimeout();
|
||||
} else {
|
||||
// Reset heartbeat on each new message
|
||||
resetHeartbeatTimeout();
|
||||
}
|
||||
|
||||
// Handle error
|
||||
@@ -177,6 +227,28 @@ export async function setupStreamingResponse(
|
||||
onToolExecution(message.toolExecution);
|
||||
}
|
||||
|
||||
// Handle progress updates
|
||||
if (message.progressUpdate) {
|
||||
console.log(`[${responseId}] Progress update:`, message.progressUpdate);
|
||||
onProgressUpdate(message.progressUpdate);
|
||||
}
|
||||
|
||||
// Handle user interaction requests
|
||||
if (message.userInteraction) {
|
||||
console.log(`[${responseId}] User interaction request:`, message.userInteraction);
|
||||
onUserInteraction(message.userInteraction).catch(error => {
|
||||
console.error(`[${responseId}] Error handling user interaction:`, error);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle error recovery options
|
||||
if (message.errorRecovery) {
|
||||
console.log(`[${responseId}] Error recovery options:`, message.errorRecovery);
|
||||
onErrorRecovery(message.errorRecovery).catch(error => {
|
||||
console.error(`[${responseId}] Error handling error recovery:`, error);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle content updates
|
||||
if (message.content) {
|
||||
// Simply append the new content - no complex deduplication
|
||||
@@ -258,3 +330,54 @@ export async function getDirectResponse(noteId: string, messageParams: any): Pro
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send user interaction response
|
||||
* @param interactionId - The interaction ID
|
||||
* @param response - The user's response
|
||||
*/
|
||||
export async function sendUserInteractionResponse(interactionId: string, response: string): Promise<void> {
|
||||
try {
|
||||
await server.post<any>(`llm/interactions/${interactionId}/respond`, {
|
||||
response: response
|
||||
});
|
||||
console.log(`User interaction response sent: ${interactionId} -> ${response}`);
|
||||
} catch (error) {
|
||||
console.error('Error sending user interaction response:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send error recovery choice
|
||||
* @param sessionId - The chat session ID
|
||||
* @param errorId - The error ID
|
||||
* @param action - The recovery action chosen
|
||||
* @param parameters - Optional parameters for the action
|
||||
*/
|
||||
export async function sendErrorRecoveryChoice(sessionId: string, errorId: string, action: string, parameters?: any): Promise<void> {
|
||||
try {
|
||||
await server.post<any>(`llm/chat/${sessionId}/error/${errorId}/recover`, {
|
||||
action: action,
|
||||
parameters: parameters
|
||||
});
|
||||
console.log(`Error recovery choice sent: ${errorId} -> ${action}`);
|
||||
} catch (error) {
|
||||
console.error('Error sending error recovery choice:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel ongoing operations
|
||||
* @param sessionId - The chat session ID
|
||||
*/
|
||||
export async function cancelChatOperations(sessionId: string): Promise<void> {
|
||||
try {
|
||||
await server.post<any>(`llm/chat/${sessionId}/cancel`, {});
|
||||
console.log(`Chat operations cancelled for session: ${sessionId}`);
|
||||
} catch (error) {
|
||||
console.error('Error cancelling chat operations:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
968
apps/client/src/widgets/llm_chat/enhanced_components.css
Normal file
968
apps/client/src/widgets/llm_chat/enhanced_components.css
Normal file
@@ -0,0 +1,968 @@
|
||||
/* Enhanced LLM Chat Components CSS */
|
||||
|
||||
/* =======================
|
||||
PROGRESS INDICATOR STYLES
|
||||
======================= */
|
||||
|
||||
.llm-progress-container {
|
||||
background: var(--main-background-color);
|
||||
border: 1px solid var(--main-border-color);
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.llm-progress-container.fade-in {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.llm-progress-container.fade-out {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
|
||||
.llm-progress-header {
|
||||
padding: 15px 20px 10px;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.llm-progress-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--main-text-color);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.llm-progress-overall {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.llm-progress-bar-container {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background: var(--accented-background-color);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.llm-progress-bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--accent-color), var(--accent-color-darker));
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.llm-progress-percentage {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--muted-text-color);
|
||||
min-width: 40px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.llm-progress-stages {
|
||||
padding: 15px 20px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.llm-progress-stage {
|
||||
margin-bottom: 15px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.llm-progress-stage:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.stage-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stage-status-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stage-label {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.stage-timing {
|
||||
font-size: 12px;
|
||||
color: var(--muted-text-color);
|
||||
min-width: 40px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.stage-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.stage-progress-bar {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: var(--accented-background-color);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stage-progress-fill {
|
||||
height: 100%;
|
||||
background: var(--accent-color);
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.stage-progress-text {
|
||||
font-size: 12px;
|
||||
color: var(--muted-text-color);
|
||||
min-width: 35px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.stage-message {
|
||||
font-size: 12px;
|
||||
color: var(--muted-text-color);
|
||||
margin-left: 30px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Stage status styles */
|
||||
.stage-pending .stage-progress-fill {
|
||||
background: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.stage-running .stage-progress-fill {
|
||||
background: var(--accent-color);
|
||||
}
|
||||
|
||||
.stage-completed .stage-progress-fill {
|
||||
background: #28a745;
|
||||
}
|
||||
|
||||
.stage-failed .stage-progress-fill {
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
.llm-progress-footer {
|
||||
padding: 10px 20px 15px;
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.llm-progress-time-info {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
font-size: 12px;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.llm-progress-cancel-btn {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.llm-progress-cancel-btn:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.llm-progress-cancel-btn:disabled {
|
||||
background: var(--muted-text-color);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* =======================
|
||||
USER INTERACTION STYLES
|
||||
======================= */
|
||||
|
||||
.llm-interaction-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.llm-interaction-overlay.show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.llm-interaction-modal-container {
|
||||
max-width: 90vw;
|
||||
max-height: 90vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.llm-interaction-modal {
|
||||
background: var(--main-background-color);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
transform: translateY(-20px);
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 400px;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.llm-interaction-modal.show {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 20px 20px 15px;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-header.risk-high {
|
||||
background: linear-gradient(135deg, #dc3545, #c82333);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.modal-header.risk-medium {
|
||||
background: linear-gradient(135deg, #ffc107, #e0a800);
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.modal-header.risk-low {
|
||||
background: linear-gradient(135deg, #28a745, #1e7e34);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.risk-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.risk-label {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.tool-info {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.tool-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-color);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.tool-description {
|
||||
font-size: 14px;
|
||||
color: var(--muted-text-color);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tool-arguments {
|
||||
background: var(--accented-background-color);
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.arguments-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--muted-text-color);
|
||||
margin-bottom: 8px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.arguments-content {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.argument-item {
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.argument-key {
|
||||
color: var(--accent-color);
|
||||
font-weight: 600;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.argument-value {
|
||||
color: var(--main-text-color);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.no-arguments {
|
||||
color: var(--muted-text-color);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.confirmation-message,
|
||||
.choice-message,
|
||||
.input-message {
|
||||
font-size: 14px;
|
||||
color: var(--main-text-color);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.choice-options {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.choice-option {
|
||||
background: var(--accented-background-color);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.choice-option:hover {
|
||||
border-color: var(--accent-color);
|
||||
background: var(--hover-item-background-color);
|
||||
}
|
||||
|
||||
.option-label {
|
||||
font-weight: 600;
|
||||
color: var(--main-text-color);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.option-description {
|
||||
font-size: 12px;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.input-field {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.input-field input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--main-border-color);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
background: var(--main-background-color);
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.input-field input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 2px rgba(var(--accent-color-rgb), 0.2);
|
||||
}
|
||||
|
||||
.timeout-indicator {
|
||||
background: var(--accented-background-color);
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.timeout-label {
|
||||
font-size: 12px;
|
||||
color: var(--muted-text-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.timeout-countdown {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.countdown-bar {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
background: var(--main-border-color);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.countdown-fill {
|
||||
height: 100%;
|
||||
background: #ffc107;
|
||||
border-radius: 2px;
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
|
||||
.countdown-text {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-color);
|
||||
min-width: 30px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 15px 20px 20px;
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--accent-color-darker);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--muted-text-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--main-text-color);
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
background: #e0a800;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
/* =======================
|
||||
ERROR RECOVERY STYLES
|
||||
======================= */
|
||||
|
||||
.llm-error-recovery-container {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.llm-error-recovery-item {
|
||||
background: var(--main-background-color);
|
||||
border: 2px solid #dc3545;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
box-shadow: 0 2px 8px rgba(220, 53, 69, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.llm-error-recovery-item.fade-out {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
|
||||
.error-header {
|
||||
background: linear-gradient(135deg, #dc3545, #c82333);
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.error-tool-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.error-attempt-info {
|
||||
font-size: 12px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.error-type-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
background: #17a2b8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-secondary {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.error-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.error-message-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--muted-text-color);
|
||||
margin-bottom: 5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.error-message-content {
|
||||
background: var(--accented-background-color);
|
||||
border-left: 4px solid #dc3545;
|
||||
padding: 10px 12px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
font-size: 14px;
|
||||
color: var(--main-text-color);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.error-context {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.context-section {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.context-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--muted-text-color);
|
||||
margin-bottom: 5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.context-content {
|
||||
background: var(--accented-background-color);
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.param-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.param-key {
|
||||
color: var(--accent-color);
|
||||
font-weight: 600;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.param-value {
|
||||
color: var(--main-text-color);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.previous-attempts-list,
|
||||
.suggestions-list {
|
||||
margin: 0;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.previous-attempts-list li,
|
||||
.suggestions-list li {
|
||||
margin-bottom: 4px;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.auto-retry-section {
|
||||
background: linear-gradient(135deg, #ffc107, #e0a800);
|
||||
color: #212529;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.auto-retry-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.retry-countdown {
|
||||
font-weight: 700;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.auto-retry-progress {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.retry-progress-bar {
|
||||
height: 6px;
|
||||
background: rgba(33, 37, 41, 0.2);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.retry-progress-fill {
|
||||
height: 100%;
|
||||
background: #dc3545;
|
||||
border-radius: 3px;
|
||||
transition: width 1s linear;
|
||||
}
|
||||
|
||||
.cancel-auto-retry {
|
||||
background: rgba(33, 37, 41, 0.8);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cancel-auto-retry:hover {
|
||||
background: #212529;
|
||||
}
|
||||
|
||||
.recovery-actions {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.recovery-actions-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--main-text-color);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.recovery-actions-grid {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.recovery-action {
|
||||
background: var(--accented-background-color);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.recovery-action:hover {
|
||||
border-color: var(--accent-color);
|
||||
background: var(--hover-item-background-color);
|
||||
}
|
||||
|
||||
.action-retry:hover {
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
.action-skip:hover {
|
||||
border-color: #6c757d;
|
||||
}
|
||||
|
||||
.action-modify:hover {
|
||||
border-color: #ffc107;
|
||||
}
|
||||
|
||||
.action-abort:hover {
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
.action-alternative:hover {
|
||||
border-color: #17a2b8;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.action-retry .action-icon {
|
||||
background: #28a745;
|
||||
}
|
||||
|
||||
.action-skip .action-icon {
|
||||
background: #6c757d;
|
||||
}
|
||||
|
||||
.action-modify .action-icon {
|
||||
background: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.action-abort .action-icon {
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
.action-alternative .action-icon {
|
||||
background: #17a2b8;
|
||||
}
|
||||
|
||||
.action-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.action-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--main-text-color);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.action-description {
|
||||
font-size: 12px;
|
||||
color: var(--muted-text-color);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.action-arrow {
|
||||
color: var(--muted-text-color);
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.recovery-action:hover .action-arrow {
|
||||
opacity: 1;
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
/* =======================
|
||||
RESPONSIVE DESIGN
|
||||
======================= */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.llm-interaction-modal {
|
||||
min-width: auto;
|
||||
width: 90vw;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 15px;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 15px;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.llm-progress-header,
|
||||
.llm-progress-stages,
|
||||
.llm-progress-footer {
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.llm-progress-footer {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.recovery-actions-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* =======================
|
||||
DARK MODE ADJUSTMENTS
|
||||
======================= */
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.llm-interaction-overlay {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.countdown-fill {
|
||||
background: #f39c12;
|
||||
}
|
||||
|
||||
.auto-retry-section {
|
||||
background: linear-gradient(135deg, #f39c12, #d68910);
|
||||
color: #212529;
|
||||
}
|
||||
}
|
||||
|
||||
/* =======================
|
||||
ANIMATIONS
|
||||
======================= */
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.stage-running .stage-status-icon i {
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes slideInUp {
|
||||
from {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.llm-error-recovery-item {
|
||||
animation: slideInUp 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -200px 0;
|
||||
}
|
||||
100% {
|
||||
background-position: calc(200px + 100%) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.stage-running .stage-progress-fill {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--accent-color) 0%,
|
||||
var(--accent-color-lighter) 50%,
|
||||
var(--accent-color) 100%
|
||||
);
|
||||
background-size: 200px 100%;
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
511
apps/client/src/widgets/llm_chat/enhanced_tool_integration.ts
Normal file
511
apps/client/src/widgets/llm_chat/enhanced_tool_integration.ts
Normal file
@@ -0,0 +1,511 @@
|
||||
/**
|
||||
* Enhanced Tool Integration
|
||||
*
|
||||
* Integrates tool preview, feedback, and error recovery into the LLM chat experience.
|
||||
*/
|
||||
|
||||
import server from "../../services/server.js";
|
||||
import { ToolPreviewUI, type ExecutionPlanData, type UserApproval } from "./tool_preview_ui.js";
|
||||
import { ToolFeedbackUI, type ToolProgressData, type ToolStepData } from "./tool_feedback_ui.js";
|
||||
|
||||
/**
|
||||
* Enhanced tool integration configuration
|
||||
*/
|
||||
export interface EnhancedToolConfig {
|
||||
enablePreview?: boolean;
|
||||
enableFeedback?: boolean;
|
||||
enableErrorRecovery?: boolean;
|
||||
requireConfirmation?: boolean;
|
||||
autoApproveTimeout?: number;
|
||||
showHistory?: boolean;
|
||||
showStatistics?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default configuration
|
||||
*/
|
||||
const DEFAULT_CONFIG: EnhancedToolConfig = {
|
||||
enablePreview: true,
|
||||
enableFeedback: true,
|
||||
enableErrorRecovery: true,
|
||||
requireConfirmation: true,
|
||||
autoApproveTimeout: 30000, // 30 seconds
|
||||
showHistory: true,
|
||||
showStatistics: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Enhanced Tool Integration Manager
|
||||
*/
|
||||
export class EnhancedToolIntegration {
|
||||
private config: EnhancedToolConfig;
|
||||
private previewUI?: ToolPreviewUI;
|
||||
private feedbackUI?: ToolFeedbackUI;
|
||||
private container: HTMLElement;
|
||||
private eventHandlers: Map<string, Function[]> = new Map();
|
||||
private activeExecutions: Set<string> = new Set();
|
||||
|
||||
constructor(container: HTMLElement, config?: Partial<EnhancedToolConfig>) {
|
||||
this.container = container;
|
||||
this.config = { ...DEFAULT_CONFIG, ...config };
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the integration
|
||||
*/
|
||||
private initialize(): void {
|
||||
// Create UI containers
|
||||
this.createUIContainers();
|
||||
|
||||
// Initialize UI components
|
||||
if (this.config.enablePreview) {
|
||||
const previewContainer = this.container.querySelector('.tool-preview-area') as HTMLElement;
|
||||
if (previewContainer) {
|
||||
this.previewUI = new ToolPreviewUI(previewContainer);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.config.enableFeedback) {
|
||||
const feedbackContainer = this.container.querySelector('.tool-feedback-area') as HTMLElement;
|
||||
if (feedbackContainer) {
|
||||
this.feedbackUI = new ToolFeedbackUI(feedbackContainer);
|
||||
|
||||
// Set up history and stats containers if enabled
|
||||
if (this.config.showHistory) {
|
||||
const historyContainer = this.container.querySelector('.tool-history-area') as HTMLElement;
|
||||
if (historyContainer) {
|
||||
this.feedbackUI.setHistoryContainer(historyContainer);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.config.showStatistics) {
|
||||
const statsContainer = this.container.querySelector('.tool-stats-area') as HTMLElement;
|
||||
if (statsContainer) {
|
||||
this.feedbackUI.setStatsContainer(statsContainer);
|
||||
this.loadStatistics();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load initial data
|
||||
this.loadActiveExecutions();
|
||||
this.loadCircuitBreakerStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create UI containers
|
||||
*/
|
||||
private createUIContainers(): void {
|
||||
// Add enhanced tool UI areas if they don't exist
|
||||
if (!this.container.querySelector('.tool-preview-area')) {
|
||||
const previewArea = document.createElement('div');
|
||||
previewArea.className = 'tool-preview-area mb-3';
|
||||
this.container.appendChild(previewArea);
|
||||
}
|
||||
|
||||
if (!this.container.querySelector('.tool-feedback-area')) {
|
||||
const feedbackArea = document.createElement('div');
|
||||
feedbackArea.className = 'tool-feedback-area mb-3';
|
||||
this.container.appendChild(feedbackArea);
|
||||
}
|
||||
|
||||
if (this.config.showHistory && !this.container.querySelector('.tool-history-area')) {
|
||||
const historySection = document.createElement('div');
|
||||
historySection.className = 'tool-history-section mt-3';
|
||||
historySection.innerHTML = `
|
||||
<details class="small">
|
||||
<summary class="text-muted cursor-pointer">
|
||||
<i class="bx bx-history me-1"></i>
|
||||
Execution History
|
||||
</summary>
|
||||
<div class="tool-history-area mt-2"></div>
|
||||
</details>
|
||||
`;
|
||||
this.container.appendChild(historySection);
|
||||
}
|
||||
|
||||
if (this.config.showStatistics && !this.container.querySelector('.tool-stats-area')) {
|
||||
const statsSection = document.createElement('div');
|
||||
statsSection.className = 'tool-stats-section mt-3';
|
||||
statsSection.innerHTML = `
|
||||
<details class="small">
|
||||
<summary class="text-muted cursor-pointer">
|
||||
<i class="bx bx-bar-chart me-1"></i>
|
||||
Tool Statistics
|
||||
</summary>
|
||||
<div class="tool-stats-area mt-2"></div>
|
||||
</details>
|
||||
`;
|
||||
this.container.appendChild(statsSection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle tool preview request
|
||||
*/
|
||||
public async handleToolPreview(toolCalls: any[]): Promise<UserApproval | null> {
|
||||
if (!this.config.enablePreview || !this.previewUI) {
|
||||
// Auto-approve if preview is disabled
|
||||
return {
|
||||
planId: `auto-${Date.now()}`,
|
||||
approved: true
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// Get preview from server
|
||||
const response = await server.post<ExecutionPlanData>('api/llm-tools/preview', {
|
||||
toolCalls
|
||||
});
|
||||
|
||||
if (!response) {
|
||||
console.error('Failed to get tool preview');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Show preview and wait for user approval
|
||||
return new Promise((resolve) => {
|
||||
let timeoutId: number | undefined;
|
||||
|
||||
const handleApproval = (approval: UserApproval) => {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
// Send approval to server
|
||||
server.post(`api/llm-tools/preview/${approval.planId}/approval`, approval)
|
||||
.catch(error => console.error('Failed to record approval:', error));
|
||||
|
||||
resolve(approval);
|
||||
};
|
||||
|
||||
// Show preview UI
|
||||
this.previewUI!.showPreview(response, handleApproval);
|
||||
|
||||
// Auto-approve after timeout if configured
|
||||
if (this.config.autoApproveTimeout && response.requiresConfirmation) {
|
||||
timeoutId = window.setTimeout(() => {
|
||||
const autoApproval: UserApproval = {
|
||||
planId: response.id,
|
||||
approved: true
|
||||
};
|
||||
handleApproval(autoApproval);
|
||||
}, this.config.autoApproveTimeout);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error handling tool preview:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start tool execution tracking
|
||||
*/
|
||||
public startToolExecution(
|
||||
executionId: string,
|
||||
toolName: string,
|
||||
displayName?: string
|
||||
): void {
|
||||
if (!this.config.enableFeedback || !this.feedbackUI) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeExecutions.add(executionId);
|
||||
this.feedbackUI.startExecution(executionId, toolName, displayName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tool execution progress
|
||||
*/
|
||||
public updateToolProgress(data: ToolProgressData): void {
|
||||
if (!this.config.enableFeedback || !this.feedbackUI) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.feedbackUI.updateProgress(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tool execution step
|
||||
*/
|
||||
public addToolStep(data: ToolStepData): void {
|
||||
if (!this.config.enableFeedback || !this.feedbackUI) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.feedbackUI.addStep(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete tool execution
|
||||
*/
|
||||
public completeToolExecution(
|
||||
executionId: string,
|
||||
status: 'success' | 'error' | 'cancelled' | 'timeout',
|
||||
result?: any,
|
||||
error?: string
|
||||
): void {
|
||||
if (!this.config.enableFeedback || !this.feedbackUI) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeExecutions.delete(executionId);
|
||||
this.feedbackUI.completeExecution(executionId, status, result, error);
|
||||
|
||||
// Refresh statistics
|
||||
if (this.config.showStatistics) {
|
||||
setTimeout(() => this.loadStatistics(), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel tool execution
|
||||
*/
|
||||
public async cancelToolExecution(executionId: string, reason?: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await server.post<any>(`api/llm-tools/executions/${executionId}/cancel`, {
|
||||
reason
|
||||
});
|
||||
|
||||
if (response?.success) {
|
||||
this.completeToolExecution(executionId, 'cancelled', undefined, reason);
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to cancel execution:', error);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load active executions
|
||||
*/
|
||||
private async loadActiveExecutions(): Promise<void> {
|
||||
if (!this.config.enableFeedback) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const executions = await server.get<any[]>('api/llm-tools/executions/active');
|
||||
|
||||
if (executions && Array.isArray(executions)) {
|
||||
executions.forEach(exec => {
|
||||
if (!this.activeExecutions.has(exec.id)) {
|
||||
this.startToolExecution(exec.id, exec.toolName);
|
||||
// Restore progress if available
|
||||
if (exec.progress) {
|
||||
this.updateToolProgress({
|
||||
executionId: exec.id,
|
||||
...exec.progress
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load active executions:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load execution statistics
|
||||
*/
|
||||
private async loadStatistics(): Promise<void> {
|
||||
if (!this.config.showStatistics) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const stats = await server.get<any>('api/llm-tools/executions/stats');
|
||||
|
||||
if (stats) {
|
||||
this.displayStatistics(stats);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load statistics:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display statistics
|
||||
*/
|
||||
private displayStatistics(stats: any): void {
|
||||
const container = this.container.querySelector('.tool-stats-area') as HTMLElement;
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="tool-stats-container">
|
||||
<div class="tool-stat-item">
|
||||
<div class="tool-stat-value">${stats.totalExecutions}</div>
|
||||
<div class="tool-stat-label">Total</div>
|
||||
</div>
|
||||
<div class="tool-stat-item">
|
||||
<div class="tool-stat-value text-success">${stats.successfulExecutions}</div>
|
||||
<div class="tool-stat-label">Success</div>
|
||||
</div>
|
||||
<div class="tool-stat-item">
|
||||
<div class="tool-stat-value text-danger">${stats.failedExecutions}</div>
|
||||
<div class="tool-stat-label">Failed</div>
|
||||
</div>
|
||||
<div class="tool-stat-item">
|
||||
<div class="tool-stat-value">${this.formatDuration(stats.averageDuration)}</div>
|
||||
<div class="tool-stat-label">Avg Time</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add tool-specific statistics if available
|
||||
if (stats.toolStatistics && Object.keys(stats.toolStatistics).length > 0) {
|
||||
const toolStatsHtml = Object.entries(stats.toolStatistics)
|
||||
.map(([toolName, toolStats]: [string, any]) => `
|
||||
<tr>
|
||||
<td>${toolName}</td>
|
||||
<td>${toolStats.count}</td>
|
||||
<td>${toolStats.successRate}%</td>
|
||||
<td>${this.formatDuration(toolStats.averageDuration)}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
container.innerHTML += `
|
||||
<div class="mt-3">
|
||||
<h6 class="small text-muted">Per-Tool Statistics</h6>
|
||||
<table class="table table-sm small">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tool</th>
|
||||
<th>Count</th>
|
||||
<th>Success</th>
|
||||
<th>Avg Time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${toolStatsHtml}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load circuit breaker status
|
||||
*/
|
||||
private async loadCircuitBreakerStatus(): Promise<void> {
|
||||
try {
|
||||
const statuses = await server.get<any[]>('api/llm-tools/circuit-breakers');
|
||||
|
||||
if (statuses && Array.isArray(statuses)) {
|
||||
this.displayCircuitBreakerStatus(statuses);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load circuit breaker status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display circuit breaker status
|
||||
*/
|
||||
private displayCircuitBreakerStatus(statuses: any[]): void {
|
||||
const openBreakers = statuses.filter(s => s.state === 'open');
|
||||
const halfOpenBreakers = statuses.filter(s => s.state === 'half_open');
|
||||
|
||||
if (openBreakers.length > 0 || halfOpenBreakers.length > 0) {
|
||||
const alertContainer = document.createElement('div');
|
||||
alertContainer.className = 'circuit-breaker-alerts mb-3';
|
||||
|
||||
if (openBreakers.length > 0) {
|
||||
alertContainer.innerHTML += `
|
||||
<div class="alert alert-danger small py-2">
|
||||
<i class="bx bx-error-circle me-1"></i>
|
||||
<strong>Circuit Breakers Open:</strong>
|
||||
${openBreakers.map(b => b.toolName).join(', ')}
|
||||
<button class="btn btn-sm btn-link reset-breakers-btn float-end py-0">
|
||||
Reset All
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
if (halfOpenBreakers.length > 0) {
|
||||
alertContainer.innerHTML += `
|
||||
<div class="alert alert-warning small py-2">
|
||||
<i class="bx bx-error me-1"></i>
|
||||
<strong>Circuit Breakers Half-Open:</strong>
|
||||
${halfOpenBreakers.map(b => b.toolName).join(', ')}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Add to container
|
||||
const existingAlerts = this.container.querySelector('.circuit-breaker-alerts');
|
||||
if (existingAlerts) {
|
||||
existingAlerts.replaceWith(alertContainer);
|
||||
} else {
|
||||
this.container.insertBefore(alertContainer, this.container.firstChild);
|
||||
}
|
||||
|
||||
// Add reset handler
|
||||
const resetBtn = alertContainer.querySelector('.reset-breakers-btn');
|
||||
resetBtn?.addEventListener('click', () => this.resetAllCircuitBreakers(openBreakers));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all circuit breakers
|
||||
*/
|
||||
private async resetAllCircuitBreakers(breakers: any[]): Promise<void> {
|
||||
for (const breaker of breakers) {
|
||||
try {
|
||||
await server.post(`api/llm-tools/circuit-breakers/${breaker.toolName}/reset`, {});
|
||||
} catch (error) {
|
||||
console.error(`Failed to reset circuit breaker for ${breaker.toolName}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Reload status
|
||||
this.loadCircuitBreakerStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format duration
|
||||
*/
|
||||
private formatDuration(milliseconds: number): string {
|
||||
if (!milliseconds || milliseconds === 0) return '0ms';
|
||||
if (milliseconds < 1000) {
|
||||
return `${Math.round(milliseconds)}ms`;
|
||||
} else if (milliseconds < 60000) {
|
||||
return `${(milliseconds / 1000).toFixed(1)}s`;
|
||||
} else {
|
||||
const minutes = Math.floor(milliseconds / 60000);
|
||||
const seconds = Math.floor((milliseconds % 60000) / 1000);
|
||||
return `${minutes}m ${seconds}s`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources
|
||||
*/
|
||||
public dispose(): void {
|
||||
this.eventHandlers.clear();
|
||||
this.activeExecutions.clear();
|
||||
|
||||
if (this.feedbackUI) {
|
||||
this.feedbackUI.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create enhanced tool integration
|
||||
*/
|
||||
export function createEnhancedToolIntegration(
|
||||
container: HTMLElement,
|
||||
config?: Partial<EnhancedToolConfig>
|
||||
): EnhancedToolIntegration {
|
||||
return new EnhancedToolIntegration(container, config);
|
||||
}
|
||||
451
apps/client/src/widgets/llm_chat/error_recovery_manager.ts
Normal file
451
apps/client/src/widgets/llm_chat/error_recovery_manager.ts
Normal file
@@ -0,0 +1,451 @@
|
||||
interface ErrorRecoveryOptions {
|
||||
errorId: string;
|
||||
toolName: string;
|
||||
message: string;
|
||||
errorType: string;
|
||||
attempt: number;
|
||||
maxAttempts: number;
|
||||
recoveryActions: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
action: 'retry' | 'skip' | 'modify' | 'abort' | 'alternative';
|
||||
parameters?: Record<string, unknown>;
|
||||
}>;
|
||||
autoRetryIn?: number; // seconds
|
||||
context?: {
|
||||
originalParams?: Record<string, unknown>;
|
||||
previousAttempts?: string[];
|
||||
suggestions?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
interface ErrorRecoveryResponse {
|
||||
errorId: string;
|
||||
action: string;
|
||||
parameters?: Record<string, unknown>;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error Recovery Manager for LLM Chat
|
||||
* Handles sophisticated error recovery with multiple strategies and user guidance
|
||||
*/
|
||||
export class ErrorRecoveryManager {
|
||||
private activeErrors: Map<string, ErrorRecoveryOptions> = new Map();
|
||||
private responseCallbacks: Map<string, (response: ErrorRecoveryResponse) => void> = new Map();
|
||||
private container: HTMLElement;
|
||||
|
||||
constructor(parentElement: HTMLElement) {
|
||||
this.container = this.createErrorContainer();
|
||||
parentElement.appendChild(this.container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create error recovery container
|
||||
*/
|
||||
private createErrorContainer(): HTMLElement {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'llm-error-recovery-container';
|
||||
container.style.display = 'none';
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show error recovery options
|
||||
*/
|
||||
public async showErrorRecovery(options: ErrorRecoveryOptions): Promise<ErrorRecoveryResponse> {
|
||||
this.activeErrors.set(options.errorId, options);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.responseCallbacks.set(options.errorId, resolve);
|
||||
|
||||
const errorElement = this.createErrorElement(options);
|
||||
this.container.appendChild(errorElement);
|
||||
this.container.style.display = 'block';
|
||||
|
||||
// Start auto-retry countdown if enabled
|
||||
if (options.autoRetryIn && options.autoRetryIn > 0) {
|
||||
this.startAutoRetryCountdown(options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create error recovery element
|
||||
*/
|
||||
private createErrorElement(options: ErrorRecoveryOptions): HTMLElement {
|
||||
const element = document.createElement('div');
|
||||
element.className = 'llm-error-recovery-item';
|
||||
element.setAttribute('data-error-id', options.errorId);
|
||||
|
||||
element.innerHTML = `
|
||||
<div class="error-header">
|
||||
<div class="error-icon">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
</div>
|
||||
<div class="error-title">
|
||||
<div class="error-tool-name">${options.toolName} Failed</div>
|
||||
<div class="error-attempt-info">Attempt ${options.attempt}/${options.maxAttempts}</div>
|
||||
</div>
|
||||
<div class="error-type-badge ${this.getErrorTypeBadgeClass(options.errorType)}">
|
||||
${options.errorType}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="error-body">
|
||||
<div class="error-message">
|
||||
<div class="error-message-label">Error Details:</div>
|
||||
<div class="error-message-content">${options.message}</div>
|
||||
</div>
|
||||
|
||||
${this.createContextSection(options.context)}
|
||||
${this.createAutoRetrySection(options.autoRetryIn)}
|
||||
|
||||
<div class="recovery-actions">
|
||||
<div class="recovery-actions-label">Recovery Options:</div>
|
||||
<div class="recovery-actions-grid">
|
||||
${this.createRecoveryActions(options)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.attachErrorEvents(element, options);
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create context section
|
||||
*/
|
||||
private createContextSection(context?: ErrorRecoveryOptions['context']): string {
|
||||
if (!context) return '';
|
||||
|
||||
return `
|
||||
<div class="error-context">
|
||||
${context.originalParams ? `
|
||||
<div class="context-section">
|
||||
<div class="context-label">Original Parameters:</div>
|
||||
<div class="context-content">
|
||||
${this.formatParameters(context.originalParams)}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${context.previousAttempts && context.previousAttempts.length > 0 ? `
|
||||
<div class="context-section">
|
||||
<div class="context-label">Previous Attempts:</div>
|
||||
<div class="context-content">
|
||||
<ul class="previous-attempts-list">
|
||||
${context.previousAttempts.map(attempt => `<li>${attempt}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${context.suggestions && context.suggestions.length > 0 ? `
|
||||
<div class="context-section">
|
||||
<div class="context-label">Suggestions:</div>
|
||||
<div class="context-content">
|
||||
<ul class="suggestions-list">
|
||||
${context.suggestions.map(suggestion => `<li>${suggestion}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create auto-retry section
|
||||
*/
|
||||
private createAutoRetrySection(autoRetryIn?: number): string {
|
||||
if (!autoRetryIn || autoRetryIn <= 0) return '';
|
||||
|
||||
return `
|
||||
<div class="auto-retry-section">
|
||||
<div class="auto-retry-info">
|
||||
<i class="fas fa-clock"></i>
|
||||
<span>Auto-retry in <span class="retry-countdown">${autoRetryIn}</span> seconds</span>
|
||||
</div>
|
||||
<div class="auto-retry-progress">
|
||||
<div class="retry-progress-bar">
|
||||
<div class="retry-progress-fill"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-secondary cancel-auto-retry">Cancel Auto-retry</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create recovery actions
|
||||
*/
|
||||
private createRecoveryActions(options: ErrorRecoveryOptions): string {
|
||||
return options.recoveryActions.map(action => {
|
||||
const actionClass = this.getActionClass(action.action);
|
||||
const icon = this.getActionIcon(action.action);
|
||||
|
||||
return `
|
||||
<div class="recovery-action ${actionClass}" data-action-id="${action.id}">
|
||||
<div class="action-icon">
|
||||
<i class="${icon}"></i>
|
||||
</div>
|
||||
<div class="action-content">
|
||||
<div class="action-label">${action.label}</div>
|
||||
${action.description ? `<div class="action-description">${action.description}</div>` : ''}
|
||||
</div>
|
||||
<div class="action-arrow">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format parameters for display
|
||||
*/
|
||||
private formatParameters(params: Record<string, unknown>): string {
|
||||
return Object.entries(params).map(([key, value]) => {
|
||||
let displayValue: string;
|
||||
if (typeof value === 'string') {
|
||||
displayValue = value.length > 50 ? value.substring(0, 50) + '...' : value;
|
||||
displayValue = `"${displayValue}"`;
|
||||
} else if (typeof value === 'object') {
|
||||
displayValue = JSON.stringify(value, null, 2);
|
||||
} else {
|
||||
displayValue = String(value);
|
||||
}
|
||||
|
||||
return `<div class="param-item">
|
||||
<span class="param-key">${key}:</span>
|
||||
<span class="param-value">${displayValue}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get error type badge class
|
||||
*/
|
||||
private getErrorTypeBadgeClass(errorType: string): string {
|
||||
const typeMap: Record<string, string> = {
|
||||
'NetworkError': 'badge-warning',
|
||||
'TimeoutError': 'badge-warning',
|
||||
'ValidationError': 'badge-danger',
|
||||
'NotFoundError': 'badge-info',
|
||||
'PermissionError': 'badge-danger',
|
||||
'RateLimitError': 'badge-warning',
|
||||
'UnknownError': 'badge-secondary'
|
||||
};
|
||||
|
||||
return typeMap[errorType] || 'badge-secondary';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get action class
|
||||
*/
|
||||
private getActionClass(action: string): string {
|
||||
const actionMap: Record<string, string> = {
|
||||
'retry': 'action-retry',
|
||||
'skip': 'action-skip',
|
||||
'modify': 'action-modify',
|
||||
'abort': 'action-abort',
|
||||
'alternative': 'action-alternative'
|
||||
};
|
||||
|
||||
return actionMap[action] || 'action-default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get action icon
|
||||
*/
|
||||
private getActionIcon(action: string): string {
|
||||
const iconMap: Record<string, string> = {
|
||||
'retry': 'fas fa-redo',
|
||||
'skip': 'fas fa-forward',
|
||||
'modify': 'fas fa-edit',
|
||||
'abort': 'fas fa-times',
|
||||
'alternative': 'fas fa-route'
|
||||
};
|
||||
|
||||
return iconMap[action] || 'fas fa-cog';
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach error events
|
||||
*/
|
||||
private attachErrorEvents(element: HTMLElement, options: ErrorRecoveryOptions): void {
|
||||
// Recovery action clicks
|
||||
const actions = element.querySelectorAll('.recovery-action');
|
||||
actions.forEach(action => {
|
||||
action.addEventListener('click', (e) => {
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
const actionId = target.getAttribute('data-action-id');
|
||||
if (actionId) {
|
||||
const recoveryAction = options.recoveryActions.find(a => a.id === actionId);
|
||||
if (recoveryAction) {
|
||||
this.executeRecoveryAction(options.errorId, recoveryAction);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Cancel auto-retry
|
||||
const cancelAutoRetry = element.querySelector('.cancel-auto-retry');
|
||||
if (cancelAutoRetry) {
|
||||
cancelAutoRetry.addEventListener('click', () => {
|
||||
this.cancelAutoRetry(options.errorId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start auto-retry countdown
|
||||
*/
|
||||
private startAutoRetryCountdown(options: ErrorRecoveryOptions): void {
|
||||
if (!options.autoRetryIn) return;
|
||||
|
||||
const element = this.container.querySelector(`[data-error-id="${options.errorId}"]`) as HTMLElement;
|
||||
if (!element) return;
|
||||
|
||||
const countdownElement = element.querySelector('.retry-countdown') as HTMLElement;
|
||||
const progressFill = element.querySelector('.retry-progress-fill') as HTMLElement;
|
||||
|
||||
let remainingTime = options.autoRetryIn;
|
||||
const totalTime = options.autoRetryIn;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
remainingTime--;
|
||||
|
||||
if (countdownElement) {
|
||||
countdownElement.textContent = remainingTime.toString();
|
||||
}
|
||||
|
||||
if (progressFill) {
|
||||
const progress = ((totalTime - remainingTime) / totalTime) * 100;
|
||||
progressFill.style.width = `${progress}%`;
|
||||
}
|
||||
|
||||
if (remainingTime <= 0) {
|
||||
clearInterval(interval);
|
||||
// Auto-execute retry
|
||||
const retryAction = options.recoveryActions.find(a => a.action === 'retry');
|
||||
if (retryAction) {
|
||||
this.executeRecoveryAction(options.errorId, retryAction);
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Store interval for potential cancellation
|
||||
element.setAttribute('data-retry-interval', interval.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel auto-retry
|
||||
*/
|
||||
private cancelAutoRetry(errorId: string): void {
|
||||
const element = this.container.querySelector(`[data-error-id="${errorId}"]`) as HTMLElement;
|
||||
if (!element) return;
|
||||
|
||||
const intervalId = element.getAttribute('data-retry-interval');
|
||||
if (intervalId) {
|
||||
clearInterval(parseInt(intervalId));
|
||||
element.removeAttribute('data-retry-interval');
|
||||
}
|
||||
|
||||
// Hide auto-retry section
|
||||
const autoRetrySection = element.querySelector('.auto-retry-section') as HTMLElement;
|
||||
if (autoRetrySection) {
|
||||
autoRetrySection.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute recovery action
|
||||
*/
|
||||
private executeRecoveryAction(errorId: string, action: ErrorRecoveryOptions['recoveryActions'][0]): void {
|
||||
const callback = this.responseCallbacks.get(errorId);
|
||||
if (!callback) return;
|
||||
|
||||
const response: ErrorRecoveryResponse = {
|
||||
errorId,
|
||||
action: action.action,
|
||||
parameters: action.parameters,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
// Clean up
|
||||
this.activeErrors.delete(errorId);
|
||||
this.responseCallbacks.delete(errorId);
|
||||
this.removeErrorElement(errorId);
|
||||
|
||||
// Call callback
|
||||
callback(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove error element
|
||||
*/
|
||||
private removeErrorElement(errorId: string): void {
|
||||
const element = this.container.querySelector(`[data-error-id="${errorId}"]`) as HTMLElement;
|
||||
if (element) {
|
||||
element.classList.add('fade-out');
|
||||
setTimeout(() => {
|
||||
element.remove();
|
||||
|
||||
// Hide container if no more errors
|
||||
if (this.container.children.length === 0) {
|
||||
this.container.style.display = 'none';
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all errors
|
||||
*/
|
||||
public clearAllErrors(): void {
|
||||
this.activeErrors.clear();
|
||||
this.responseCallbacks.clear();
|
||||
this.container.innerHTML = '';
|
||||
this.container.style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active error count
|
||||
*/
|
||||
public getActiveErrorCount(): number {
|
||||
return this.activeErrors.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if error recovery is active
|
||||
*/
|
||||
public hasActiveErrors(): boolean {
|
||||
return this.activeErrors.size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update error context (for adding new information)
|
||||
*/
|
||||
public updateErrorContext(errorId: string, newContext: Partial<ErrorRecoveryOptions['context']>): void {
|
||||
const options = this.activeErrors.get(errorId);
|
||||
if (!options) return;
|
||||
|
||||
options.context = { ...options.context, ...newContext };
|
||||
|
||||
// Re-render the context section
|
||||
const element = this.container.querySelector(`[data-error-id="${errorId}"]`) as HTMLElement;
|
||||
if (element) {
|
||||
const contextContainer = element.querySelector('.error-context') as HTMLElement;
|
||||
if (contextContainer) {
|
||||
contextContainer.outerHTML = this.createContextSection(options.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Export types for use in other modules
|
||||
export type { ErrorRecoveryOptions, ErrorRecoveryResponse };
|
||||
529
apps/client/src/widgets/llm_chat/interaction_manager.ts
Normal file
529
apps/client/src/widgets/llm_chat/interaction_manager.ts
Normal file
@@ -0,0 +1,529 @@
|
||||
interface UserInteractionRequest {
|
||||
id: string;
|
||||
type: 'confirmation' | 'choice' | 'input' | 'tool_confirmation';
|
||||
title: string;
|
||||
message: string;
|
||||
options?: Array<{
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
style?: 'primary' | 'secondary' | 'warning' | 'danger';
|
||||
action?: string;
|
||||
}>;
|
||||
defaultValue?: string;
|
||||
timeout?: number; // milliseconds
|
||||
tool?: {
|
||||
name: string;
|
||||
description: string;
|
||||
arguments: Record<string, unknown>;
|
||||
riskLevel?: 'low' | 'medium' | 'high';
|
||||
};
|
||||
}
|
||||
|
||||
interface UserInteractionResponse {
|
||||
id: string;
|
||||
response: string;
|
||||
value?: any;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* User Interaction Manager for LLM Chat
|
||||
* Handles confirmations, choices, and input prompts during LLM operations
|
||||
*/
|
||||
export class InteractionManager {
|
||||
private activeInteractions: Map<string, UserInteractionRequest> = new Map();
|
||||
private responseCallbacks: Map<string, (response: UserInteractionResponse) => void> = new Map();
|
||||
private modalContainer: HTMLElement;
|
||||
private overlay: HTMLElement;
|
||||
|
||||
constructor(parentElement: HTMLElement) {
|
||||
this.createModalContainer(parentElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create modal container and overlay
|
||||
*/
|
||||
private createModalContainer(parentElement: HTMLElement): void {
|
||||
// Create overlay
|
||||
this.overlay = document.createElement('div');
|
||||
this.overlay.className = 'llm-interaction-overlay';
|
||||
this.overlay.style.display = 'none';
|
||||
|
||||
// Create modal container
|
||||
this.modalContainer = document.createElement('div');
|
||||
this.modalContainer.className = 'llm-interaction-modal-container';
|
||||
|
||||
this.overlay.appendChild(this.modalContainer);
|
||||
parentElement.appendChild(this.overlay);
|
||||
|
||||
// Close on overlay click
|
||||
this.overlay.addEventListener('click', (e) => {
|
||||
if (e.target === this.overlay) {
|
||||
this.cancelAllInteractions();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle escape key
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && this.hasActiveInteractions()) {
|
||||
this.cancelAllInteractions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Request user interaction
|
||||
*/
|
||||
public async requestUserInteraction(request: UserInteractionRequest): Promise<UserInteractionResponse> {
|
||||
this.activeInteractions.set(request.id, request);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Set up response callback
|
||||
this.responseCallbacks.set(request.id, resolve);
|
||||
|
||||
// Create and show modal
|
||||
const modal = this.createInteractionModal(request);
|
||||
this.showModal(modal);
|
||||
|
||||
// Set up timeout if specified
|
||||
if (request.timeout && request.timeout > 0) {
|
||||
setTimeout(() => {
|
||||
if (this.activeInteractions.has(request.id)) {
|
||||
this.handleTimeout(request.id);
|
||||
}
|
||||
}, request.timeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create interaction modal based on request type
|
||||
*/
|
||||
private createInteractionModal(request: UserInteractionRequest): HTMLElement {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = `llm-interaction-modal llm-interaction-${request.type}`;
|
||||
modal.setAttribute('data-interaction-id', request.id);
|
||||
|
||||
switch (request.type) {
|
||||
case 'tool_confirmation':
|
||||
return this.createToolConfirmationModal(modal, request);
|
||||
case 'confirmation':
|
||||
return this.createConfirmationModal(modal, request);
|
||||
case 'choice':
|
||||
return this.createChoiceModal(modal, request);
|
||||
case 'input':
|
||||
return this.createInputModal(modal, request);
|
||||
default:
|
||||
return this.createGenericModal(modal, request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tool confirmation modal
|
||||
*/
|
||||
private createToolConfirmationModal(modal: HTMLElement, request: UserInteractionRequest): HTMLElement {
|
||||
const tool = request.tool!;
|
||||
const riskClass = tool.riskLevel ? `risk-${tool.riskLevel}` : '';
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="modal-header ${riskClass}">
|
||||
<div class="modal-title">
|
||||
<i class="fas fa-tools"></i>
|
||||
Tool Execution Confirmation
|
||||
</div>
|
||||
<div class="risk-indicator ${riskClass}">
|
||||
<span class="risk-label">${(tool.riskLevel || 'medium').toUpperCase()} RISK</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="tool-info">
|
||||
<div class="tool-name">${tool.name}</div>
|
||||
<div class="tool-description">${tool.description}</div>
|
||||
</div>
|
||||
<div class="tool-arguments">
|
||||
<div class="arguments-label">Parameters:</div>
|
||||
<div class="arguments-content">
|
||||
${this.formatToolArguments(tool.arguments)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="confirmation-message">${request.message}</div>
|
||||
${this.createTimeoutIndicator(request.timeout)}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
${this.createActionButtons(request)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.attachButtonEvents(modal, request);
|
||||
return modal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create confirmation modal
|
||||
*/
|
||||
private createConfirmationModal(modal: HTMLElement, request: UserInteractionRequest): HTMLElement {
|
||||
modal.innerHTML = `
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">
|
||||
<i class="fas fa-question-circle"></i>
|
||||
${request.title}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="confirmation-message">${request.message}</div>
|
||||
${this.createTimeoutIndicator(request.timeout)}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
${this.createActionButtons(request)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.attachButtonEvents(modal, request);
|
||||
return modal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create choice modal
|
||||
*/
|
||||
private createChoiceModal(modal: HTMLElement, request: UserInteractionRequest): HTMLElement {
|
||||
modal.innerHTML = `
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">
|
||||
<i class="fas fa-list"></i>
|
||||
${request.title}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="choice-message">${request.message}</div>
|
||||
<div class="choice-options">
|
||||
${(request.options || []).map(option => `
|
||||
<div class="choice-option" data-option-id="${option.id}">
|
||||
<div class="option-label">${option.label}</div>
|
||||
${option.description ? `<div class="option-description">${option.description}</div>` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
${this.createTimeoutIndicator(request.timeout)}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary cancel-btn">Cancel</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.attachChoiceEvents(modal, request);
|
||||
return modal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create input modal
|
||||
*/
|
||||
private createInputModal(modal: HTMLElement, request: UserInteractionRequest): HTMLElement {
|
||||
modal.innerHTML = `
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">
|
||||
<i class="fas fa-edit"></i>
|
||||
${request.title}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="input-message">${request.message}</div>
|
||||
<div class="input-field">
|
||||
<input type="text" class="form-control" placeholder="Enter your response..."
|
||||
value="${request.defaultValue || ''}" autofocus>
|
||||
</div>
|
||||
${this.createTimeoutIndicator(request.timeout)}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary cancel-btn">Cancel</button>
|
||||
<button class="btn btn-primary submit-btn">Submit</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.attachInputEvents(modal, request);
|
||||
return modal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create generic modal
|
||||
*/
|
||||
private createGenericModal(modal: HTMLElement, request: UserInteractionRequest): HTMLElement {
|
||||
modal.innerHTML = `
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">${request.title}</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="generic-message">${request.message}</div>
|
||||
${this.createTimeoutIndicator(request.timeout)}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
${this.createActionButtons(request)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.attachButtonEvents(modal, request);
|
||||
return modal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format tool arguments for display
|
||||
*/
|
||||
private formatToolArguments(args: Record<string, unknown>): string {
|
||||
const formatted = Object.entries(args).map(([key, value]) => {
|
||||
let displayValue: string;
|
||||
if (typeof value === 'string') {
|
||||
displayValue = value.length > 100 ? value.substring(0, 100) + '...' : value;
|
||||
displayValue = `"${displayValue}"`;
|
||||
} else if (typeof value === 'object') {
|
||||
displayValue = JSON.stringify(value, null, 2);
|
||||
} else {
|
||||
displayValue = String(value);
|
||||
}
|
||||
|
||||
return `<div class="argument-item">
|
||||
<span class="argument-key">${key}:</span>
|
||||
<span class="argument-value">${displayValue}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
return formatted || '<div class="no-arguments">No parameters</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create action buttons based on request options
|
||||
*/
|
||||
private createActionButtons(request: UserInteractionRequest): string {
|
||||
if (request.options && request.options.length > 0) {
|
||||
return request.options.map(option => `
|
||||
<button class="btn btn-${option.style || 'secondary'} action-btn"
|
||||
data-action="${option.id}" data-response="${option.action || option.id}">
|
||||
${option.label}
|
||||
</button>
|
||||
`).join('');
|
||||
} else {
|
||||
// Default confirmation buttons
|
||||
return `
|
||||
<button class="btn btn-secondary cancel-btn" data-response="cancel">Cancel</button>
|
||||
<button class="btn btn-primary confirm-btn" data-response="confirm">Confirm</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create timeout indicator
|
||||
*/
|
||||
private createTimeoutIndicator(timeout?: number): string {
|
||||
if (!timeout || timeout <= 0) return '';
|
||||
|
||||
return `
|
||||
<div class="timeout-indicator">
|
||||
<div class="timeout-label">Auto-cancel in:</div>
|
||||
<div class="timeout-countdown" data-timeout="${timeout}">
|
||||
<div class="countdown-bar">
|
||||
<div class="countdown-fill"></div>
|
||||
</div>
|
||||
<div class="countdown-text">${Math.ceil(timeout / 1000)}s</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show modal
|
||||
*/
|
||||
private showModal(modal: HTMLElement): void {
|
||||
this.modalContainer.innerHTML = '';
|
||||
this.modalContainer.appendChild(modal);
|
||||
this.overlay.style.display = 'flex';
|
||||
|
||||
// Trigger animation
|
||||
setTimeout(() => {
|
||||
this.overlay.classList.add('show');
|
||||
modal.classList.add('show');
|
||||
}, 10);
|
||||
|
||||
// Start timeout countdown if present
|
||||
this.startTimeoutCountdown(modal);
|
||||
|
||||
// Focus first input if present
|
||||
const firstInput = modal.querySelector('input, button') as HTMLElement;
|
||||
if (firstInput) {
|
||||
firstInput.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide modal
|
||||
*/
|
||||
private hideModal(): void {
|
||||
this.overlay.classList.remove('show');
|
||||
const modal = this.modalContainer.querySelector('.llm-interaction-modal') as HTMLElement;
|
||||
if (modal) {
|
||||
modal.classList.remove('show');
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.overlay.style.display = 'none';
|
||||
this.modalContainer.innerHTML = '';
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach button events
|
||||
*/
|
||||
private attachButtonEvents(modal: HTMLElement, request: UserInteractionRequest): void {
|
||||
const buttons = modal.querySelectorAll('.action-btn, .confirm-btn, .cancel-btn');
|
||||
buttons.forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
const response = target.getAttribute('data-response') || 'cancel';
|
||||
this.respondToInteraction(request.id, response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach choice events
|
||||
*/
|
||||
private attachChoiceEvents(modal: HTMLElement, request: UserInteractionRequest): void {
|
||||
const options = modal.querySelectorAll('.choice-option');
|
||||
options.forEach(option => {
|
||||
option.addEventListener('click', (e) => {
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
const optionId = target.getAttribute('data-option-id');
|
||||
if (optionId) {
|
||||
this.respondToInteraction(request.id, optionId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Cancel button
|
||||
const cancelBtn = modal.querySelector('.cancel-btn');
|
||||
if (cancelBtn) {
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
this.respondToInteraction(request.id, 'cancel');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach input events
|
||||
*/
|
||||
private attachInputEvents(modal: HTMLElement, request: UserInteractionRequest): void {
|
||||
const input = modal.querySelector('input') as HTMLInputElement;
|
||||
const submitBtn = modal.querySelector('.submit-btn') as HTMLElement;
|
||||
const cancelBtn = modal.querySelector('.cancel-btn') as HTMLElement;
|
||||
|
||||
const submitValue = () => {
|
||||
const value = input.value.trim();
|
||||
this.respondToInteraction(request.id, 'submit', value);
|
||||
};
|
||||
|
||||
submitBtn.addEventListener('click', submitValue);
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
submitValue();
|
||||
}
|
||||
});
|
||||
|
||||
cancelBtn.addEventListener('click', () => {
|
||||
this.respondToInteraction(request.id, 'cancel');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start timeout countdown
|
||||
*/
|
||||
private startTimeoutCountdown(modal: HTMLElement): void {
|
||||
const countdown = modal.querySelector('.timeout-countdown') as HTMLElement;
|
||||
if (!countdown) return;
|
||||
|
||||
const timeout = parseInt(countdown.getAttribute('data-timeout') || '0');
|
||||
if (timeout <= 0) return;
|
||||
|
||||
const startTime = Date.now();
|
||||
const interval = setInterval(() => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
const remaining = Math.max(0, timeout - elapsed);
|
||||
const progress = (elapsed / timeout) * 100;
|
||||
|
||||
// Update countdown bar
|
||||
const fill = countdown.querySelector('.countdown-fill') as HTMLElement;
|
||||
if (fill) {
|
||||
fill.style.width = `${Math.min(100, progress)}%`;
|
||||
}
|
||||
|
||||
// Update countdown text
|
||||
const text = countdown.querySelector('.countdown-text') as HTMLElement;
|
||||
if (text) {
|
||||
text.textContent = `${Math.ceil(remaining / 1000)}s`;
|
||||
}
|
||||
|
||||
// Stop when timeout reached
|
||||
if (remaining <= 0) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// Store interval for cleanup
|
||||
countdown.setAttribute('data-interval', interval.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to interaction
|
||||
*/
|
||||
private respondToInteraction(id: string, response: string, value?: any): void {
|
||||
const callback = this.responseCallbacks.get(id);
|
||||
if (!callback) return;
|
||||
|
||||
const interactionResponse: UserInteractionResponse = {
|
||||
id,
|
||||
response,
|
||||
value,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
// Clean up
|
||||
this.activeInteractions.delete(id);
|
||||
this.responseCallbacks.delete(id);
|
||||
this.hideModal();
|
||||
|
||||
// Call callback
|
||||
callback(interactionResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle interaction timeout
|
||||
*/
|
||||
private handleTimeout(id: string): void {
|
||||
this.respondToInteraction(id, 'timeout');
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel all active interactions
|
||||
*/
|
||||
public cancelAllInteractions(): void {
|
||||
const activeIds = Array.from(this.activeInteractions.keys());
|
||||
activeIds.forEach(id => {
|
||||
this.respondToInteraction(id, 'cancel');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are active interactions
|
||||
*/
|
||||
public hasActiveInteractions(): boolean {
|
||||
return this.activeInteractions.size > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active interaction count
|
||||
*/
|
||||
public getActiveInteractionCount(): number {
|
||||
return this.activeInteractions.size;
|
||||
}
|
||||
}
|
||||
|
||||
// Export types for use in other modules
|
||||
export type { UserInteractionRequest, UserInteractionResponse };
|
||||
387
apps/client/src/widgets/llm_chat/progress_indicator.ts
Normal file
387
apps/client/src/widgets/llm_chat/progress_indicator.ts
Normal file
@@ -0,0 +1,387 @@
|
||||
interface ProgressStage {
|
||||
id: string;
|
||||
label: string;
|
||||
status: 'pending' | 'running' | 'completed' | 'failed';
|
||||
progress: number; // 0-100
|
||||
startTime?: number;
|
||||
endTime?: number;
|
||||
message?: string;
|
||||
estimatedDuration?: number;
|
||||
}
|
||||
|
||||
interface ProgressUpdate {
|
||||
stageId: string;
|
||||
progress: number;
|
||||
status: 'pending' | 'running' | 'completed' | 'failed';
|
||||
message?: string;
|
||||
estimatedTimeRemaining?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced Progress Indicator for LLM Chat Operations
|
||||
* Displays multi-stage progress with progress bars, timing, and status updates
|
||||
*/
|
||||
export class ProgressIndicator {
|
||||
private container: HTMLElement;
|
||||
private stages: Map<string, ProgressStage> = new Map();
|
||||
private overallProgress: number = 0;
|
||||
private isVisible: boolean = false;
|
||||
|
||||
constructor(parentElement: HTMLElement) {
|
||||
this.container = this.createProgressContainer();
|
||||
parentElement.appendChild(this.container);
|
||||
this.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the main progress container
|
||||
*/
|
||||
private createProgressContainer(): HTMLElement {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'llm-progress-container';
|
||||
container.innerHTML = `
|
||||
<div class="llm-progress-header">
|
||||
<div class="llm-progress-title">Processing...</div>
|
||||
<div class="llm-progress-overall">
|
||||
<div class="llm-progress-bar-container">
|
||||
<div class="llm-progress-bar-fill" style="width: 0%"></div>
|
||||
</div>
|
||||
<div class="llm-progress-percentage">0%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="llm-progress-stages"></div>
|
||||
<div class="llm-progress-footer">
|
||||
<div class="llm-progress-time-info">
|
||||
<span class="elapsed-time">Elapsed: 0s</span>
|
||||
<span class="estimated-remaining">Est. remaining: --</span>
|
||||
</div>
|
||||
<button class="llm-progress-cancel-btn" title="Cancel operation">
|
||||
<i class="fas fa-times"></i> Cancel
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the progress indicator
|
||||
*/
|
||||
public show(): void {
|
||||
if (!this.isVisible) {
|
||||
this.container.style.display = 'block';
|
||||
this.container.classList.add('fade-in');
|
||||
this.isVisible = true;
|
||||
this.startElapsedTimer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the progress indicator
|
||||
*/
|
||||
public hide(): void {
|
||||
if (this.isVisible) {
|
||||
this.container.classList.add('fade-out');
|
||||
setTimeout(() => {
|
||||
this.container.style.display = 'none';
|
||||
this.container.classList.remove('fade-in', 'fade-out');
|
||||
this.isVisible = false;
|
||||
this.stopElapsedTimer();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new progress stage
|
||||
*/
|
||||
public addStage(stageId: string, label: string, estimatedDuration?: number): void {
|
||||
const stage: ProgressStage = {
|
||||
id: stageId,
|
||||
label,
|
||||
status: 'pending',
|
||||
progress: 0,
|
||||
estimatedDuration
|
||||
};
|
||||
|
||||
this.stages.set(stageId, stage);
|
||||
this.renderStage(stage);
|
||||
this.updateOverallProgress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update progress for a specific stage
|
||||
*/
|
||||
public updateStageProgress(update: ProgressUpdate): void {
|
||||
const stage = this.stages.get(update.stageId);
|
||||
if (!stage) return;
|
||||
|
||||
// Update stage data
|
||||
stage.progress = Math.max(0, Math.min(100, update.progress));
|
||||
stage.status = update.status;
|
||||
stage.message = update.message;
|
||||
|
||||
// Set timing
|
||||
if (update.status === 'running' && !stage.startTime) {
|
||||
stage.startTime = Date.now();
|
||||
} else if ((update.status === 'completed' || update.status === 'failed') && stage.startTime && !stage.endTime) {
|
||||
stage.endTime = Date.now();
|
||||
}
|
||||
|
||||
this.renderStage(stage);
|
||||
this.updateOverallProgress();
|
||||
|
||||
if (update.estimatedTimeRemaining !== undefined) {
|
||||
this.updateEstimatedTime(update.estimatedTimeRemaining);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a stage as completed
|
||||
*/
|
||||
public completeStage(stageId: string): void {
|
||||
this.updateStageProgress({
|
||||
stageId,
|
||||
progress: 100,
|
||||
status: 'completed',
|
||||
message: 'Completed'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a stage as failed
|
||||
*/
|
||||
public failStage(stageId: string, message?: string): void {
|
||||
this.updateStageProgress({
|
||||
stageId,
|
||||
progress: 0,
|
||||
status: 'failed',
|
||||
message: message || 'Failed'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a specific stage
|
||||
*/
|
||||
private renderStage(stage: ProgressStage): void {
|
||||
const stagesContainer = this.container.querySelector('.llm-progress-stages') as HTMLElement;
|
||||
let stageElement = stagesContainer.querySelector(`[data-stage-id="${stage.id}"]`) as HTMLElement;
|
||||
|
||||
if (!stageElement) {
|
||||
stageElement = this.createStageElement(stage);
|
||||
stagesContainer.appendChild(stageElement);
|
||||
}
|
||||
|
||||
this.updateStageElement(stageElement, stage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new stage element
|
||||
*/
|
||||
private createStageElement(stage: ProgressStage): HTMLElement {
|
||||
const element = document.createElement('div');
|
||||
element.className = 'llm-progress-stage';
|
||||
element.setAttribute('data-stage-id', stage.id);
|
||||
|
||||
element.innerHTML = `
|
||||
<div class="stage-header">
|
||||
<div class="stage-status-icon">
|
||||
<i class="fas fa-circle"></i>
|
||||
</div>
|
||||
<div class="stage-label">${stage.label}</div>
|
||||
<div class="stage-timing"></div>
|
||||
</div>
|
||||
<div class="stage-progress">
|
||||
<div class="stage-progress-bar">
|
||||
<div class="stage-progress-fill"></div>
|
||||
</div>
|
||||
<div class="stage-progress-text">0%</div>
|
||||
</div>
|
||||
<div class="stage-message"></div>
|
||||
`;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update stage element with current data
|
||||
*/
|
||||
private updateStageElement(element: HTMLElement, stage: ProgressStage): void {
|
||||
// Update status icon
|
||||
const icon = element.querySelector('.stage-status-icon i') as HTMLElement;
|
||||
icon.className = this.getStatusIcon(stage.status);
|
||||
|
||||
// Update progress bar
|
||||
const progressFill = element.querySelector('.stage-progress-fill') as HTMLElement;
|
||||
progressFill.style.width = `${stage.progress}%`;
|
||||
|
||||
// Update progress text
|
||||
const progressText = element.querySelector('.stage-progress-text') as HTMLElement;
|
||||
progressText.textContent = `${Math.round(stage.progress)}%`;
|
||||
|
||||
// Update message
|
||||
const messageElement = element.querySelector('.stage-message') as HTMLElement;
|
||||
messageElement.textContent = stage.message || '';
|
||||
messageElement.style.display = stage.message ? 'block' : 'none';
|
||||
|
||||
// Update timing
|
||||
const timingElement = element.querySelector('.stage-timing') as HTMLElement;
|
||||
timingElement.textContent = this.getStageTimingText(stage);
|
||||
|
||||
// Update stage status class
|
||||
element.className = `llm-progress-stage stage-${stage.status}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status icon for stage
|
||||
*/
|
||||
private getStatusIcon(status: string): string {
|
||||
switch (status) {
|
||||
case 'pending': return 'fas fa-circle text-muted';
|
||||
case 'running': return 'fas fa-spinner fa-spin text-primary';
|
||||
case 'completed': return 'fas fa-check-circle text-success';
|
||||
case 'failed': return 'fas fa-exclamation-circle text-danger';
|
||||
default: return 'fas fa-circle';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timing text for stage
|
||||
*/
|
||||
private getStageTimingText(stage: ProgressStage): string {
|
||||
if (stage.endTime && stage.startTime) {
|
||||
const duration = Math.round((stage.endTime - stage.startTime) / 1000);
|
||||
return `${duration}s`;
|
||||
} else if (stage.startTime) {
|
||||
const elapsed = Math.round((Date.now() - stage.startTime) / 1000);
|
||||
return `${elapsed}s`;
|
||||
} else if (stage.estimatedDuration) {
|
||||
return `~${stage.estimatedDuration / 1000}s`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Update overall progress
|
||||
*/
|
||||
private updateOverallProgress(): void {
|
||||
if (this.stages.size === 0) {
|
||||
this.overallProgress = 0;
|
||||
} else {
|
||||
const totalProgress = Array.from(this.stages.values())
|
||||
.reduce((sum, stage) => sum + stage.progress, 0);
|
||||
this.overallProgress = totalProgress / this.stages.size;
|
||||
}
|
||||
|
||||
// Update overall progress bar
|
||||
const overallFill = this.container.querySelector('.llm-progress-bar-fill') as HTMLElement;
|
||||
overallFill.style.width = `${this.overallProgress}%`;
|
||||
|
||||
// Update percentage text
|
||||
const percentageText = this.container.querySelector('.llm-progress-percentage') as HTMLElement;
|
||||
percentageText.textContent = `${Math.round(this.overallProgress)}%`;
|
||||
|
||||
// Update title based on progress
|
||||
const titleElement = this.container.querySelector('.llm-progress-title') as HTMLElement;
|
||||
if (this.overallProgress >= 100) {
|
||||
titleElement.textContent = 'Completed';
|
||||
} else if (this.overallProgress > 0) {
|
||||
titleElement.textContent = 'Processing...';
|
||||
} else {
|
||||
titleElement.textContent = 'Starting...';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update estimated remaining time
|
||||
*/
|
||||
private updateEstimatedTime(seconds: number): void {
|
||||
const estimatedElement = this.container.querySelector('.estimated-remaining') as HTMLElement;
|
||||
if (seconds > 0) {
|
||||
estimatedElement.textContent = `Est. remaining: ${this.formatTime(seconds)}`;
|
||||
} else {
|
||||
estimatedElement.textContent = 'Est. remaining: --';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format time in seconds to readable format
|
||||
*/
|
||||
private formatTime(seconds: number): string {
|
||||
if (seconds < 60) {
|
||||
return `${Math.round(seconds)}s`;
|
||||
} else if (seconds < 3600) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = Math.round(seconds % 60);
|
||||
return `${minutes}m ${remainingSeconds}s`;
|
||||
} else {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
return `${hours}h ${minutes}m`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start elapsed time timer
|
||||
*/
|
||||
private elapsedTimer?: number;
|
||||
private startTime: number = Date.now();
|
||||
|
||||
private startElapsedTimer(): void {
|
||||
this.startTime = Date.now();
|
||||
this.elapsedTimer = window.setInterval(() => {
|
||||
const elapsed = Math.round((Date.now() - this.startTime) / 1000);
|
||||
const elapsedElement = this.container.querySelector('.elapsed-time') as HTMLElement;
|
||||
elapsedElement.textContent = `Elapsed: ${this.formatTime(elapsed)}`;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop elapsed time timer
|
||||
*/
|
||||
private stopElapsedTimer(): void {
|
||||
if (this.elapsedTimer) {
|
||||
clearInterval(this.elapsedTimer);
|
||||
this.elapsedTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all stages and reset
|
||||
*/
|
||||
public reset(): void {
|
||||
this.stages.clear();
|
||||
const stagesContainer = this.container.querySelector('.llm-progress-stages') as HTMLElement;
|
||||
stagesContainer.innerHTML = '';
|
||||
this.overallProgress = 0;
|
||||
this.updateOverallProgress();
|
||||
this.stopElapsedTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cancel callback
|
||||
*/
|
||||
public onCancel(callback: () => void): void {
|
||||
const cancelBtn = this.container.querySelector('.llm-progress-cancel-btn') as HTMLElement;
|
||||
cancelBtn.onclick = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable cancel button
|
||||
*/
|
||||
public disableCancel(): void {
|
||||
const cancelBtn = this.container.querySelector('.llm-progress-cancel-btn') as HTMLButtonElement;
|
||||
cancelBtn.disabled = true;
|
||||
cancelBtn.style.opacity = '0.5';
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable cancel button
|
||||
*/
|
||||
public enableCancel(): void {
|
||||
const cancelBtn = this.container.querySelector('.llm-progress-cancel-btn') as HTMLButtonElement;
|
||||
cancelBtn.disabled = false;
|
||||
cancelBtn.style.opacity = '1';
|
||||
}
|
||||
}
|
||||
|
||||
// Export types for use in other modules
|
||||
export type { ProgressStage, ProgressUpdate };
|
||||
333
apps/client/src/widgets/llm_chat/tool_enhanced_ui.css
Normal file
333
apps/client/src/widgets/llm_chat/tool_enhanced_ui.css
Normal file
@@ -0,0 +1,333 @@
|
||||
/**
|
||||
* Enhanced Tool UI Styles
|
||||
* Styles for tool preview, feedback, and error recovery UI components
|
||||
*/
|
||||
|
||||
/* Tool Preview Styles */
|
||||
.tool-preview-container {
|
||||
animation: slideIn 0.3s ease-out;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tool-preview-container.fade-out {
|
||||
animation: fadeOut 0.3s ease-out;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.tool-preview-header {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.tool-preview-item {
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tool-preview-item:hover {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.tool-preview-item input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tool-preview-item .parameter-item {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.tool-preview-item .parameter-key {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tool-preview-item details summary {
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tool-preview-item details summary:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.tool-preview-actions button {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
/* Tool Feedback Styles */
|
||||
.tool-execution-feedback {
|
||||
animation: slideIn 0.3s ease-out;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tool-execution-feedback.fade-out {
|
||||
animation: fadeOut 0.3s ease-out;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.tool-execution-feedback.border-success {
|
||||
border-color: var(--bs-success) !important;
|
||||
background-color: rgba(25, 135, 84, 0.05) !important;
|
||||
}
|
||||
|
||||
.tool-execution-feedback.border-danger {
|
||||
border-color: var(--bs-danger) !important;
|
||||
background-color: rgba(220, 53, 69, 0.05) !important;
|
||||
}
|
||||
|
||||
.tool-execution-feedback.border-warning {
|
||||
border-color: var(--bs-warning) !important;
|
||||
background-color: rgba(255, 193, 7, 0.05) !important;
|
||||
}
|
||||
|
||||
.tool-execution-feedback .progress {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.tool-execution-feedback .progress-bar {
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.tool-execution-feedback .tool-steps {
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
padding-top: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.tool-execution-feedback .tool-step {
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.tool-execution-feedback .tool-step.tool-step-error {
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
}
|
||||
|
||||
.tool-execution-feedback .tool-step.tool-step-warning {
|
||||
background-color: rgba(255, 193, 7, 0.1);
|
||||
}
|
||||
|
||||
.tool-execution-feedback .tool-step.tool-step-progress {
|
||||
background-color: rgba(13, 110, 253, 0.1);
|
||||
}
|
||||
|
||||
.tool-execution-feedback .cancel-btn {
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.tool-execution-feedback .cancel-btn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Real-time Progress Indicator */
|
||||
.tool-progress-realtime {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tool-progress-realtime::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.2),
|
||||
transparent
|
||||
);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
/* Tool Execution History */
|
||||
.tool-history-container {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding: 0.5rem;
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tool-history-container .history-item {
|
||||
padding: 2px 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.tool-history-container .history-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* Tool Statistics */
|
||||
.tool-stats-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tool-stat-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tool-stat-value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: var(--bs-primary);
|
||||
}
|
||||
|
||||
.tool-stat-label {
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--bs-secondary);
|
||||
}
|
||||
|
||||
/* Error Recovery UI */
|
||||
.tool-error-recovery {
|
||||
background-color: rgba(220, 53, 69, 0.05);
|
||||
border: 1px solid var(--bs-danger);
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.tool-error-recovery .error-message {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tool-error-recovery .error-suggestions {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.tool-error-recovery .error-suggestions li {
|
||||
padding: 0.25rem 0;
|
||||
padding-left: 1.5rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tool-error-recovery .error-suggestions li::before {
|
||||
content: '→';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--bs-warning);
|
||||
}
|
||||
|
||||
.tool-recovery-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.tool-recovery-actions button {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* Circuit Breaker Indicator */
|
||||
.circuit-breaker-status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.circuit-breaker-status.status-closed {
|
||||
background-color: rgba(25, 135, 84, 0.1);
|
||||
color: var(--bs-success);
|
||||
}
|
||||
|
||||
.circuit-breaker-status.status-open {
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
color: var(--bs-danger);
|
||||
}
|
||||
|
||||
.circuit-breaker-status.status-half-open {
|
||||
background-color: rgba(255, 193, 7, 0.1);
|
||||
color: var(--bs-warning);
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
to {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Spinner Override for Tool Execution */
|
||||
.tool-execution-feedback .spinner-border-sm {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-width: 0.15em;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.tool-preview-container {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.tool-preview-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tool-preview-actions button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tool-stats-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark Mode Support */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.tool-preview-container,
|
||||
.tool-execution-feedback {
|
||||
background-color: rgba(255, 255, 255, 0.05) !important;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.tool-preview-item {
|
||||
background-color: rgba(255, 255, 255, 0.03) !important;
|
||||
}
|
||||
|
||||
.tool-history-container,
|
||||
.tool-stats-container {
|
||||
background-color: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.parameter-item {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
309
apps/client/src/widgets/llm_chat/tool_execution_ui.ts
Normal file
309
apps/client/src/widgets/llm_chat/tool_execution_ui.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
/**
|
||||
* Tool Execution UI Components
|
||||
*
|
||||
* This module provides enhanced UI components for displaying tool execution status,
|
||||
* progress, and user-friendly error messages during LLM tool calls.
|
||||
*/
|
||||
|
||||
import { t } from "../../services/i18n.js";
|
||||
|
||||
/**
|
||||
* Tool execution status types
|
||||
*/
|
||||
export type ToolExecutionStatus = 'pending' | 'running' | 'success' | 'error' | 'cancelled';
|
||||
|
||||
/**
|
||||
* Tool execution display data
|
||||
*/
|
||||
export interface ToolExecutionDisplay {
|
||||
toolName: string;
|
||||
displayName: string;
|
||||
status: ToolExecutionStatus;
|
||||
description?: string;
|
||||
progress?: {
|
||||
current: number;
|
||||
total: number;
|
||||
message?: string;
|
||||
};
|
||||
result?: string;
|
||||
error?: string;
|
||||
startTime?: number;
|
||||
endTime?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of tool names to user-friendly display names
|
||||
*/
|
||||
const TOOL_DISPLAY_NAMES: Record<string, string> = {
|
||||
'search_notes': 'Searching Notes',
|
||||
'get_note_content': 'Reading Note',
|
||||
'create_note': 'Creating Note',
|
||||
'update_note': 'Updating Note',
|
||||
'execute_code': 'Running Code',
|
||||
'web_search': 'Searching Web',
|
||||
'get_note_attributes': 'Reading Note Properties',
|
||||
'set_note_attribute': 'Setting Note Property',
|
||||
'navigate_notes': 'Navigating Notes',
|
||||
'query_decomposition': 'Analyzing Query',
|
||||
'contextual_thinking': 'Processing Context'
|
||||
};
|
||||
|
||||
/**
|
||||
* Map of tool names to descriptions
|
||||
*/
|
||||
const TOOL_DESCRIPTIONS: Record<string, string> = {
|
||||
'search_notes': 'Finding relevant notes in your knowledge base',
|
||||
'get_note_content': 'Reading the content of a specific note',
|
||||
'create_note': 'Creating a new note with the provided content',
|
||||
'update_note': 'Updating an existing note',
|
||||
'execute_code': 'Running code in a safe environment',
|
||||
'web_search': 'Searching the web for current information',
|
||||
'get_note_attributes': 'Reading note metadata and properties',
|
||||
'set_note_attribute': 'Updating note metadata',
|
||||
'navigate_notes': 'Exploring the note hierarchy',
|
||||
'query_decomposition': 'Breaking down complex queries',
|
||||
'contextual_thinking': 'Analyzing context for better understanding'
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a tool execution indicator element
|
||||
*/
|
||||
export function createToolExecutionIndicator(toolName: string): HTMLElement {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'tool-execution-indicator mb-2 p-2 border rounded bg-light';
|
||||
container.dataset.toolName = toolName;
|
||||
|
||||
const displayName = TOOL_DISPLAY_NAMES[toolName] || toolName;
|
||||
const description = TOOL_DESCRIPTIONS[toolName] || '';
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="tool-status-icon me-2">
|
||||
<div class="spinner-border spinner-border-sm text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<div class="tool-name fw-bold small">${displayName}</div>
|
||||
${description ? `<div class="tool-description text-muted small">${description}</div>` : ''}
|
||||
<div class="tool-progress" style="display: none;">
|
||||
<div class="progress mt-1" style="height: 4px;">
|
||||
<div class="progress-bar" role="progressbar" style="width: 0%"></div>
|
||||
</div>
|
||||
<div class="progress-message text-muted small mt-1"></div>
|
||||
</div>
|
||||
<div class="tool-result text-success small mt-1" style="display: none;"></div>
|
||||
<div class="tool-error text-danger small mt-1" style="display: none;"></div>
|
||||
</div>
|
||||
<div class="tool-duration text-muted small ms-2" style="display: none;"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tool execution status
|
||||
*/
|
||||
export function updateToolExecutionStatus(
|
||||
container: HTMLElement,
|
||||
status: ToolExecutionStatus,
|
||||
data?: {
|
||||
progress?: { current: number; total: number; message?: string };
|
||||
result?: string;
|
||||
error?: string;
|
||||
duration?: number;
|
||||
}
|
||||
): void {
|
||||
const statusIcon = container.querySelector('.tool-status-icon');
|
||||
const progressDiv = container.querySelector('.tool-progress') as HTMLElement;
|
||||
const progressBar = container.querySelector('.progress-bar') as HTMLElement;
|
||||
const progressMessage = container.querySelector('.progress-message') as HTMLElement;
|
||||
const resultDiv = container.querySelector('.tool-result') as HTMLElement;
|
||||
const errorDiv = container.querySelector('.tool-error') as HTMLElement;
|
||||
const durationDiv = container.querySelector('.tool-duration') as HTMLElement;
|
||||
|
||||
if (!statusIcon) return;
|
||||
|
||||
// Update status icon
|
||||
switch (status) {
|
||||
case 'pending':
|
||||
statusIcon.innerHTML = `
|
||||
<div class="spinner-border spinner-border-sm text-secondary" role="status">
|
||||
<span class="visually-hidden">Pending...</span>
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
|
||||
case 'running':
|
||||
statusIcon.innerHTML = `
|
||||
<div class="spinner-border spinner-border-sm text-primary" role="status">
|
||||
<span class="visually-hidden">Running...</span>
|
||||
</div>
|
||||
`;
|
||||
break;
|
||||
|
||||
case 'success':
|
||||
statusIcon.innerHTML = '<i class="bx bx-check-circle text-success fs-5"></i>';
|
||||
container.classList.add('border-success', 'bg-success-subtle');
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
statusIcon.innerHTML = '<i class="bx bx-error-circle text-danger fs-5"></i>';
|
||||
container.classList.add('border-danger', 'bg-danger-subtle');
|
||||
break;
|
||||
|
||||
case 'cancelled':
|
||||
statusIcon.innerHTML = '<i class="bx bx-x-circle text-warning fs-5"></i>';
|
||||
container.classList.add('border-warning', 'bg-warning-subtle');
|
||||
break;
|
||||
}
|
||||
|
||||
// Update progress if provided
|
||||
if (data?.progress && progressDiv && progressBar && progressMessage) {
|
||||
progressDiv.style.display = 'block';
|
||||
const percentage = (data.progress.current / data.progress.total) * 100;
|
||||
progressBar.style.width = `${percentage}%`;
|
||||
if (data.progress.message) {
|
||||
progressMessage.textContent = data.progress.message;
|
||||
}
|
||||
}
|
||||
|
||||
// Update result if provided
|
||||
if (data?.result && resultDiv) {
|
||||
resultDiv.style.display = 'block';
|
||||
resultDiv.textContent = data.result;
|
||||
}
|
||||
|
||||
// Update error if provided
|
||||
if (data?.error && errorDiv) {
|
||||
errorDiv.style.display = 'block';
|
||||
errorDiv.textContent = formatErrorMessage(data.error);
|
||||
}
|
||||
|
||||
// Update duration if provided
|
||||
if (data?.duration && durationDiv) {
|
||||
durationDiv.style.display = 'block';
|
||||
durationDiv.textContent = formatDuration(data.duration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format error messages to be user-friendly
|
||||
*/
|
||||
function formatErrorMessage(error: string): string {
|
||||
// Remove technical details and provide user-friendly messages
|
||||
const errorMappings: Record<string, string> = {
|
||||
'ECONNREFUSED': 'Connection refused. Please check if the service is running.',
|
||||
'ETIMEDOUT': 'Request timed out. Please try again.',
|
||||
'ENOTFOUND': 'Service not found. Please check your configuration.',
|
||||
'401': 'Authentication failed. Please check your API credentials.',
|
||||
'403': 'Access denied. Please check your permissions.',
|
||||
'404': 'Resource not found.',
|
||||
'429': 'Rate limit exceeded. Please wait a moment and try again.',
|
||||
'500': 'Server error. Please try again later.',
|
||||
'503': 'Service temporarily unavailable. Please try again later.'
|
||||
};
|
||||
|
||||
for (const [key, message] of Object.entries(errorMappings)) {
|
||||
if (error.includes(key)) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
// Generic error formatting
|
||||
if (error.length > 100) {
|
||||
return error.substring(0, 100) + '...';
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format duration in a human-readable way
|
||||
*/
|
||||
function formatDuration(milliseconds: number): string {
|
||||
if (milliseconds < 1000) {
|
||||
return `${milliseconds}ms`;
|
||||
} else if (milliseconds < 60000) {
|
||||
return `${(milliseconds / 1000).toFixed(1)}s`;
|
||||
} else {
|
||||
const minutes = Math.floor(milliseconds / 60000);
|
||||
const seconds = Math.floor((milliseconds % 60000) / 1000);
|
||||
return `${minutes}m ${seconds}s`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tool execution summary
|
||||
*/
|
||||
export function createToolExecutionSummary(executions: ToolExecutionDisplay[]): HTMLElement {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'tool-execution-summary mt-2 p-2 border rounded bg-light small';
|
||||
|
||||
const successful = executions.filter(e => e.status === 'success').length;
|
||||
const failed = executions.filter(e => e.status === 'error').length;
|
||||
const total = executions.length;
|
||||
|
||||
const totalDuration = executions.reduce((sum, e) => {
|
||||
if (e.startTime && e.endTime) {
|
||||
return sum + (e.endTime - e.startTime);
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<i class="bx bx-check-shield me-1"></i>
|
||||
<span class="fw-bold">Tools Executed:</span>
|
||||
<span class="badge bg-success ms-1">${successful} successful</span>
|
||||
${failed > 0 ? `<span class="badge bg-danger ms-1">${failed} failed</span>` : ''}
|
||||
<span class="badge bg-secondary ms-1">${total} total</span>
|
||||
</div>
|
||||
${totalDuration > 0 ? `
|
||||
<div class="text-muted">
|
||||
<i class="bx bx-time me-1"></i>
|
||||
${formatDuration(totalDuration)}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a loading indicator with custom message
|
||||
*/
|
||||
export function createLoadingIndicator(message: string = 'Processing...'): HTMLElement {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'loading-indicator-enhanced d-flex align-items-center p-2';
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="spinner-grow spinner-grow-sm text-primary me-2" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<span class="loading-message">${message}</span>
|
||||
`;
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update loading indicator message
|
||||
*/
|
||||
export function updateLoadingMessage(container: HTMLElement, message: string): void {
|
||||
const messageElement = container.querySelector('.loading-message');
|
||||
if (messageElement) {
|
||||
messageElement.textContent = message;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
createToolExecutionIndicator,
|
||||
updateToolExecutionStatus,
|
||||
createToolExecutionSummary,
|
||||
createLoadingIndicator,
|
||||
updateLoadingMessage
|
||||
};
|
||||
599
apps/client/src/widgets/llm_chat/tool_feedback_ui.ts
Normal file
599
apps/client/src/widgets/llm_chat/tool_feedback_ui.ts
Normal file
@@ -0,0 +1,599 @@
|
||||
/**
|
||||
* Tool Feedback UI Component
|
||||
*
|
||||
* Provides real-time feedback UI during tool execution including
|
||||
* progress tracking, step visualization, and execution history.
|
||||
*/
|
||||
|
||||
import { t } from "../../services/i18n.js";
|
||||
import { VirtualScrollManager, createVirtualScroll } from './virtual_scroll.js';
|
||||
|
||||
// UI Constants
|
||||
const UI_CONSTANTS = {
|
||||
HISTORY_MOVE_DELAY: 5000,
|
||||
STEP_COLLAPSE_DELAY: 1000,
|
||||
FADE_OUT_DURATION: 300,
|
||||
MAX_HISTORY_UI_SIZE: 50,
|
||||
MAX_VISIBLE_STEPS: 3,
|
||||
MAX_STRING_DISPLAY_LENGTH: 100,
|
||||
MAX_STEP_CONTAINER_HEIGHT: 150,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Tool execution status
|
||||
*/
|
||||
export type ToolExecutionStatus = 'pending' | 'running' | 'success' | 'error' | 'cancelled' | 'timeout';
|
||||
|
||||
/**
|
||||
* Tool execution progress data
|
||||
*/
|
||||
export interface ToolProgressData {
|
||||
executionId: string;
|
||||
current: number;
|
||||
total: number;
|
||||
percentage: number;
|
||||
message?: string;
|
||||
estimatedTimeRemaining?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool execution step data
|
||||
*/
|
||||
export interface ToolStepData {
|
||||
executionId: string;
|
||||
timestamp: string;
|
||||
message: string;
|
||||
type: 'info' | 'warning' | 'error' | 'progress';
|
||||
data?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool execution tracker
|
||||
*/
|
||||
interface ExecutionTracker {
|
||||
id: string;
|
||||
toolName: string;
|
||||
element: HTMLElement;
|
||||
startTime: number;
|
||||
status: ToolExecutionStatus;
|
||||
steps: ToolStepData[];
|
||||
animationFrameId?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool Feedback UI Manager
|
||||
*/
|
||||
export class ToolFeedbackUI {
|
||||
private container: HTMLElement;
|
||||
private executions: Map<string, ExecutionTracker> = new Map();
|
||||
private historyContainer?: HTMLElement;
|
||||
private statsContainer?: HTMLElement;
|
||||
private virtualScroll?: VirtualScrollManager;
|
||||
private historyItems: any[] = [];
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start tracking a tool execution
|
||||
*/
|
||||
public startExecution(
|
||||
executionId: string,
|
||||
toolName: string,
|
||||
displayName?: string
|
||||
): void {
|
||||
// Create execution element
|
||||
const element = this.createExecutionElement(executionId, toolName, displayName);
|
||||
this.container.appendChild(element);
|
||||
|
||||
// Create tracker
|
||||
const tracker: ExecutionTracker = {
|
||||
id: executionId,
|
||||
toolName,
|
||||
element,
|
||||
startTime: Date.now(),
|
||||
status: 'running',
|
||||
steps: []
|
||||
};
|
||||
|
||||
// Start elapsed time update with requestAnimationFrame
|
||||
this.startElapsedTimeAnimation(tracker);
|
||||
|
||||
this.executions.set(executionId, tracker);
|
||||
|
||||
// Auto-scroll to new execution
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Update execution progress
|
||||
*/
|
||||
public updateProgress(data: ToolProgressData): void {
|
||||
const tracker = this.executions.get(data.executionId);
|
||||
if (!tracker) return;
|
||||
|
||||
const progressBar = tracker.element.querySelector('.progress-bar') as HTMLElement;
|
||||
const progressText = tracker.element.querySelector('.progress-text') as HTMLElement;
|
||||
const progressContainer = tracker.element.querySelector('.tool-progress') as HTMLElement;
|
||||
|
||||
if (progressContainer) {
|
||||
progressContainer.style.display = 'block';
|
||||
}
|
||||
|
||||
if (progressBar) {
|
||||
progressBar.style.width = `${data.percentage}%`;
|
||||
progressBar.setAttribute('aria-valuenow', String(data.percentage));
|
||||
}
|
||||
|
||||
if (progressText) {
|
||||
let text = `${data.current}/${data.total}`;
|
||||
if (data.message) {
|
||||
text += ` - ${data.message}`;
|
||||
}
|
||||
if (data.estimatedTimeRemaining) {
|
||||
text += ` (${this.formatDuration(data.estimatedTimeRemaining)} remaining)`;
|
||||
}
|
||||
progressText.textContent = text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add execution step
|
||||
*/
|
||||
public addStep(data: ToolStepData): void {
|
||||
const tracker = this.executions.get(data.executionId);
|
||||
if (!tracker) return;
|
||||
|
||||
tracker.steps.push(data);
|
||||
|
||||
const stepsContainer = tracker.element.querySelector('.tool-steps') as HTMLElement;
|
||||
if (stepsContainer) {
|
||||
const stepElement = this.createStepElement(data);
|
||||
stepsContainer.appendChild(stepElement);
|
||||
|
||||
// Show steps container if hidden
|
||||
stepsContainer.style.display = 'block';
|
||||
|
||||
// Auto-scroll steps
|
||||
stepsContainer.scrollTop = stepsContainer.scrollHeight;
|
||||
}
|
||||
|
||||
// Update status indicator for warnings/errors
|
||||
if (data.type === 'warning' || data.type === 'error') {
|
||||
this.updateStatusIndicator(tracker, data.type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete execution
|
||||
*/
|
||||
public completeExecution(
|
||||
executionId: string,
|
||||
status: 'success' | 'error' | 'cancelled' | 'timeout',
|
||||
result?: any,
|
||||
error?: string
|
||||
): void {
|
||||
const tracker = this.executions.get(executionId);
|
||||
if (!tracker) return;
|
||||
|
||||
tracker.status = status;
|
||||
|
||||
// Stop elapsed time update
|
||||
if (tracker.animationFrameId) {
|
||||
cancelAnimationFrame(tracker.animationFrameId);
|
||||
tracker.animationFrameId = undefined;
|
||||
}
|
||||
|
||||
// Update UI
|
||||
this.updateStatusIndicator(tracker, status);
|
||||
|
||||
const duration = Date.now() - tracker.startTime;
|
||||
const durationElement = tracker.element.querySelector('.tool-duration') as HTMLElement;
|
||||
if (durationElement) {
|
||||
durationElement.textContent = this.formatDuration(duration);
|
||||
}
|
||||
|
||||
// Show result or error
|
||||
if (status === 'success' && result) {
|
||||
const resultElement = tracker.element.querySelector('.tool-result') as HTMLElement;
|
||||
if (resultElement) {
|
||||
resultElement.style.display = 'block';
|
||||
resultElement.textContent = this.formatResult(result);
|
||||
}
|
||||
} else if ((status === 'error' || status === 'timeout') && error) {
|
||||
const errorElement = tracker.element.querySelector('.tool-error') as HTMLElement;
|
||||
if (errorElement) {
|
||||
errorElement.style.display = 'block';
|
||||
errorElement.textContent = error;
|
||||
}
|
||||
}
|
||||
|
||||
// Collapse steps after completion
|
||||
setTimeout(() => {
|
||||
this.collapseStepsIfNeeded(tracker);
|
||||
}, UI_CONSTANTS.STEP_COLLAPSE_DELAY);
|
||||
|
||||
// Move to history after a delay
|
||||
setTimeout(() => {
|
||||
this.moveToHistory(tracker);
|
||||
}, UI_CONSTANTS.HISTORY_MOVE_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel execution
|
||||
*/
|
||||
public cancelExecution(executionId: string): void {
|
||||
this.completeExecution(executionId, 'cancelled', undefined, 'Cancelled by user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create execution element
|
||||
*/
|
||||
private createExecutionElement(
|
||||
executionId: string,
|
||||
toolName: string,
|
||||
displayName?: string
|
||||
): HTMLElement {
|
||||
const element = document.createElement('div');
|
||||
element.className = 'tool-execution-feedback mb-2 p-2 border rounded bg-light';
|
||||
element.dataset.executionId = executionId;
|
||||
|
||||
element.innerHTML = `
|
||||
<div class="d-flex align-items-start">
|
||||
<div class="tool-status-icon me-2">
|
||||
<div class="spinner-border spinner-border-sm text-primary" role="status">
|
||||
<span class="visually-hidden">Running...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="tool-name fw-bold small">
|
||||
${displayName || toolName}
|
||||
</div>
|
||||
<div class="tool-actions">
|
||||
<button class="btn btn-sm btn-link p-0 cancel-btn" title="Cancel">
|
||||
<i class="bx bx-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tool-progress mt-1" style="display: none;">
|
||||
<div class="progress" style="height: 4px;">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
||||
role="progressbar"
|
||||
style="width: 0%"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-text text-muted small mt-1"></div>
|
||||
</div>
|
||||
<div class="tool-steps mt-2 small" style="display: none; max-height: ${UI_CONSTANTS.MAX_STEP_CONTAINER_HEIGHT}px; overflow-y: auto;">
|
||||
</div>
|
||||
<div class="tool-result text-success small mt-2" style="display: none;"></div>
|
||||
<div class="tool-error text-danger small mt-2" style="display: none;"></div>
|
||||
</div>
|
||||
<div class="tool-duration text-muted small ms-2">
|
||||
<span class="elapsed-time">0s</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add cancel button listener
|
||||
const cancelBtn = element.querySelector('.cancel-btn') as HTMLButtonElement;
|
||||
cancelBtn?.addEventListener('click', () => {
|
||||
this.cancelExecution(executionId);
|
||||
});
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create step element
|
||||
*/
|
||||
private createStepElement(step: ToolStepData): HTMLElement {
|
||||
const element = document.createElement('div');
|
||||
element.className = `tool-step tool-step-${step.type} text-${this.getStepColor(step.type)} mb-1`;
|
||||
|
||||
const timestamp = new Date(step.timestamp).toLocaleTimeString();
|
||||
|
||||
element.innerHTML = `
|
||||
<i class="bx ${this.getStepIcon(step.type)} me-1"></i>
|
||||
<span class="step-time text-muted">[${timestamp}]</span>
|
||||
<span class="step-message ms-1">${step.message}</span>
|
||||
`;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update status indicator
|
||||
*/
|
||||
private updateStatusIndicator(tracker: ExecutionTracker, status: string): void {
|
||||
const statusIcon = tracker.element.querySelector('.tool-status-icon');
|
||||
if (!statusIcon) return;
|
||||
|
||||
const icons: Record<string, string> = {
|
||||
'success': '<i class="bx bx-check-circle text-success fs-5"></i>',
|
||||
'error': '<i class="bx bx-error-circle text-danger fs-5"></i>',
|
||||
'warning': '<i class="bx bx-error text-warning fs-5"></i>',
|
||||
'cancelled': '<i class="bx bx-x-circle text-warning fs-5"></i>',
|
||||
'timeout': '<i class="bx bx-time-five text-danger fs-5"></i>'
|
||||
};
|
||||
|
||||
if (icons[status]) {
|
||||
statusIcon.innerHTML = icons[status];
|
||||
}
|
||||
|
||||
// Update container style
|
||||
const borderColors: Record<string, string> = {
|
||||
'success': 'border-success',
|
||||
'error': 'border-danger',
|
||||
'warning': 'border-warning',
|
||||
'cancelled': 'border-warning',
|
||||
'timeout': 'border-danger'
|
||||
};
|
||||
|
||||
if (borderColors[status]) {
|
||||
tracker.element.classList.add(borderColors[status]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start elapsed time animation with requestAnimationFrame
|
||||
*/
|
||||
private startElapsedTimeAnimation(tracker: ExecutionTracker): void {
|
||||
const updateTime = () => {
|
||||
if (this.executions.has(tracker.id)) {
|
||||
const elapsed = Date.now() - tracker.startTime;
|
||||
const elapsedElement = tracker.element.querySelector('.elapsed-time') as HTMLElement;
|
||||
if (elapsedElement) {
|
||||
elapsedElement.textContent = this.formatDuration(elapsed);
|
||||
}
|
||||
tracker.animationFrameId = requestAnimationFrame(updateTime);
|
||||
}
|
||||
};
|
||||
tracker.animationFrameId = requestAnimationFrame(updateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move execution to history
|
||||
*/
|
||||
private moveToHistory(tracker: ExecutionTracker): void {
|
||||
// Remove from active executions
|
||||
this.executions.delete(tracker.id);
|
||||
|
||||
// Fade out and remove
|
||||
tracker.element.classList.add('fade-out');
|
||||
setTimeout(() => {
|
||||
tracker.element.remove();
|
||||
}, UI_CONSTANTS.FADE_OUT_DURATION);
|
||||
|
||||
// Add to history
|
||||
this.addToHistory(tracker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tracker to history
|
||||
*/
|
||||
private addToHistory(tracker: ExecutionTracker): void {
|
||||
// Add to history items array
|
||||
this.historyItems.unshift(tracker);
|
||||
|
||||
// Limit history size
|
||||
if (this.historyItems.length > UI_CONSTANTS.MAX_HISTORY_UI_SIZE) {
|
||||
this.historyItems = this.historyItems.slice(0, UI_CONSTANTS.MAX_HISTORY_UI_SIZE);
|
||||
}
|
||||
|
||||
// Update display
|
||||
if (this.virtualScroll) {
|
||||
this.virtualScroll.updateTotalItems(this.historyItems.length);
|
||||
this.virtualScroll.refresh();
|
||||
} else if (this.historyContainer) {
|
||||
const historyItem = this.createHistoryItem(tracker);
|
||||
this.historyContainer.prepend(historyItem);
|
||||
|
||||
// Limit DOM elements
|
||||
const elements = this.historyContainer.querySelectorAll('.history-item');
|
||||
if (elements.length > UI_CONSTANTS.MAX_HISTORY_UI_SIZE) {
|
||||
elements[elements.length - 1].remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create history item
|
||||
*/
|
||||
private createHistoryItem(tracker: ExecutionTracker): HTMLElement {
|
||||
const element = document.createElement('div');
|
||||
element.className = 'history-item small text-muted mb-1';
|
||||
|
||||
const duration = Date.now() - tracker.startTime;
|
||||
const statusIcon = this.getStatusIcon(tracker.status);
|
||||
const time = new Date(tracker.startTime).toLocaleTimeString();
|
||||
|
||||
element.innerHTML = `
|
||||
${statusIcon}
|
||||
<span class="ms-1">${tracker.toolName}</span>
|
||||
<span class="ms-1">(${this.formatDuration(duration)})</span>
|
||||
<span class="ms-1 text-muted">${time}</span>
|
||||
`;
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get step color
|
||||
*/
|
||||
private getStepColor(type: string): string {
|
||||
const colors: Record<string, string> = {
|
||||
'info': 'muted',
|
||||
'warning': 'warning',
|
||||
'error': 'danger',
|
||||
'progress': 'primary'
|
||||
};
|
||||
return colors[type] || 'muted';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get step icon
|
||||
*/
|
||||
private getStepIcon(type: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
'info': 'bx-info-circle',
|
||||
'warning': 'bx-error',
|
||||
'error': 'bx-error-circle',
|
||||
'progress': 'bx-loader-alt'
|
||||
};
|
||||
return icons[type] || 'bx-circle';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status icon
|
||||
*/
|
||||
private getStatusIcon(status: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
'success': '<i class="bx bx-check-circle text-success"></i>',
|
||||
'error': '<i class="bx bx-error-circle text-danger"></i>',
|
||||
'cancelled': '<i class="bx bx-x-circle text-warning"></i>',
|
||||
'timeout': '<i class="bx bx-time-five text-danger"></i>',
|
||||
'running': '<i class="bx bx-loader-alt text-primary"></i>',
|
||||
'pending': '<i class="bx bx-time text-muted"></i>'
|
||||
};
|
||||
return icons[status] || '<i class="bx bx-circle text-muted"></i>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse steps if there are too many
|
||||
*/
|
||||
private collapseStepsIfNeeded(tracker: ExecutionTracker): void {
|
||||
const stepsContainer = tracker.element.querySelector('.tool-steps') as HTMLElement;
|
||||
if (stepsContainer && tracker.steps.length > UI_CONSTANTS.MAX_VISIBLE_STEPS) {
|
||||
const details = document.createElement('details');
|
||||
details.className = 'mt-2';
|
||||
details.innerHTML = `
|
||||
<summary class="text-muted small cursor-pointer">
|
||||
Show ${tracker.steps.length} execution steps
|
||||
</summary>
|
||||
`;
|
||||
details.appendChild(stepsContainer.cloneNode(true));
|
||||
stepsContainer.replaceWith(details);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format result for display
|
||||
*/
|
||||
private formatResult(result: any): string {
|
||||
if (typeof result === 'string') {
|
||||
return this.truncateString(result);
|
||||
}
|
||||
const json = JSON.stringify(result);
|
||||
return this.truncateString(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate string for display
|
||||
*/
|
||||
private truncateString(str: string, maxLength: number = UI_CONSTANTS.MAX_STRING_DISPLAY_LENGTH): string {
|
||||
if (str.length <= maxLength) {
|
||||
return str;
|
||||
}
|
||||
return `${str.substring(0, maxLength)}...`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format duration
|
||||
*/
|
||||
private formatDuration(milliseconds: number): string {
|
||||
if (milliseconds < 1000) {
|
||||
return `${Math.round(milliseconds)}ms`;
|
||||
} else if (milliseconds < 60000) {
|
||||
return `${(milliseconds / 1000).toFixed(1)}s`;
|
||||
} else {
|
||||
const minutes = Math.floor(milliseconds / 60000);
|
||||
const seconds = Math.floor((milliseconds % 60000) / 1000);
|
||||
return `${minutes}m ${seconds}s`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set history container with virtual scrolling
|
||||
*/
|
||||
public setHistoryContainer(container: HTMLElement, useVirtualScroll: boolean = false): void {
|
||||
this.historyContainer = container;
|
||||
|
||||
if (useVirtualScroll && this.historyItems.length > 20) {
|
||||
this.initializeVirtualScroll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize virtual scrolling for history
|
||||
*/
|
||||
private initializeVirtualScroll(): void {
|
||||
if (!this.historyContainer) return;
|
||||
|
||||
this.virtualScroll = createVirtualScroll({
|
||||
container: this.historyContainer,
|
||||
itemHeight: 30, // Approximate height of history items
|
||||
totalItems: this.historyItems.length,
|
||||
overscan: 3,
|
||||
onRenderItem: (index) => {
|
||||
return this.renderHistoryItemAtIndex(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render history item at specific index
|
||||
*/
|
||||
private renderHistoryItemAtIndex(index: number): HTMLElement {
|
||||
const item = this.historyItems[index];
|
||||
if (!item) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'history-item-empty';
|
||||
return empty;
|
||||
}
|
||||
|
||||
return this.createHistoryItem(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set statistics container
|
||||
*/
|
||||
public setStatsContainer(container: HTMLElement): void {
|
||||
this.statsContainer = container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all executions
|
||||
*/
|
||||
public clear(): void {
|
||||
this.executions.forEach(tracker => {
|
||||
if (tracker.animationFrameId) {
|
||||
cancelAnimationFrame(tracker.animationFrameId);
|
||||
}
|
||||
});
|
||||
this.executions.clear();
|
||||
this.container.innerHTML = '';
|
||||
this.historyItems = [];
|
||||
|
||||
if (this.virtualScroll) {
|
||||
this.virtualScroll.destroy();
|
||||
this.virtualScroll = undefined;
|
||||
}
|
||||
|
||||
if (this.historyContainer) {
|
||||
this.historyContainer.innerHTML = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tool feedback UI instance
|
||||
*/
|
||||
export function createToolFeedbackUI(container: HTMLElement): ToolFeedbackUI {
|
||||
return new ToolFeedbackUI(container);
|
||||
}
|
||||
367
apps/client/src/widgets/llm_chat/tool_preview_ui.ts
Normal file
367
apps/client/src/widgets/llm_chat/tool_preview_ui.ts
Normal file
@@ -0,0 +1,367 @@
|
||||
/**
|
||||
* Tool Preview UI Component
|
||||
*
|
||||
* Provides UI for previewing tool executions before they run,
|
||||
* allowing users to approve, reject, or modify tool parameters.
|
||||
*/
|
||||
|
||||
import { t } from "../../services/i18n.js";
|
||||
|
||||
/**
|
||||
* Tool preview data from server
|
||||
*/
|
||||
export interface ToolPreviewData {
|
||||
id: string;
|
||||
toolName: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
parameters: Record<string, unknown>;
|
||||
formattedParameters: string[];
|
||||
estimatedDuration: number;
|
||||
riskLevel: 'low' | 'medium' | 'high';
|
||||
requiresConfirmation: boolean;
|
||||
warnings?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution plan from server
|
||||
*/
|
||||
export interface ExecutionPlanData {
|
||||
id: string;
|
||||
tools: ToolPreviewData[];
|
||||
totalEstimatedDuration: number;
|
||||
requiresConfirmation: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* User approval data
|
||||
*/
|
||||
export interface UserApproval {
|
||||
planId: string;
|
||||
approved: boolean;
|
||||
rejectedTools?: string[];
|
||||
modifiedParameters?: Record<string, Record<string, unknown>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool Preview UI Manager
|
||||
*/
|
||||
export class ToolPreviewUI {
|
||||
private container: HTMLElement;
|
||||
private currentPlan?: ExecutionPlanData;
|
||||
private onApprovalCallback?: (approval: UserApproval) => void;
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show tool execution preview
|
||||
*/
|
||||
public async showPreview(
|
||||
plan: ExecutionPlanData,
|
||||
onApproval: (approval: UserApproval) => void
|
||||
): Promise<void> {
|
||||
this.currentPlan = plan;
|
||||
this.onApprovalCallback = onApproval;
|
||||
|
||||
const previewElement = this.createPreviewElement(plan);
|
||||
this.container.appendChild(previewElement);
|
||||
|
||||
// Auto-scroll to preview
|
||||
previewElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Create preview element
|
||||
*/
|
||||
private createPreviewElement(plan: ExecutionPlanData): HTMLElement {
|
||||
const element = document.createElement('div');
|
||||
element.className = 'tool-preview-container mb-3 border rounded p-3 bg-light';
|
||||
element.dataset.planId = plan.id;
|
||||
|
||||
// Header
|
||||
const header = document.createElement('div');
|
||||
header.className = 'tool-preview-header mb-3';
|
||||
header.innerHTML = `
|
||||
<h6 class="mb-2">
|
||||
<i class="bx bx-shield-quarter me-2"></i>
|
||||
${t('Tool Execution Preview')}
|
||||
</h6>
|
||||
<p class="text-muted small mb-2">
|
||||
${plan.tools.length} ${plan.tools.length === 1 ? 'tool' : 'tools'} will be executed
|
||||
${plan.requiresConfirmation ? ' (confirmation required)' : ''}
|
||||
</p>
|
||||
<div class="d-flex align-items-center gap-3 small text-muted">
|
||||
<span>
|
||||
<i class="bx bx-time-five me-1"></i>
|
||||
Estimated time: ${this.formatDuration(plan.totalEstimatedDuration)}
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
element.appendChild(header);
|
||||
|
||||
// Tool list
|
||||
const toolList = document.createElement('div');
|
||||
toolList.className = 'tool-preview-list mb-3';
|
||||
|
||||
plan.tools.forEach((tool, index) => {
|
||||
const toolElement = this.createToolPreviewItem(tool, index);
|
||||
toolList.appendChild(toolElement);
|
||||
});
|
||||
|
||||
element.appendChild(toolList);
|
||||
|
||||
// Actions
|
||||
const actions = document.createElement('div');
|
||||
actions.className = 'tool-preview-actions d-flex gap-2';
|
||||
|
||||
if (plan.requiresConfirmation) {
|
||||
actions.innerHTML = `
|
||||
<button class="btn btn-success btn-sm approve-all-btn">
|
||||
<i class="bx bx-check me-1"></i>
|
||||
Approve All
|
||||
</button>
|
||||
<button class="btn btn-warning btn-sm modify-btn">
|
||||
<i class="bx bx-edit me-1"></i>
|
||||
Modify
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm reject-all-btn">
|
||||
<i class="bx bx-x me-1"></i>
|
||||
Reject All
|
||||
</button>
|
||||
`;
|
||||
|
||||
// Add event listeners
|
||||
const approveBtn = actions.querySelector('.approve-all-btn') as HTMLButtonElement;
|
||||
const modifyBtn = actions.querySelector('.modify-btn') as HTMLButtonElement;
|
||||
const rejectBtn = actions.querySelector('.reject-all-btn') as HTMLButtonElement;
|
||||
|
||||
approveBtn?.addEventListener('click', () => this.handleApproveAll());
|
||||
modifyBtn?.addEventListener('click', () => this.handleModify());
|
||||
rejectBtn?.addEventListener('click', () => this.handleRejectAll());
|
||||
} else {
|
||||
// Auto-approve after showing preview
|
||||
setTimeout(() => {
|
||||
this.handleApproveAll();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
element.appendChild(actions);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tool preview item
|
||||
*/
|
||||
private createToolPreviewItem(tool: ToolPreviewData, index: number): HTMLElement {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'tool-preview-item mb-2 p-2 border rounded bg-white';
|
||||
item.dataset.toolName = tool.toolName;
|
||||
|
||||
const riskBadge = this.getRiskBadge(tool.riskLevel);
|
||||
const riskIcon = this.getRiskIcon(tool.riskLevel);
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="d-flex align-items-start">
|
||||
<div class="tool-preview-checkbox me-2 pt-1">
|
||||
<input type="checkbox" class="form-check-input"
|
||||
id="tool-${index}"
|
||||
checked
|
||||
${tool.requiresConfirmation ? '' : 'disabled'}>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<label class="tool-name fw-bold small mb-0" for="tool-${index}">
|
||||
${tool.displayName}
|
||||
</label>
|
||||
${riskBadge}
|
||||
${riskIcon}
|
||||
</div>
|
||||
<div class="tool-description text-muted small mb-2">
|
||||
${tool.description}
|
||||
</div>
|
||||
<div class="tool-parameters small">
|
||||
<details>
|
||||
<summary class="text-primary cursor-pointer">
|
||||
Parameters (${Object.keys(tool.parameters).length})
|
||||
</summary>
|
||||
<div class="mt-1 p-2 bg-light rounded">
|
||||
${this.formatParameters(tool.formattedParameters)}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
${tool.warnings && tool.warnings.length > 0 ? `
|
||||
<div class="tool-warnings mt-2">
|
||||
${tool.warnings.map(w => `
|
||||
<div class="alert alert-warning py-1 px-2 mb-1 small">
|
||||
<i class="bx bx-error-circle me-1"></i>
|
||||
${w}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="tool-duration text-muted small ms-2">
|
||||
<i class="bx bx-time me-1"></i>
|
||||
~${this.formatDuration(tool.estimatedDuration)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get risk level badge
|
||||
*/
|
||||
private getRiskBadge(riskLevel: 'low' | 'medium' | 'high'): string {
|
||||
const badges = {
|
||||
low: '<span class="badge bg-success ms-2">Low Risk</span>',
|
||||
medium: '<span class="badge bg-warning ms-2">Medium Risk</span>',
|
||||
high: '<span class="badge bg-danger ms-2">High Risk</span>'
|
||||
};
|
||||
return badges[riskLevel] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get risk level icon
|
||||
*/
|
||||
private getRiskIcon(riskLevel: 'low' | 'medium' | 'high'): string {
|
||||
const icons = {
|
||||
low: '<i class="bx bx-shield-check text-success ms-2"></i>',
|
||||
medium: '<i class="bx bx-shield text-warning ms-2"></i>',
|
||||
high: '<i class="bx bx-shield-x text-danger ms-2"></i>'
|
||||
};
|
||||
return icons[riskLevel] || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format parameters for display
|
||||
*/
|
||||
private formatParameters(parameters: string[]): string {
|
||||
return parameters.map(param => {
|
||||
const [key, ...valueParts] = param.split(':');
|
||||
const value = valueParts.join(':').trim();
|
||||
return `
|
||||
<div class="parameter-item mb-1">
|
||||
<span class="parameter-key text-muted">${key}:</span>
|
||||
<span class="parameter-value ms-1">${this.escapeHtml(value)}</span>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle approve all
|
||||
*/
|
||||
private handleApproveAll(): void {
|
||||
if (!this.currentPlan || !this.onApprovalCallback) return;
|
||||
|
||||
const approval: UserApproval = {
|
||||
planId: this.currentPlan.id,
|
||||
approved: true
|
||||
};
|
||||
|
||||
this.onApprovalCallback(approval);
|
||||
this.hidePreview();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle modify
|
||||
*/
|
||||
private handleModify(): void {
|
||||
if (!this.currentPlan) return;
|
||||
|
||||
// Get selected tools
|
||||
const checkboxes = this.container.querySelectorAll('.tool-preview-item input[type="checkbox"]');
|
||||
const rejectedTools: string[] = [];
|
||||
|
||||
checkboxes.forEach((checkbox: Element) => {
|
||||
const input = checkbox as HTMLInputElement;
|
||||
const toolItem = input.closest('.tool-preview-item') as HTMLElement;
|
||||
const toolName = toolItem?.dataset.toolName;
|
||||
|
||||
if (toolName && !input.checked) {
|
||||
rejectedTools.push(toolName);
|
||||
}
|
||||
});
|
||||
|
||||
const approval: UserApproval = {
|
||||
planId: this.currentPlan.id,
|
||||
approved: true,
|
||||
rejectedTools: rejectedTools.length > 0 ? rejectedTools : undefined
|
||||
};
|
||||
|
||||
if (this.onApprovalCallback) {
|
||||
this.onApprovalCallback(approval);
|
||||
}
|
||||
|
||||
this.hidePreview();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle reject all
|
||||
*/
|
||||
private handleRejectAll(): void {
|
||||
if (!this.currentPlan || !this.onApprovalCallback) return;
|
||||
|
||||
const approval: UserApproval = {
|
||||
planId: this.currentPlan.id,
|
||||
approved: false
|
||||
};
|
||||
|
||||
this.onApprovalCallback(approval);
|
||||
this.hidePreview();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide preview
|
||||
*/
|
||||
private hidePreview(): void {
|
||||
const preview = this.container.querySelector('.tool-preview-container');
|
||||
if (preview) {
|
||||
// Add fade out animation
|
||||
preview.classList.add('fade-out');
|
||||
setTimeout(() => {
|
||||
preview.remove();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
this.currentPlan = undefined;
|
||||
this.onApprovalCallback = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format duration
|
||||
*/
|
||||
private formatDuration(milliseconds: number): string {
|
||||
if (milliseconds < 1000) {
|
||||
return `${milliseconds}ms`;
|
||||
} else if (milliseconds < 60000) {
|
||||
return `${(milliseconds / 1000).toFixed(1)}s`;
|
||||
} else {
|
||||
const minutes = Math.floor(milliseconds / 60000);
|
||||
const seconds = Math.floor((milliseconds % 60000) / 1000);
|
||||
return `${minutes}m ${seconds}s`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape HTML
|
||||
*/
|
||||
private escapeHtml(text: string): string {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tool preview UI instance
|
||||
*/
|
||||
export function createToolPreviewUI(container: HTMLElement): ToolPreviewUI {
|
||||
return new ToolPreviewUI(container);
|
||||
}
|
||||
419
apps/client/src/widgets/llm_chat/tool_websocket.ts
Normal file
419
apps/client/src/widgets/llm_chat/tool_websocket.ts
Normal file
@@ -0,0 +1,419 @@
|
||||
/**
|
||||
* Tool WebSocket Manager
|
||||
*
|
||||
* Provides real-time WebSocket communication for tool execution updates.
|
||||
* Implements automatic reconnection, heartbeat, and message queuing.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
/**
|
||||
* WebSocket message types
|
||||
*/
|
||||
export enum WSMessageType {
|
||||
// Tool execution events
|
||||
TOOL_START = 'tool:start',
|
||||
TOOL_PROGRESS = 'tool:progress',
|
||||
TOOL_STEP = 'tool:step',
|
||||
TOOL_COMPLETE = 'tool:complete',
|
||||
TOOL_ERROR = 'tool:error',
|
||||
TOOL_CANCELLED = 'tool:cancelled',
|
||||
|
||||
// Connection events
|
||||
HEARTBEAT = 'heartbeat',
|
||||
PING = 'ping',
|
||||
PONG = 'pong',
|
||||
|
||||
// Control events
|
||||
SUBSCRIBE = 'subscribe',
|
||||
UNSUBSCRIBE = 'unsubscribe',
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket message structure
|
||||
*/
|
||||
export interface WSMessage {
|
||||
id: string;
|
||||
type: WSMessageType;
|
||||
timestamp: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket configuration
|
||||
*/
|
||||
export interface WSConfig {
|
||||
url: string;
|
||||
reconnectInterval?: number;
|
||||
maxReconnectAttempts?: number;
|
||||
heartbeatInterval?: number;
|
||||
messageTimeout?: number;
|
||||
autoReconnect?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection state
|
||||
*/
|
||||
export enum ConnectionState {
|
||||
CONNECTING = 'connecting',
|
||||
CONNECTED = 'connected',
|
||||
RECONNECTING = 'reconnecting',
|
||||
DISCONNECTED = 'disconnected',
|
||||
FAILED = 'failed'
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool WebSocket Manager
|
||||
*/
|
||||
export class ToolWebSocketManager extends EventEmitter {
|
||||
private ws?: WebSocket;
|
||||
private config: Required<WSConfig>;
|
||||
private state: ConnectionState = ConnectionState.DISCONNECTED;
|
||||
private reconnectAttempts: number = 0;
|
||||
private reconnectTimer?: number;
|
||||
private heartbeatTimer?: number;
|
||||
private messageQueue: WSMessage[] = [];
|
||||
private subscriptions: Set<string> = new Set();
|
||||
private lastPingTime?: number;
|
||||
private lastPongTime?: number;
|
||||
|
||||
// Performance constants
|
||||
private static readonly DEFAULT_RECONNECT_INTERVAL = 3000;
|
||||
private static readonly DEFAULT_MAX_RECONNECT_ATTEMPTS = 10;
|
||||
private static readonly DEFAULT_HEARTBEAT_INTERVAL = 30000;
|
||||
private static readonly DEFAULT_MESSAGE_TIMEOUT = 5000;
|
||||
private static readonly MAX_QUEUE_SIZE = 100;
|
||||
|
||||
constructor(config: WSConfig) {
|
||||
super();
|
||||
|
||||
this.config = {
|
||||
url: config.url,
|
||||
reconnectInterval: config.reconnectInterval ?? ToolWebSocketManager.DEFAULT_RECONNECT_INTERVAL,
|
||||
maxReconnectAttempts: config.maxReconnectAttempts ?? ToolWebSocketManager.DEFAULT_MAX_RECONNECT_ATTEMPTS,
|
||||
heartbeatInterval: config.heartbeatInterval ?? ToolWebSocketManager.DEFAULT_HEARTBEAT_INTERVAL,
|
||||
messageTimeout: config.messageTimeout ?? ToolWebSocketManager.DEFAULT_MESSAGE_TIMEOUT,
|
||||
autoReconnect: config.autoReconnect ?? true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to WebSocket server
|
||||
*/
|
||||
public connect(): void {
|
||||
if (this.state === ConnectionState.CONNECTED || this.state === ConnectionState.CONNECTING) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.state = ConnectionState.CONNECTING;
|
||||
this.emit('connecting');
|
||||
|
||||
try {
|
||||
this.ws = new WebSocket(this.config.url);
|
||||
this.setupEventHandlers();
|
||||
} catch (error) {
|
||||
this.handleConnectionError(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup WebSocket event handlers
|
||||
*/
|
||||
private setupEventHandlers(): void {
|
||||
if (!this.ws) return;
|
||||
|
||||
this.ws.onopen = () => {
|
||||
this.state = ConnectionState.CONNECTED;
|
||||
this.reconnectAttempts = 0;
|
||||
this.emit('connected');
|
||||
|
||||
// Start heartbeat
|
||||
this.startHeartbeat();
|
||||
|
||||
// Re-subscribe to previous subscriptions
|
||||
this.resubscribe();
|
||||
|
||||
// Flush message queue
|
||||
this.flushMessageQueue();
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const message: WSMessage = JSON.parse(event.data);
|
||||
this.handleMessage(message);
|
||||
} catch (error) {
|
||||
console.error('Failed to parse WebSocket message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
this.emit('error', error);
|
||||
};
|
||||
|
||||
this.ws.onclose = (event) => {
|
||||
this.state = ConnectionState.DISCONNECTED;
|
||||
this.stopHeartbeat();
|
||||
this.emit('disconnected', event.code, event.reason);
|
||||
|
||||
if (this.config.autoReconnect && !event.wasClean) {
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming message
|
||||
*/
|
||||
private handleMessage(message: WSMessage): void {
|
||||
// Handle control messages
|
||||
switch (message.type) {
|
||||
case WSMessageType.PONG:
|
||||
this.lastPongTime = Date.now();
|
||||
return;
|
||||
|
||||
case WSMessageType.HEARTBEAT:
|
||||
this.send({
|
||||
id: message.id,
|
||||
type: WSMessageType.PONG,
|
||||
timestamp: new Date().toISOString(),
|
||||
data: null
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit message for subscribers
|
||||
this.emit('message', message);
|
||||
this.emit(message.type, message.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message
|
||||
*/
|
||||
public send(message: WSMessage): void {
|
||||
if (this.state === ConnectionState.CONNECTED && this.ws?.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
this.ws.send(JSON.stringify(message));
|
||||
} catch (error) {
|
||||
console.error('Failed to send WebSocket message:', error);
|
||||
this.queueMessage(message);
|
||||
}
|
||||
} else {
|
||||
this.queueMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a message for later sending
|
||||
*/
|
||||
private queueMessage(message: WSMessage): void {
|
||||
if (this.messageQueue.length >= ToolWebSocketManager.MAX_QUEUE_SIZE) {
|
||||
this.messageQueue.shift(); // Remove oldest message
|
||||
}
|
||||
this.messageQueue.push(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush message queue
|
||||
*/
|
||||
private flushMessageQueue(): void {
|
||||
while (this.messageQueue.length > 0 && this.state === ConnectionState.CONNECTED) {
|
||||
const message = this.messageQueue.shift();
|
||||
if (message) {
|
||||
this.send(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to tool execution updates
|
||||
*/
|
||||
public subscribe(executionId: string): void {
|
||||
this.subscriptions.add(executionId);
|
||||
|
||||
if (this.state === ConnectionState.CONNECTED) {
|
||||
this.send({
|
||||
id: this.generateMessageId(),
|
||||
type: WSMessageType.SUBSCRIBE,
|
||||
timestamp: new Date().toISOString(),
|
||||
data: { executionId }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from tool execution updates
|
||||
*/
|
||||
public unsubscribe(executionId: string): void {
|
||||
this.subscriptions.delete(executionId);
|
||||
|
||||
if (this.state === ConnectionState.CONNECTED) {
|
||||
this.send({
|
||||
id: this.generateMessageId(),
|
||||
type: WSMessageType.UNSUBSCRIBE,
|
||||
timestamp: new Date().toISOString(),
|
||||
data: { executionId }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-subscribe to all previous subscriptions
|
||||
*/
|
||||
private resubscribe(): void {
|
||||
this.subscriptions.forEach(executionId => {
|
||||
this.send({
|
||||
id: this.generateMessageId(),
|
||||
type: WSMessageType.SUBSCRIBE,
|
||||
timestamp: new Date().toISOString(),
|
||||
data: { executionId }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start heartbeat mechanism
|
||||
*/
|
||||
private startHeartbeat(): void {
|
||||
this.stopHeartbeat();
|
||||
|
||||
this.heartbeatTimer = window.setInterval(() => {
|
||||
if (this.state === ConnectionState.CONNECTED) {
|
||||
// Check if last pong was received
|
||||
if (this.lastPingTime && this.lastPongTime) {
|
||||
const timeSinceLastPong = Date.now() - this.lastPongTime;
|
||||
if (timeSinceLastPong > this.config.heartbeatInterval * 2) {
|
||||
// Connection seems dead, reconnect
|
||||
this.reconnect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Send ping
|
||||
this.lastPingTime = Date.now();
|
||||
this.send({
|
||||
id: this.generateMessageId(),
|
||||
type: WSMessageType.PING,
|
||||
timestamp: new Date().toISOString(),
|
||||
data: null
|
||||
});
|
||||
}
|
||||
}, this.config.heartbeatInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop heartbeat mechanism
|
||||
*/
|
||||
private stopHeartbeat(): void {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule reconnection attempt
|
||||
*/
|
||||
private scheduleReconnect(): void {
|
||||
if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
|
||||
this.state = ConnectionState.FAILED;
|
||||
this.emit('failed', 'Max reconnection attempts reached');
|
||||
return;
|
||||
}
|
||||
|
||||
this.state = ConnectionState.RECONNECTING;
|
||||
this.reconnectAttempts++;
|
||||
|
||||
const delay = Math.min(
|
||||
this.config.reconnectInterval * Math.pow(1.5, this.reconnectAttempts - 1),
|
||||
30000 // Max 30 seconds
|
||||
);
|
||||
|
||||
this.emit('reconnecting', this.reconnectAttempts, delay);
|
||||
|
||||
this.reconnectTimer = window.setTimeout(() => {
|
||||
this.connect();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect to server
|
||||
*/
|
||||
public reconnect(): void {
|
||||
this.disconnect(false);
|
||||
this.connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle connection error
|
||||
*/
|
||||
private handleConnectionError(error: any): void {
|
||||
console.error('WebSocket connection error:', error);
|
||||
this.state = ConnectionState.DISCONNECTED;
|
||||
this.emit('error', error);
|
||||
|
||||
if (this.config.autoReconnect) {
|
||||
this.scheduleReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from server
|
||||
*/
|
||||
public disconnect(clearSubscriptions: boolean = true): void {
|
||||
if (this.reconnectTimer) {
|
||||
clearTimeout(this.reconnectTimer);
|
||||
this.reconnectTimer = undefined;
|
||||
}
|
||||
|
||||
this.stopHeartbeat();
|
||||
|
||||
if (this.ws) {
|
||||
this.ws.close(1000, 'Client disconnect');
|
||||
this.ws = undefined;
|
||||
}
|
||||
|
||||
if (clearSubscriptions) {
|
||||
this.subscriptions.clear();
|
||||
}
|
||||
|
||||
this.messageQueue = [];
|
||||
this.state = ConnectionState.DISCONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection state
|
||||
*/
|
||||
public getState(): ConnectionState {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if connected
|
||||
*/
|
||||
public isConnected(): boolean {
|
||||
return this.state === ConnectionState.CONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique message ID
|
||||
*/
|
||||
private generateMessageId(): string {
|
||||
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the WebSocket manager
|
||||
*/
|
||||
public destroy(): void {
|
||||
this.disconnect(true);
|
||||
this.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create WebSocket manager instance
|
||||
*/
|
||||
export function createToolWebSocket(config: WSConfig): ToolWebSocketManager {
|
||||
return new ToolWebSocketManager(config);
|
||||
}
|
||||
312
apps/client/src/widgets/llm_chat/virtual_scroll.ts
Normal file
312
apps/client/src/widgets/llm_chat/virtual_scroll.ts
Normal file
@@ -0,0 +1,312 @@
|
||||
/**
|
||||
* Virtual Scrolling Component
|
||||
*
|
||||
* Provides efficient rendering of large lists by only rendering visible items.
|
||||
* Optimized for the tool execution history display.
|
||||
*/
|
||||
|
||||
export interface VirtualScrollOptions {
|
||||
container: HTMLElement;
|
||||
itemHeight: number;
|
||||
totalItems: number;
|
||||
renderBuffer?: number;
|
||||
overscan?: number;
|
||||
onRenderItem: (index: number) => HTMLElement;
|
||||
onScrollEnd?: () => void;
|
||||
}
|
||||
|
||||
export interface VirtualScrollItem {
|
||||
index: number;
|
||||
element: HTMLElement;
|
||||
top: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual Scroll Manager
|
||||
*/
|
||||
export class VirtualScrollManager {
|
||||
private container: HTMLElement;
|
||||
private viewport: HTMLElement;
|
||||
private content: HTMLElement;
|
||||
private itemHeight: number;
|
||||
private totalItems: number;
|
||||
private renderBuffer: number;
|
||||
private overscan: number;
|
||||
private onRenderItem: (index: number) => HTMLElement;
|
||||
private onScrollEnd?: () => void;
|
||||
|
||||
private visibleItems: Map<number, VirtualScrollItem> = new Map();
|
||||
private scrollRAF?: number;
|
||||
private lastScrollTop: number = 0;
|
||||
private scrollEndTimeout?: number;
|
||||
|
||||
// Performance optimization constants
|
||||
private static readonly DEFAULT_RENDER_BUFFER = 3;
|
||||
private static readonly DEFAULT_OVERSCAN = 2;
|
||||
private static readonly SCROLL_END_DELAY = 150;
|
||||
private static readonly RECYCLE_POOL_SIZE = 50;
|
||||
|
||||
// Element recycling pool
|
||||
private recyclePool: HTMLElement[] = [];
|
||||
|
||||
constructor(options: VirtualScrollOptions) {
|
||||
this.container = options.container;
|
||||
this.itemHeight = options.itemHeight;
|
||||
this.totalItems = options.totalItems;
|
||||
this.renderBuffer = options.renderBuffer ?? VirtualScrollManager.DEFAULT_RENDER_BUFFER;
|
||||
this.overscan = options.overscan ?? VirtualScrollManager.DEFAULT_OVERSCAN;
|
||||
this.onRenderItem = options.onRenderItem;
|
||||
this.onScrollEnd = options.onScrollEnd;
|
||||
|
||||
this.setupStructure();
|
||||
this.attachListeners();
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup DOM structure for virtual scrolling
|
||||
*/
|
||||
private setupStructure(): void {
|
||||
// Create viewport (scrollable container)
|
||||
this.viewport = document.createElement('div');
|
||||
this.viewport.className = 'virtual-scroll-viewport';
|
||||
this.viewport.style.cssText = `
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
// Create content (holds actual items)
|
||||
this.content = document.createElement('div');
|
||||
this.content.className = 'virtual-scroll-content';
|
||||
this.content.style.cssText = `
|
||||
position: relative;
|
||||
height: ${this.totalItems * this.itemHeight}px;
|
||||
`;
|
||||
|
||||
this.viewport.appendChild(this.content);
|
||||
this.container.appendChild(this.viewport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach scroll listeners
|
||||
*/
|
||||
private attachListeners(): void {
|
||||
this.viewport.addEventListener('scroll', this.handleScroll.bind(this), { passive: true });
|
||||
|
||||
// Use ResizeObserver for dynamic container size changes
|
||||
if (typeof ResizeObserver !== 'undefined') {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
this.render();
|
||||
});
|
||||
resizeObserver.observe(this.viewport);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle scroll events with requestAnimationFrame
|
||||
*/
|
||||
private handleScroll(): void {
|
||||
if (this.scrollRAF) {
|
||||
cancelAnimationFrame(this.scrollRAF);
|
||||
}
|
||||
|
||||
this.scrollRAF = requestAnimationFrame(() => {
|
||||
this.render();
|
||||
this.detectScrollEnd();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect when scrolling has ended
|
||||
*/
|
||||
private detectScrollEnd(): void {
|
||||
const scrollTop = this.viewport.scrollTop;
|
||||
|
||||
if (this.scrollEndTimeout) {
|
||||
clearTimeout(this.scrollEndTimeout);
|
||||
}
|
||||
|
||||
this.scrollEndTimeout = window.setTimeout(() => {
|
||||
if (scrollTop === this.lastScrollTop) {
|
||||
this.onScrollEnd?.();
|
||||
}
|
||||
this.lastScrollTop = scrollTop;
|
||||
}, VirtualScrollManager.SCROLL_END_DELAY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render visible items
|
||||
*/
|
||||
private render(): void {
|
||||
const scrollTop = this.viewport.scrollTop;
|
||||
const viewportHeight = this.viewport.clientHeight;
|
||||
|
||||
// Calculate visible range with overscan
|
||||
const startIndex = Math.max(0,
|
||||
Math.floor(scrollTop / this.itemHeight) - this.overscan
|
||||
);
|
||||
const endIndex = Math.min(this.totalItems - 1,
|
||||
Math.ceil((scrollTop + viewportHeight) / this.itemHeight) + this.overscan
|
||||
);
|
||||
|
||||
// Remove items that are no longer visible
|
||||
this.removeInvisibleItems(startIndex, endIndex);
|
||||
|
||||
// Add new visible items
|
||||
for (let i = startIndex; i <= endIndex; i++) {
|
||||
if (!this.visibleItems.has(i)) {
|
||||
this.addItem(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove items outside visible range
|
||||
*/
|
||||
private removeInvisibleItems(startIndex: number, endIndex: number): void {
|
||||
const itemsToRemove: number[] = [];
|
||||
|
||||
this.visibleItems.forEach((item, index) => {
|
||||
if (index < startIndex - this.renderBuffer || index > endIndex + this.renderBuffer) {
|
||||
itemsToRemove.push(index);
|
||||
}
|
||||
});
|
||||
|
||||
itemsToRemove.forEach(index => {
|
||||
const item = this.visibleItems.get(index);
|
||||
if (item) {
|
||||
this.recycleElement(item.element);
|
||||
this.visibleItems.delete(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single item to the visible list
|
||||
*/
|
||||
private addItem(index: number): void {
|
||||
const element = this.getOrCreateElement(index);
|
||||
const top = index * this.itemHeight;
|
||||
|
||||
element.style.cssText = `
|
||||
position: absolute;
|
||||
top: ${top}px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: ${this.itemHeight}px;
|
||||
`;
|
||||
|
||||
this.content.appendChild(element);
|
||||
|
||||
this.visibleItems.set(index, {
|
||||
index,
|
||||
element,
|
||||
top
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create an element (with recycling)
|
||||
*/
|
||||
private getOrCreateElement(index: number): HTMLElement {
|
||||
let element = this.recyclePool.pop();
|
||||
|
||||
if (element) {
|
||||
// Clear previous content
|
||||
element.innerHTML = '';
|
||||
element.className = '';
|
||||
} else {
|
||||
element = document.createElement('div');
|
||||
}
|
||||
|
||||
// Render new content
|
||||
const content = this.onRenderItem(index);
|
||||
if (content !== element) {
|
||||
element.appendChild(content);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recycle an element for reuse
|
||||
*/
|
||||
private recycleElement(element: HTMLElement): void {
|
||||
element.remove();
|
||||
|
||||
if (this.recyclePool.length < VirtualScrollManager.RECYCLE_POOL_SIZE) {
|
||||
this.recyclePool.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update total items and re-render
|
||||
*/
|
||||
public updateTotalItems(totalItems: number): void {
|
||||
this.totalItems = totalItems;
|
||||
this.content.style.height = `${totalItems * this.itemHeight}px`;
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to a specific index
|
||||
*/
|
||||
public scrollToIndex(index: number, behavior: ScrollBehavior = 'smooth'): void {
|
||||
const top = index * this.itemHeight;
|
||||
this.viewport.scrollTo({
|
||||
top,
|
||||
behavior
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current scroll position
|
||||
*/
|
||||
public getScrollPosition(): { index: number; offset: number } {
|
||||
const scrollTop = this.viewport.scrollTop;
|
||||
const index = Math.floor(scrollTop / this.itemHeight);
|
||||
const offset = scrollTop % this.itemHeight;
|
||||
|
||||
return { index, offset };
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh visible items
|
||||
*/
|
||||
public refresh(): void {
|
||||
this.visibleItems.forEach(item => {
|
||||
item.element.remove();
|
||||
});
|
||||
this.visibleItems.clear();
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the virtual scroll manager
|
||||
*/
|
||||
public destroy(): void {
|
||||
if (this.scrollRAF) {
|
||||
cancelAnimationFrame(this.scrollRAF);
|
||||
}
|
||||
|
||||
if (this.scrollEndTimeout) {
|
||||
clearTimeout(this.scrollEndTimeout);
|
||||
}
|
||||
|
||||
this.visibleItems.forEach(item => {
|
||||
item.element.remove();
|
||||
});
|
||||
|
||||
this.visibleItems.clear();
|
||||
this.recyclePool = [];
|
||||
this.viewport.remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a virtual scroll instance
|
||||
*/
|
||||
export function createVirtualScroll(options: VirtualScrollOptions): VirtualScrollManager {
|
||||
return new VirtualScrollManager(options);
|
||||
}
|
||||
@@ -727,9 +727,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
for (const key in hotKeys) {
|
||||
const handler = hotKeys[key];
|
||||
|
||||
$(this.tree.$container).on("keydown", null, key, (evt) => {
|
||||
shortcutService.bindElShortcut($(this.tree.$container), key, () => {
|
||||
const node = this.tree.getActiveNode();
|
||||
return handler(node, evt);
|
||||
return handler(node, {} as JQuery.KeyDownEvent);
|
||||
// return false from the handler will stop default handling.
|
||||
});
|
||||
}
|
||||
@@ -1552,7 +1552,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
const hotKeyMap: Record<string, (node: Fancytree.FancytreeNode, e: JQuery.KeyDownEvent) => boolean> = {};
|
||||
|
||||
for (const action of actions) {
|
||||
for (const shortcut of action.effectiveShortcuts) {
|
||||
for (const shortcut of action.effectiveShortcuts ?? []) {
|
||||
hotKeyMap[shortcutService.normalizeShortcut(shortcut)] = (node) => {
|
||||
const notePath = treeService.getNotePath(node);
|
||||
|
||||
|
||||
41
apps/client/src/widgets/react/Modal.tsx
Normal file
41
apps/client/src/widgets/react/Modal.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { useEffect, useRef } from "preact/hooks";
|
||||
import { t } from "../../services/i18n";
|
||||
import { ComponentChildren } from "preact";
|
||||
|
||||
interface ModalProps {
|
||||
className: string;
|
||||
title: string;
|
||||
size: "lg" | "sm";
|
||||
children: ComponentChildren;
|
||||
onShown?: () => void;
|
||||
}
|
||||
|
||||
export default function Modal({ children, className, size, title, onShown }: ModalProps) {
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
if (onShown) {
|
||||
useEffect(() => {
|
||||
const modalElement = modalRef.current;
|
||||
if (modalElement) {
|
||||
modalElement.addEventListener("shown.bs.modal", onShown);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`modal fade mx-auto ${className}`} tabIndex={-1} role="dialog" ref={modalRef}>
|
||||
<div className={`modal-dialog modal-${size}`} role="document">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">{title}</h5>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label={t("modal.close")}></button>
|
||||
</div>
|
||||
|
||||
<div className="modal-body">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
14
apps/client/src/widgets/react/ReactBasicWidget.ts
Normal file
14
apps/client/src/widgets/react/ReactBasicWidget.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { JSX, render } from "preact";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
|
||||
export default abstract class ReactBasicWidget extends BasicWidget {
|
||||
|
||||
abstract get component(): JSX.Element;
|
||||
|
||||
doRender() {
|
||||
const renderContainer = new DocumentFragment();
|
||||
render(this.component, renderContainer);
|
||||
this.$widget = $(renderContainer.firstChild as HTMLElement);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -52,7 +52,8 @@ export default class DateTimeFormatOptions extends OptionsWidget {
|
||||
}
|
||||
|
||||
async optionsLoaded(options: OptionMap) {
|
||||
const shortcutKey = (await keyboardActionsService.getAction("insertDateTimeToText")).effectiveShortcuts.join(", ");
|
||||
const action = await keyboardActionsService.getAction("insertDateTimeToText");
|
||||
const shortcutKey = (action.effectiveShortcuts ?? []).join(", ");
|
||||
const $link = await linkService.createLink("_hidden/_options/_optionsShortcuts", {
|
||||
"title": shortcutKey,
|
||||
"showTooltip": false
|
||||
|
||||
@@ -15,26 +15,26 @@ export async function getBoardData(parentNote: FNote, groupByColumn: string, per
|
||||
|
||||
// Get all columns that exist in the notes
|
||||
const columnsFromNotes = [...byColumn.keys()];
|
||||
|
||||
|
||||
// Get existing persisted columns and preserve their order
|
||||
const existingPersistedColumns = persistedData.columns || [];
|
||||
const existingColumnValues = existingPersistedColumns.map(c => c.value);
|
||||
|
||||
|
||||
// Find truly new columns (exist in notes but not in persisted data)
|
||||
const newColumnValues = columnsFromNotes.filter(col => !existingColumnValues.includes(col));
|
||||
|
||||
|
||||
// Build the complete correct column list: existing + new
|
||||
const allColumns = [
|
||||
...existingPersistedColumns, // Preserve existing order
|
||||
...newColumnValues.map(value => ({ value })) // Add new columns
|
||||
];
|
||||
|
||||
|
||||
// Remove duplicates (just in case) and ensure we only keep columns that exist in notes or are explicitly preserved
|
||||
const deduplicatedColumns = allColumns.filter((column, index) => {
|
||||
const firstIndex = allColumns.findIndex(c => c.value === column.value);
|
||||
return firstIndex === index; // Keep only the first occurrence
|
||||
});
|
||||
|
||||
|
||||
// Ensure all persisted columns have empty arrays in byColumn (even if no notes use them)
|
||||
for (const column of deduplicatedColumns) {
|
||||
if (!byColumn.has(column.value)) {
|
||||
@@ -44,10 +44,10 @@ export async function getBoardData(parentNote: FNote, groupByColumn: string, per
|
||||
|
||||
// Return updated persisted data only if there were changes
|
||||
let newPersistedData: BoardData | undefined;
|
||||
const hasChanges = newColumnValues.length > 0 ||
|
||||
const hasChanges = newColumnValues.length > 0 ||
|
||||
existingPersistedColumns.length !== deduplicatedColumns.length ||
|
||||
!existingPersistedColumns.every((col, idx) => deduplicatedColumns[idx]?.value === col.value);
|
||||
|
||||
|
||||
if (hasChanges) {
|
||||
newPersistedData = {
|
||||
...persistedData,
|
||||
@@ -68,6 +68,10 @@ async function recursiveGroupBy(branches: FBranch[], byColumn: ColumnMap, groupB
|
||||
continue;
|
||||
}
|
||||
|
||||
if (note.hasChildren()) {
|
||||
await recursiveGroupBy(note.getChildBranches(), byColumn, groupByColumn);
|
||||
}
|
||||
|
||||
const group = note.getLabelValue(groupByColumn);
|
||||
if (!group) {
|
||||
continue;
|
||||
|
||||
@@ -292,6 +292,7 @@ class ListOrGridView extends ViewMode<{}> {
|
||||
|
||||
const $card = $('<div class="note-book-card">')
|
||||
.attr("data-note-id", note.noteId)
|
||||
.addClass("no-tooltip-preview")
|
||||
.append(
|
||||
$('<h5 class="note-book-header">')
|
||||
.append($expander)
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
"lib": [ "ESNext" ],
|
||||
"outDir": "dist",
|
||||
"types": [
|
||||
"node"
|
||||
"node",
|
||||
"preact"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"jsx": "preserve",
|
||||
"jsxFactory": "h",
|
||||
"jsxImportSource": "preact",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo"
|
||||
@@ -33,6 +37,7 @@
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.json"
|
||||
],
|
||||
"references": [
|
||||
|
||||
@@ -4,6 +4,7 @@ import { defineConfig, type Plugin } from 'vite';
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy'
|
||||
import asset_path from './src/asset_path';
|
||||
import webpackStatsPlugin from 'rollup-plugin-webpack-stats';
|
||||
import preact from "@preact/preset-vite";
|
||||
|
||||
const assets = [ "assets", "stylesheets", "fonts", "translations" ];
|
||||
|
||||
@@ -20,6 +21,7 @@ export default defineConfig(() => ({
|
||||
host: 'localhost',
|
||||
},
|
||||
plugins: [
|
||||
preact(),
|
||||
viteStaticCopy({
|
||||
targets: assets.map((asset) => ({
|
||||
src: `src/${asset}/*`,
|
||||
|
||||
@@ -19,6 +19,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"dotenv": "17.2.1",
|
||||
"electron": "37.2.4"
|
||||
"electron": "37.2.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/desktop",
|
||||
"version": "0.97.1",
|
||||
"version": "0.97.2",
|
||||
"description": "Build your personal knowledge base with Trilium Notes",
|
||||
"private": true,
|
||||
"main": "main.cjs",
|
||||
@@ -17,15 +17,15 @@
|
||||
"@types/electron-squirrel-startup": "1.0.2",
|
||||
"@triliumnext/server": "workspace:*",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"electron": "37.2.4",
|
||||
"@electron-forge/cli": "7.8.2",
|
||||
"@electron-forge/maker-deb": "7.8.2",
|
||||
"@electron-forge/maker-dmg": "7.8.2",
|
||||
"@electron-forge/maker-flatpak": "7.8.2",
|
||||
"@electron-forge/maker-rpm": "7.8.2",
|
||||
"@electron-forge/maker-squirrel": "7.8.2",
|
||||
"@electron-forge/maker-zip": "7.8.2",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "7.8.2",
|
||||
"electron": "37.2.6",
|
||||
"@electron-forge/cli": "7.8.3",
|
||||
"@electron-forge/maker-deb": "7.8.3",
|
||||
"@electron-forge/maker-dmg": "7.8.3",
|
||||
"@electron-forge/maker-flatpak": "7.8.3",
|
||||
"@electron-forge/maker-rpm": "7.8.3",
|
||||
"@electron-forge/maker-squirrel": "7.8.3",
|
||||
"@electron-forge/maker-zip": "7.8.3",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "7.8.3",
|
||||
"prebuild-install": "^7.1.1"
|
||||
},
|
||||
"config": {
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
"@triliumnext/desktop": "workspace:*",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"copy-webpack-plugin": "13.0.0",
|
||||
"electron": "37.2.4",
|
||||
"fs-extra": "11.3.0"
|
||||
"electron": "37.2.6",
|
||||
"fs-extra": "11.3.1"
|
||||
},
|
||||
"nx": {
|
||||
"name": "edit-docs",
|
||||
|
||||
@@ -47,15 +47,14 @@ test("User can change language from settings", async ({ page, context }) => {
|
||||
|
||||
// Select Chinese and ensure the translation is set.
|
||||
await languageCombobox.selectOption("cn");
|
||||
|
||||
// Press the refresh button.
|
||||
await app.currentNoteSplit.getByRole("button", { name: "Restart the application" }).click();
|
||||
await app.currentNoteSplit.locator("button.restart-app-button").click();
|
||||
|
||||
await expect(app.currentNoteSplit).toContainText("一周的第一天", { timeout: 15000 });
|
||||
await expect(languageCombobox).toHaveValue("cn");
|
||||
|
||||
// Select English again.
|
||||
await languageCombobox.selectOption("en");
|
||||
await app.currentNoteSplit.locator("button.restart-app-button").click();
|
||||
await expect(app.currentNoteSplit).toContainText("Language", { timeout: 15000 });
|
||||
await expect(languageCombobox).toHaveValue("en");
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22.17.1-bullseye-slim AS builder
|
||||
FROM node:22.18.0-bullseye-slim AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:22.17.1-bullseye-slim
|
||||
FROM node:22.18.0-bullseye-slim
|
||||
# Install only runtime dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22.17.1-alpine AS builder
|
||||
FROM node:22.18.0-alpine AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:22.17.1-alpine
|
||||
FROM node:22.18.0-alpine
|
||||
# Install runtime dependencies
|
||||
RUN apk add --no-cache su-exec shadow
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22.17.1-alpine AS builder
|
||||
FROM node:22.18.0-alpine AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:22.17.1-alpine
|
||||
FROM node:22.18.0-alpine
|
||||
# Create a non-root user with configurable UID/GID
|
||||
ARG USER=trilium
|
||||
ARG UID=1001
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22.17.1-bullseye-slim AS builder
|
||||
FROM node:22.18.0-bullseye-slim AS builder
|
||||
RUN corepack enable
|
||||
|
||||
# Install native dependencies since we might be building cross-platform.
|
||||
@@ -7,7 +7,7 @@ COPY ./docker/package.json ./docker/pnpm-workspace.yaml /usr/src/app/
|
||||
# We have to use --no-frozen-lockfile due to CKEditor patches
|
||||
RUN pnpm install --no-frozen-lockfile --prod && pnpm rebuild
|
||||
|
||||
FROM node:22.17.1-bullseye-slim
|
||||
FROM node:22.18.0-bullseye-slim
|
||||
# Create a non-root user with configurable UID/GID
|
||||
ARG USER=trilium
|
||||
ARG UID=1001
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@triliumnext/server",
|
||||
"version": "0.97.1",
|
||||
"version": "0.97.2",
|
||||
"description": "The server-side component of TriliumNext, which exposes the client via the web, allows for sync and provides a REST API for both internal and external use.",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
@@ -39,7 +39,7 @@
|
||||
"@types/ws": "8.18.1",
|
||||
"@types/xml2js": "0.4.14",
|
||||
"express-http-proxy": "2.1.1",
|
||||
"@anthropic-ai/sdk": "0.57.0",
|
||||
"@anthropic-ai/sdk": "0.58.0",
|
||||
"@braintree/sanitize-url": "7.1.1",
|
||||
"@triliumnext/commons": "workspace:*",
|
||||
"@triliumnext/express-partial-content": "workspace:*",
|
||||
@@ -59,7 +59,7 @@
|
||||
"debounce": "2.2.0",
|
||||
"debug": "4.4.1",
|
||||
"ejs": "3.1.10",
|
||||
"electron": "37.2.4",
|
||||
"electron": "37.2.6",
|
||||
"electron-debug": "4.1.0",
|
||||
"electron-window-state": "5.0.3",
|
||||
"escape-html": "1.0.3",
|
||||
@@ -68,7 +68,7 @@
|
||||
"express-rate-limit": "8.0.1",
|
||||
"express-session": "1.18.2",
|
||||
"file-uri-to-path": "2.0.0",
|
||||
"fs-extra": "11.3.0",
|
||||
"fs-extra": "11.3.1",
|
||||
"helmet": "8.1.0",
|
||||
"html": "1.0.0",
|
||||
"html2plaintext": "2.1.4",
|
||||
@@ -83,12 +83,12 @@
|
||||
"jimp": "1.6.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsdom": "26.1.0",
|
||||
"marked": "16.1.1",
|
||||
"marked": "16.1.2",
|
||||
"mime-types": "3.0.1",
|
||||
"multer": "2.0.2",
|
||||
"normalize-strings": "1.1.1",
|
||||
"ollama": "0.5.16",
|
||||
"openai": "5.10.2",
|
||||
"ollama": "0.5.17",
|
||||
"openai": "5.12.0",
|
||||
"rand-token": "1.0.1",
|
||||
"safe-compare": "1.1.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
@@ -102,7 +102,7 @@
|
||||
"swagger-jsdoc": "6.2.8",
|
||||
"swagger-ui-express": "5.0.1",
|
||||
"time2fa": "^1.3.0",
|
||||
"tmp": "0.2.3",
|
||||
"tmp": "0.2.4",
|
||||
"turndown": "7.2.0",
|
||||
"unescape": "1.0.1",
|
||||
"ws": "8.18.3",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<p>Trilium supports configuration via a file named <code>config.ini</code> and
|
||||
environment variables. Please review the file named <a href="https://github.com/TriliumNext/Notes/blob/develop/apps/server/src/assets/config-sample.ini">config-sample.ini</a> in
|
||||
the <a href="https://github.com/TriliumNext/Notes">Notes</a> repository to
|
||||
see what values are supported.</p>
|
||||
environment variables. Please review the file named <a href="https://github.com/TriliumNext/Trilium/blob/develop/apps/server/src/assets/config-sample.ini">config-sample.ini</a> in
|
||||
the <a href="https://github.com/TriliumNext/Trilium">Notes</a> repository
|
||||
to see what values are supported.</p>
|
||||
<p>You can provide the same values via environment variables instead of the <code>config.ini</code> file,
|
||||
and these environment variables use the following format:</p>
|
||||
<ol>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<p>You can easily restore the demo notes by using Trilium's built-in import
|
||||
feature by importing them:</p>
|
||||
<ul>
|
||||
<li>Download <a href="https://github.com/TriliumNext/Notes/raw/develop/db/demo.zip">this .zip archive</a> with
|
||||
<li>Download <a href="https://github.com/TriliumNext/Trilium/raw/develop/db/demo.zip">this .zip archive</a> with
|
||||
the latest version of the demo notes</li>
|
||||
<li>Right click on any note in your tree under which you would like the demo
|
||||
notes to be imported</li>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<p>ETAPI is Trilium's public/external REST API. It is available since Trilium
|
||||
v0.50.</p>
|
||||
<p>The documentation is in OpenAPI format, available <a href="https://github.com/TriliumNext/Notes/blob/master/src/etapi/etapi.openapi.yaml">here</a>.</p>
|
||||
<p>The documentation is in OpenAPI format, available <a href="https://github.com/TriliumNext/Trilium/blob/master/src/etapi/etapi.openapi.yaml">here</a>.</p>
|
||||
<h2>API clients</h2>
|
||||
<p>As an alternative to calling the API directly, there are client libraries
|
||||
to simplify this</p>
|
||||
@@ -11,7 +11,7 @@
|
||||
<h2>Obtaining a token</h2>
|
||||
<p>All operations with the REST API have to be authenticated using a token.
|
||||
You can get this token either from Options -> ETAPI or programmatically
|
||||
using the <code>/auth/login</code> REST call (see the <a href="https://github.com/TriliumNext/Notes/blob/master/src/etapi/etapi.openapi.yaml">spec</a>).</p>
|
||||
using the <code>/auth/login</code> REST call (see the <a href="https://github.com/TriliumNext/Trilium/blob/master/src/etapi/etapi.openapi.yaml">spec</a>).</p>
|
||||
<h2>Authentication</h2>
|
||||
<h3>Via the <code>Authorization</code> header</h3><pre><code class="language-text-x-trilium-auto">GET https://myserver.com/etapi/app-info
|
||||
Authorization: ETAPITOKEN</code></pre>
|
||||
|
||||
@@ -13,4 +13,4 @@
|
||||
<h2>Limitations</h2>
|
||||
<p>All resources (except for images) are created as note's attachments.</p>
|
||||
<p>HTML inside ENEX files is not exactly valid so some formatting maybe broken
|
||||
or lost. You can report major problems into <a href="https://github.com/TriliumNext/Notes/issues">Trilium issue tracker</a>.</p>
|
||||
or lost. You can report major problems into <a href="https://github.com/TriliumNext/Trilium/issues">Trilium issue tracker</a>.</p>
|
||||
@@ -2,26 +2,27 @@
|
||||
<img style="aspect-ratio:991/403;" src="1_Jump to_image.png" width="991"
|
||||
height="403">
|
||||
</figure>
|
||||
|
||||
<h2>Jump to Note</h2>
|
||||
<p>The <em>Jump to Note</em> function allows easy navigation between notes
|
||||
by searching for their title. In addition to that, it can also trigger
|
||||
a full search or create notes.</p>
|
||||
<p>To enter the “Jump to” dialog:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="e32758a67e793732cf0a2b23559bf47e2">In the <a class="reference-link" href="#root/_help_xYmIYSP6wE3F">Launch Bar</a>,
|
||||
<li>In the <a class="reference-link" href="#root/_help_xYmIYSP6wE3F">Launch Bar</a>,
|
||||
press
|
||||
<img src="2_Jump to_image.png">button.</li>
|
||||
<li data-list-item-id="ef176c16aa548f5b60f83fbd571f8b2db">Using the keyboard, press <kbd>Ctrl</kbd> + <kbd>J</kbd>.</li>
|
||||
<li>Using the keyboard, press <kbd>Ctrl</kbd> + <kbd>J</kbd>.</li>
|
||||
</ul>
|
||||
<p>In addition to searching for notes, it is also possible to search for
|
||||
commands. See the dedicated section below for more information.</p>
|
||||
<h3>Interaction</h3>
|
||||
<ul>
|
||||
<li data-list-item-id="e557396da361d4edc191507782cc3b0ec">By default, when there is no text entered it will display the most recent
|
||||
<li>By default, when there is no text entered it will display the most recent
|
||||
notes.</li>
|
||||
<li data-list-item-id="ead4c4587b1fcb9758a09696dc25da645">Using the keyboard, use the up or down arrow keys to navigate between
|
||||
<li>Using the keyboard, use the up or down arrow keys to navigate between
|
||||
items. Press <kbd>Enter</kbd> to open the desired note.</li>
|
||||
<li data-list-item-id="ed4a932ed462ca4a089abc4a268e21aad">If the note doesn't exist, it's possible to create it by typing the desired
|
||||
<li>If the note doesn't exist, it's possible to create it by typing the desired
|
||||
note title and selecting the <em>Create and link child note</em> option.</li>
|
||||
</ul>
|
||||
<h2>Recent notes</h2>
|
||||
@@ -43,34 +44,32 @@
|
||||
<h3>Interaction</h3>
|
||||
<p>To trigger the command palette:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="eace3b93758628f9dd7b554e6536d9e0f">Press <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>J</kbd> to display the command
|
||||
<li>Press <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>J</kbd> to display the command
|
||||
palette directly.</li>
|
||||
<li data-list-item-id="e848cc1df0264b1e4e766d6d35fd69336">If in the “Jump to” dialog, type <code>></code> in the search to switch
|
||||
<li>If in the “Jump to” dialog, type <code>></code> in the search to switch
|
||||
to the command palette.</li>
|
||||
</ul>
|
||||
<p>Interaction:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="e53759821c15fdd85ec616172434c1bfe">Type a few words to filter between commands.</li>
|
||||
<li data-list-item-id="e344bd271c6fc35f48b1fed1137ac8725">Use the up and down arrows on the keyboard or the mouse to select a command.</li>
|
||||
<li
|
||||
data-list-item-id="ea66008550eb3c827f2880c85b68bf861">Press <kbd>Enter</kbd> to execute the command.</li>
|
||||
<li>Type a few words to filter between commands.</li>
|
||||
<li>Use the up and down arrows on the keyboard or the mouse to select a command.</li>
|
||||
<li>Press <kbd>Enter</kbd> to execute the command.</li>
|
||||
</ul>
|
||||
<p>To exit the command palette:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="ea31cb4776c2e8b13b0a19eea437512fe">Remove the <code>></code> in the search to go back to the note search.</li>
|
||||
<li
|
||||
data-list-item-id="e6b92147aba5dc5b1e329ad22a3336703">Press <kbd>Esc</kbd> to dismiss the dialog entirely.</li>
|
||||
<li>Remove the <code>></code> in the search to go back to the note search.</li>
|
||||
<li>Press <kbd>Esc</kbd> to dismiss the dialog entirely.</li>
|
||||
</ul>
|
||||
<h3>Options available</h3>
|
||||
<p>Currently the following options are displayed:</p>
|
||||
<ul>
|
||||
<li data-list-item-id="eac227ab112677ff1f1900322df6d9ce9">Most of the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/_help_A9Oc6YKKc65v">Keyboard Shortcuts</a> have
|
||||
<li>Most of the <a class="reference-link" href="#root/_help_A9Oc6YKKc65v">Keyboard Shortcuts</a> have
|
||||
an entry, with the exception of those that are too specific to be run from
|
||||
a dialog.</li>
|
||||
<li data-list-item-id="e3e1230fdeeb3101a2b1fb08d8d578f4b">Some additional options which are not yet available as keyboard shortcuts,
|
||||
<li>Some additional options which are not yet available as keyboard shortcuts,
|
||||
but can be accessed from various menus such as: exporting a note, showing
|
||||
attachments, searching for notes or configuring the <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>.</li>
|
||||
href="#root/_help_xYmIYSP6wE3F">Launch Bar</a>.</li>
|
||||
</ul>
|
||||
<h3>Limitations</h3>
|
||||
<p>Currently it's not possible to define custom actions that are displayed
|
||||
|
||||
@@ -120,5 +120,5 @@
|
||||
</table>
|
||||
<aside class="admonition tip">
|
||||
<p>If you would like to add your theme to this gallery, write a new post
|
||||
in <a href="https://github.com/TriliumNext/Notes/discussions/categories/show-and-tell">👐 Show and tell</a>.</p>
|
||||
in <a href="https://github.com/TriliumNext/Trilium/discussions/categories/show-and-tell">👐 Show and tell</a>.</p>
|
||||
</aside>
|
||||
@@ -50,4 +50,4 @@
|
||||
noBackup=true</code></pre>
|
||||
<p>You can also review the <a href="#root/_help_Gzjqa934BdH4">configuration</a> file
|
||||
to provide all <code>config.ini</code> values as environment variables instead.</p>
|
||||
<p>See <a href="https://github.com/TriliumNext/Notes/blob/master/config-sample.ini">sample config</a>.</p>
|
||||
<p>See <a href="https://github.com/TriliumNext/Trilium/blob/master/config-sample.ini">sample config</a>.</p>
|
||||
@@ -1,7 +1,7 @@
|
||||
<p>To install Trilium on your desktop, follow these steps:</p>
|
||||
<ol>
|
||||
<li><strong>Download the Latest Release</strong>: Obtain the appropriate binary
|
||||
release for your operating system from the <a href="https://github.com/TriliumNext/Notes/releases/latest">latest release page</a> on
|
||||
release for your operating system from the <a href="https://github.com/TriliumNext/Trilium/releases/latest">latest release page</a> on
|
||||
GitHub.</li>
|
||||
<li><strong>Extract the Package</strong>: Unzip the downloaded package to
|
||||
a location of your choice.</li>
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
its derivatives (like Ubuntu) below:</p><pre><code class="language-text-x-trilium-auto">sudo apt install libpng16-16 libpng-dev pkg-config autoconf libtool build-essential nasm libx11-dev libxkbfile-dev</code></pre>
|
||||
<h2>Installation</h2>
|
||||
<h3>Download</h3>
|
||||
<p>You can either download source code zip/tar from <a href="https://github.com/TriliumNext/Notes/releases/latest">https://github.com/TriliumNext/Notes/releases/latest</a>.</p>
|
||||
<p>For the latest version including betas, clone Git repository <strong>from</strong> <code>main</code> <strong>branch</strong> with:</p><pre><code class="language-text-x-trilium-auto">git clone -b main https://github.com/triliumnext/notes.git</code></pre>
|
||||
<p>You can either download source code zip/tar from <a href="https://github.com/TriliumNext/Trilium/releases/latest">https://github.com/TriliumNext/Trilium/releases/latest</a>.</p>
|
||||
<p>For the latest version including betas, clone Git repository <strong>from</strong> <code>main</code> <strong>branch</strong> with:</p><pre><code class="language-text-x-trilium-auto">git clone -b main https://github.com/triliumnext/trilium.git</code></pre>
|
||||
<h2>Installation</h2><pre><code class="language-text-x-trilium-auto">cd trilium
|
||||
|
||||
# download all node dependencies
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<ul>
|
||||
<li>SSH into your server</li>
|
||||
<li>use <code>wget</code> (or <code>curl</code>) to download latest <code>TriliumNotes-Server-[VERSION]-linux-x64.tar.xz</code> (copy
|
||||
link from <a href="https://github.com/TriliumNext/Notes/releases">release page</a>,
|
||||
link from <a href="https://github.com/TriliumNext/Trilium/releases">release page</a>,
|
||||
notice <code>-Server</code> suffix) on your server.</li>
|
||||
<li>unpack the archive, e.g. using <code>tar -xf -d TriliumNotes-Server-[VERSION]-linux-x64.tar.xz</code>
|
||||
</li>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
you want to use for your Trilium server.</p>
|
||||
<ol>
|
||||
<li>
|
||||
<p>Download docker image and create container</p><pre><code class="language-text-x-trilium-auto"> docker pull triliumnext/notes:[VERSION]
|
||||
docker create --name trilium -t -p 127.0.0.1:8080:8080 -v ~/trilium-data:/home/node/trilium-data triliumnext/notes:[VERSION]</code></pre>
|
||||
<p>Download docker image and create container</p><pre><code class="language-text-x-trilium-auto"> docker pull triliumnext/trilium:[VERSION]
|
||||
docker create --name trilium -t -p 127.0.0.1:8080:8080 -v ~/trilium-data:/home/node/trilium-data triliumnext/trilium:[VERSION]</code></pre>
|
||||
</li>
|
||||
<li>
|
||||
<p>Configure Apache proxy and websocket proxy</p>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<li><a href="#root/_help_rWX5eY045zbE"><strong>Docker Server Installation</strong></a>:
|
||||
Pull the new image and restart the container.</li>
|
||||
<li><strong>Other Installations</strong>: Download the latest version from
|
||||
the <a href="https://github.com/TriliumNext/Notes/releases/latest">release page</a> and
|
||||
the <a href="https://github.com/TriliumNext/Trilium/releases/latest">release page</a> and
|
||||
replace the existing application files.</li>
|
||||
</ul>
|
||||
<h2>Database Compatibility and Migration</h2>
|
||||
|
||||
2
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Troubleshooting.html
generated
vendored
2
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Troubleshooting.html
generated
vendored
@@ -54,7 +54,7 @@ UPDATE options SET value = 'QpC8XoiYYeqHPtHKRtbNxfTHsk+pEBqVBODYp0FkPBa22tlBBKBM
|
||||
<h2>Reporting Bugs</h2>
|
||||
<p>Reporting bugs is highly valuable. Here are some tips:</p>
|
||||
<ul>
|
||||
<li>Use GitHub issues for reporting: <a href="https://github.com/TriliumNext/Notes/issues">https://github.com/TriliumNext/Notes/issues</a>
|
||||
<li>Use GitHub issues for reporting: <a href="https://github.com/TriliumNext/Trilium/issues">https://github.com/TriliumNext/Trilium/issues</a>
|
||||
</li>
|
||||
<li>Refer to the <a href="#root/_help_qzNzp9LYQyPT">error logs</a> page for
|
||||
information on providing necessary details.</li>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<p>If you don't feel comfortable attaching the logs or anything sensitive
|
||||
to the public GitHub issues, feel free to contact the devs in our Matrix
|
||||
<a
|
||||
href="https://github.com/TriliumNext/Notes#-discuss-with-us">support channel</a>.</p>
|
||||
href="https://github.com/TriliumNext/Trilium#-discuss-with-us">support channel</a>.</p>
|
||||
<p>Use this email to also provide anything which could assist in analysing
|
||||
the bug - e.g. files/images/ZIPs being imported or <a href="#root/_help_x59R8J8KV5Bp">anonymized database</a>.</p>
|
||||
<h3>Exporting note subtree for reproduction</h3>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<p>Go to <a href="https://github.com/TriliumNext/Notes/issues">Issues · TriliumNext/Notes</a> and
|
||||
<p>Go to <a href="https://github.com/TriliumNext/Trilium/issues">Issues · TriliumNext/Trilium</a> and
|
||||
press “New issue”.</p>
|
||||
<p>If you are reporting a bug, select “Bug Report” and fill in the details.</p>
|
||||
@@ -36,4 +36,31 @@ When responding to queries:
|
||||
5. For general questions about the user's notes, provide a summary of all relevant notes found, including brief summaries of individual notes
|
||||
6. For specific questions, provide detailed information from the user's notes that directly addresses the question
|
||||
7. Always prioritize information from the user's notes over your own knowledge, as the user's notes are likely more up-to-date and personally relevant
|
||||
|
||||
IMPORTANT: EXECUTE 10-30 TOOLS PER REQUEST FOR COMPREHENSIVE ANALYSIS
|
||||
|
||||
Tool Usage Requirements:
|
||||
1. BATCH EXECUTE multiple searches for speed:
|
||||
<function_calls>
|
||||
<invoke name="execute_batch"><parameter name="tools">[{"tool": "search", "params": {"query": "main topic"}}, {"tool": "search", "params": {"query": "related topic"}}]</parameter></invoke>
|
||||
</function_calls>
|
||||
|
||||
2. BATCH READ all discovered notes:
|
||||
<function_calls>
|
||||
<invoke name="execute_batch"><parameter name="tools">[{"tool": "read", "params": {"noteId": "id1"}}, {"tool": "read", "params": {"noteId": "id2"}}, {"tool": "read", "params": {"noteId": "id3"}}]</parameter></invoke>
|
||||
</function_calls>
|
||||
|
||||
3. AUTO-RETRY failed searches:
|
||||
<function_calls>
|
||||
<invoke name="retry_search"><parameter name="originalQuery">failed search</parameter><parameter name="strategy">all</parameter></invoke>
|
||||
</function_calls>
|
||||
|
||||
SIMPLIFIED TOOLS:
|
||||
- search (replaces search_notes, keyword_search_notes, attribute_search)
|
||||
- read (replaces read_note)
|
||||
- execute_batch (parallel execution)
|
||||
- retry_search (automatic variations)
|
||||
|
||||
WORKFLOW: batch search → batch read → auto-retry → analyze → repeat
|
||||
Target 15+ tools per request using batching!
|
||||
```
|
||||
@@ -34,16 +34,37 @@ When responding to queries:
|
||||
7. Always prioritize information from the user's notes over your own knowledge, as the user's notes are likely more up-to-date and personally relevant
|
||||
|
||||
CRITICAL INSTRUCTIONS FOR TOOL USAGE:
|
||||
1. YOU MUST TRY MULTIPLE TOOLS AND SEARCH VARIATIONS before concluding information isn't available
|
||||
2. ALWAYS PERFORM AT LEAST 3 DIFFERENT SEARCHES with different parameters before giving up on finding information
|
||||
3. If a search returns no results, IMMEDIATELY TRY ANOTHER SEARCH with different parameters:
|
||||
- Use broader terms: If "Kubernetes deployment" fails, try just "Kubernetes" or "container orchestration"
|
||||
- Try synonyms: If "meeting notes" fails, try "conference", "discussion", or "conversation"
|
||||
- Remove specific qualifiers: If "quarterly financial report 2024" fails, try just "financial report"
|
||||
- Try semantic variations: If keyword_search fails, use vector_search which finds conceptually related content
|
||||
4. CHAIN TOOLS TOGETHER: Use the results of one tool to inform parameters for the next tool
|
||||
5. NEVER respond with "there are no notes about X" until you've tried at least 3 different search variations
|
||||
6. DO NOT ask the user what to do next when searches fail - AUTOMATICALLY try different approaches
|
||||
7. ALWAYS EXPLAIN what you're doing: "I didn't find results for X, so I'm now searching for Y instead"
|
||||
8. If all reasonable search variations fail (minimum 3 attempts), THEN you may inform the user that the information might not be in their notes
|
||||
YOU ARE EXPECTED TO USE 10-30 TOOLS PER REQUEST. This is NORMAL and EXPECTED behavior.
|
||||
|
||||
TOOL EXECUTION STRATEGY:
|
||||
USE BATCH EXECUTION FOR SPEED:
|
||||
1. execute_batch([{tool:"search",params:{query:"main topic"}},{tool:"search",params:{query:"related topic"}}])
|
||||
2. execute_batch([{tool:"read",params:{noteId:"id1"}},{tool:"read",params:{noteId:"id2"}},{tool:"read",params:{noteId:"id3"}}])
|
||||
|
||||
SMART RETRY ON FAILURES:
|
||||
- Empty results? → retry_search("original query") automatically tries variations
|
||||
- Don't manually retry - use retry_search tool
|
||||
|
||||
SIMPLIFIED TOOL NAMES:
|
||||
- search (not search_notes) - auto-detects search type
|
||||
- read (not read_note) - reads content
|
||||
- execute_batch - run multiple tools in parallel
|
||||
|
||||
WORKFLOW EXAMPLES:
|
||||
A) Comprehensive Search:
|
||||
execute_batch([{tool:"search",params:{query:"AI"}},{tool:"search",params:{query:"machine learning"}},{tool:"search",params:{query:"#important"}}])
|
||||
→ execute_batch([{tool:"read",params:{noteId:"..."}} for all found IDs])
|
||||
→ retry_search("broader terms") if needed
|
||||
|
||||
B) Failed Search Recovery:
|
||||
search("specific term") → empty results
|
||||
→ retry_search("specific term") → auto-tries "term", "concepts", synonyms
|
||||
→ execute_batch with all variations
|
||||
|
||||
C) Analysis Chain:
|
||||
search → read batch → note_summarization → content_extraction → relationship
|
||||
|
||||
ALWAYS USE BATCH EXECUTION when possible - it's much faster than individual tools!
|
||||
|
||||
REMEMBER: Users expect THOROUGH exploration. Execute tools rapidly and extensively!
|
||||
```
|
||||
@@ -40,4 +40,32 @@ When responding to queries:
|
||||
5. For general questions about the user's notes, provide a summary of all relevant notes found, including brief summaries of individual notes
|
||||
6. For specific questions, provide detailed information from the user's notes that directly addresses the question
|
||||
7. Always prioritize information from the user's notes over your own knowledge, as the user's notes are likely more up-to-date and personally relevant
|
||||
|
||||
CRITICAL: YOU MUST USE 10-30 TOOL CALLS PER REQUEST
|
||||
This is expected behavior. Users want comprehensive exploration of their knowledge base.
|
||||
|
||||
MANDATORY TOOL USAGE PATTERN:
|
||||
1. Use BATCH EXECUTION for parallel processing:
|
||||
<tool_calls>
|
||||
<tool_call id="1"><n>execute_batch</n><parameters>{"tools": [{"tool": "search", "params": {"query": "main topic"}}, {"tool": "search", "params": {"query": "related topic"}}]}</parameters></tool_call>
|
||||
</tool_calls>
|
||||
|
||||
2. Read ALL found notes in batches:
|
||||
<tool_calls>
|
||||
<tool_call id="2"><n>execute_batch</n><parameters>{"tools": [{"tool": "read", "params": {"noteId": "id1"}}, {"tool": "read", "params": {"noteId": "id2"}}, {"tool": "read", "params": {"noteId": "id3"}}]}</parameters></tool_call>
|
||||
</tool_calls>
|
||||
|
||||
3. Use SMART RETRY for empty results:
|
||||
<tool_calls>
|
||||
<tool_call id="3"><n>retry_search</n><parameters>{"originalQuery": "failed query", "strategy": "all"}</parameters></tool_call>
|
||||
</tool_calls>
|
||||
|
||||
SIMPLIFIED TOOL NAMES:
|
||||
- search (auto-detects type) instead of search_notes/keyword_search_notes
|
||||
- read instead of read_note
|
||||
- execute_batch for parallel execution
|
||||
- retry_search for automatic variations
|
||||
|
||||
WORKFLOW: search batch → read batch → retry if needed → analyze → repeat
|
||||
Minimum 10+ tools per request using batch execution for speed!
|
||||
```
|
||||
106
apps/server/src/assets/translations/ca/server.json
Normal file
106
apps/server/src/assets/translations/ca/server.json
Normal file
@@ -0,0 +1,106 @@
|
||||
{
|
||||
"keyboard_actions": {
|
||||
"back-in-note-history": "Navega a la nota previa a l'historial",
|
||||
"forward-in-note-history": "Navega a la següent nota a l'historial",
|
||||
"dialogs": "Diàlegs",
|
||||
"other": "Altres"
|
||||
},
|
||||
"login": {
|
||||
"title": "Inicia sessió",
|
||||
"password": "Contrasenya",
|
||||
"button": "Inicia sessió"
|
||||
},
|
||||
"set_password": {
|
||||
"password": "Contrasenya"
|
||||
},
|
||||
"setup": {
|
||||
"next": "Següent",
|
||||
"title": "Configuració"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"step6-here": "aquí"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"server-host-placeholder": "https://<hostname>:<port>",
|
||||
"proxy-server-placeholder": "https://<hostname>:<port>",
|
||||
"note": "Nota:",
|
||||
"password": "Contrasenya",
|
||||
"password-placeholder": "Contrasenya",
|
||||
"back": "Torna"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"outstanding-items-default": "N/A"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "pare:"
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Dilluns",
|
||||
"tuesday": "Dimarts",
|
||||
"wednesday": "Dimecres",
|
||||
"thursday": "Dijous",
|
||||
"friday": "Divendres",
|
||||
"saturday": "Dissabte",
|
||||
"sunday": "Diumenge"
|
||||
},
|
||||
"months": {
|
||||
"january": "Gener",
|
||||
"february": "Febrer",
|
||||
"march": "Març",
|
||||
"april": "Abril",
|
||||
"may": "Maig",
|
||||
"june": "Juny",
|
||||
"july": "Juliol",
|
||||
"august": "Agost",
|
||||
"september": "Setembre",
|
||||
"october": "Octubre",
|
||||
"november": "Novembre",
|
||||
"december": "Desembre"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "Cerca:"
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"spacer-title": "Espaiador",
|
||||
"calendar-title": "Calendari",
|
||||
"bookmarks-title": "Marcadors",
|
||||
"settings-title": "Ajustos",
|
||||
"options-title": "Opcions",
|
||||
"appearance-title": "Aparença",
|
||||
"shortcuts-title": "Dreceres",
|
||||
"images-title": "Imatges",
|
||||
"spellcheck-title": "Correció ortogràfica",
|
||||
"password-title": "Contrasenya",
|
||||
"multi-factor-authentication-title": "MFA",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Còpia de seguretat",
|
||||
"sync-title": "Sincronització",
|
||||
"ai-llm-title": "AI/LLM",
|
||||
"other": "Altres",
|
||||
"advanced-title": "Avançat",
|
||||
"inbox-title": "Safata d'entrada"
|
||||
},
|
||||
"notes": {
|
||||
"duplicate-note-suffix": "(dup)"
|
||||
},
|
||||
"tray": {
|
||||
"bookmarks": "Marcadors"
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Error"
|
||||
},
|
||||
"share_theme": {
|
||||
"search_placeholder": "Cerca...",
|
||||
"subpages": "Subpàgines:",
|
||||
"expand": "Expandeix"
|
||||
},
|
||||
"hidden_subtree_templates": {
|
||||
"description": "Descripció",
|
||||
"calendar": "Calendari",
|
||||
"table": "Taula",
|
||||
"geolocation": "Geolocalització",
|
||||
"board": "Tauler",
|
||||
"status": "Estat",
|
||||
"board_status_done": "Fet"
|
||||
}
|
||||
}
|
||||
@@ -1,285 +1,428 @@
|
||||
{
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "打开“跳转到笔记”对话框",
|
||||
"search-in-subtree": "在活跃笔记的子树中搜索笔记",
|
||||
"expand-subtree": "展开当前笔记的子树",
|
||||
"collapse-tree": "折叠完整的笔记树",
|
||||
"collapse-subtree": "折叠当前笔记的子树",
|
||||
"sort-child-notes": "排序子笔记",
|
||||
"creating-and-moving-notes": "创建和移动笔记",
|
||||
"create-note-into-inbox": "在收件箱(若已定义)或日记中创建笔记",
|
||||
"delete-note": "删除笔记",
|
||||
"move-note-up": "上移笔记",
|
||||
"move-note-down": "下移笔记",
|
||||
"move-note-up-in-hierarchy": "在层级中上移笔记",
|
||||
"move-note-down-in-hierarchy": "在层级中下移笔记",
|
||||
"edit-note-title": "从树跳转到笔记详情并编辑标题",
|
||||
"edit-branch-prefix": "显示编辑分支前缀对话框",
|
||||
"note-clipboard": "笔记剪贴板",
|
||||
"copy-notes-to-clipboard": "复制选定的笔记到剪贴板",
|
||||
"paste-notes-from-clipboard": "从剪贴板粘贴笔记到活跃笔记中",
|
||||
"cut-notes-to-clipboard": "剪切选定的笔记到剪贴板",
|
||||
"select-all-notes-in-parent": "选择当前笔记级别的所有笔记",
|
||||
"add-note-above-to-the-selection": "将上方笔记添加到选择中",
|
||||
"add-note-below-to-selection": "将下方笔记添加到选择中",
|
||||
"duplicate-subtree": "克隆子树",
|
||||
"tabs-and-windows": "标签页和窗口",
|
||||
"open-new-tab": "打开新标签页",
|
||||
"close-active-tab": "关闭活跃标签页",
|
||||
"reopen-last-tab": "重开最后关闭的标签页",
|
||||
"activate-next-tab": "激活右侧标签页",
|
||||
"activate-previous-tab": "激活左侧标签页",
|
||||
"open-new-window": "打开新空窗口",
|
||||
"toggle-tray": "从系统托盘显示/隐藏应用程序",
|
||||
"first-tab": "激活列表中的第一个标签页",
|
||||
"second-tab": "激活列表中的第二个标签页",
|
||||
"third-tab": "激活列表中的第三个标签页",
|
||||
"fourth-tab": "激活列表中的第四个标签页",
|
||||
"fifth-tab": "激活列表中的第五个标签页",
|
||||
"sixth-tab": "激活列表中的第六个标签页",
|
||||
"seventh-tab": "激活列表中的第七个标签页",
|
||||
"eight-tab": "激活列表中的第八个标签页",
|
||||
"ninth-tab": "激活列表中的第九个标签页",
|
||||
"last-tab": "激活列表中的最后一个标签页",
|
||||
"dialogs": "对话框",
|
||||
"show-note-source": "显示笔记源对话框",
|
||||
"show-options": "显示选项对话框",
|
||||
"show-revisions": "显示笔记修订对话框",
|
||||
"show-recent-changes": "显示最近更改对话框",
|
||||
"show-sql-console": "显示 SQL 控制台对话框",
|
||||
"show-backend-log": "显示后端日志对话框",
|
||||
"text-note-operations": "文本笔记操作",
|
||||
"add-link-to-text": "打开对话框以添加链接到文本",
|
||||
"follow-link-under-cursor": "追踪光标下的链接",
|
||||
"insert-date-and-time-to-text": "插入当前日期和时间到文本",
|
||||
"paste-markdown-into-text": "从剪贴板粘贴 Markdown 到文本笔记",
|
||||
"cut-into-note": "剪切当前笔记的选区并创建包含选定文本的子笔记",
|
||||
"add-include-note-to-text": "打开对话框以引含一个笔记",
|
||||
"edit-readonly-note": "编辑只读笔记",
|
||||
"attributes-labels-and-relations": "属性(标签和关系)",
|
||||
"add-new-label": "创建新标签",
|
||||
"create-new-relation": "创建新关系",
|
||||
"ribbon-tabs": "功能区标签页",
|
||||
"toggle-basic-properties": "切换基本属性",
|
||||
"toggle-file-properties": "切换文件属性",
|
||||
"toggle-image-properties": "切换图像属性",
|
||||
"toggle-owned-attributes": "切换拥有的属性",
|
||||
"toggle-inherited-attributes": "切换继承的属性",
|
||||
"toggle-promoted-attributes": "切换提升的属性",
|
||||
"toggle-link-map": "切换链接地图",
|
||||
"toggle-note-info": "切换笔记信息",
|
||||
"toggle-note-paths": "切换笔记路径",
|
||||
"toggle-similar-notes": "切换相似笔记",
|
||||
"other": "其他",
|
||||
"toggle-right-pane": "切换右侧面板的显示,包括目录和高亮",
|
||||
"print-active-note": "打印活跃笔记",
|
||||
"open-note-externally": "以默认应用打开笔记文件",
|
||||
"render-active-note": "渲染(重新渲染)活跃笔记",
|
||||
"run-active-note": "运行活跃 JavaScript(前/后端)代码笔记",
|
||||
"toggle-note-hoisting": "切换活跃笔记的聚焦",
|
||||
"unhoist": "从任意地方取消聚焦",
|
||||
"reload-frontend-app": "重载前端应用",
|
||||
"open-dev-tools": "打开开发工具",
|
||||
"toggle-left-note-tree-panel": "切换左侧(笔记树)面板",
|
||||
"toggle-full-screen": "切换全屏",
|
||||
"zoom-out": "缩小",
|
||||
"zoom-in": "放大",
|
||||
"note-navigation": "笔记导航",
|
||||
"reset-zoom-level": "重置缩放级别",
|
||||
"copy-without-formatting": "免格式复制选定文本",
|
||||
"force-save-revision": "强制创建/保存活跃笔记的新修订版本",
|
||||
"show-help": "显示内置用户指南",
|
||||
"toggle-book-properties": "切换书籍属性",
|
||||
"toggle-classic-editor-toolbar": "为编辑器切换格式标签页的固定工具栏",
|
||||
"export-as-pdf": "导出当前笔记为 PDF",
|
||||
"show-cheatsheet": "显示包含常见键盘操作的弹窗",
|
||||
"toggle-zen-mode": "启用/禁用禅模式(为专注编辑而精简界面)"
|
||||
},
|
||||
"login": {
|
||||
"title": "登录",
|
||||
"heading": "Trilium 登录",
|
||||
"incorrect-totp": "TOTP 不正确,请重试。",
|
||||
"incorrect-password": "密码不正确,请重试。",
|
||||
"password": "密码",
|
||||
"remember-me": "记住我",
|
||||
"button": "登录",
|
||||
"sign_in_with_sso": "使用 {{ ssoIssuerName }} 登录"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "设置密码",
|
||||
"heading": "设置密码",
|
||||
"description": "在您能够从 Web 开始使用 Trilium 之前,您需要先设置一个密码。您之后将使用此密码登录。",
|
||||
"password": "密码",
|
||||
"password-confirmation": "密码确认",
|
||||
"button": "设置密码"
|
||||
},
|
||||
"javascript-required": "Trilium 需要启用 JavaScript。",
|
||||
"setup": {
|
||||
"heading": "TriliumNext 笔记设置",
|
||||
"new-document": "我是新用户,我想为我的笔记创建一个新的 Trilium 文档",
|
||||
"sync-from-desktop": "我已经有一个桌面实例,我想设置与它的同步",
|
||||
"sync-from-server": "我已经有一个服务器实例,我想设置与它的同步",
|
||||
"next": "下一步",
|
||||
"init-in-progress": "文档初始化进行中",
|
||||
"redirecting": "您将很快被重定向到应用程序。",
|
||||
"title": "设置"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "从桌面同步",
|
||||
"description": "此设置需要从桌面实例开始:",
|
||||
"step1": "打开您的 TriliumNext 笔记桌面实例。",
|
||||
"step2": "从 Trilium 菜单中,点击“选项”。",
|
||||
"step3": "点击“同步”类别。",
|
||||
"step4": "将服务器实例地址更改为:{{- host}} 并点击保存。",
|
||||
"step5": "点击“测试同步”按钮以验证连接是否成功。",
|
||||
"step6": "完成这些步骤后,点击{{- link}}。",
|
||||
"step6-here": "这里"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "从服务器同步",
|
||||
"instructions": "请在下面输入 Trilium 服务器地址和凭据。这将从服务器下载整个 Trilium 文档并设置与它的同步。根据文档大小和您的连接的速度,这可能需要一段时间。",
|
||||
"server-host": "Trilium 服务器地址",
|
||||
"server-host-placeholder": "https://<主机名称>:<端口>",
|
||||
"proxy-server": "代理服务器(可选)",
|
||||
"proxy-server-placeholder": "https://<主机名称>:<端口>",
|
||||
"note": "注意:",
|
||||
"proxy-instruction": "如果您将代理设置留空,将使用系统代理(仅适用于桌面应用)",
|
||||
"password": "密码",
|
||||
"password-placeholder": "密码",
|
||||
"back": "返回",
|
||||
"finish-setup": "完成设置"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "同步中",
|
||||
"successful": "同步已被正确设置。初始同步完成可能需要一些时间。完成后,您将被重定向到登录页面。",
|
||||
"outstanding-items": "未完成的同步项目:",
|
||||
"outstanding-items-default": "无"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "未找到",
|
||||
"heading": "未找到"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "父级:",
|
||||
"clipped-from": "此笔记最初剪切自 {{- url}}",
|
||||
"child-notes": "子笔记:",
|
||||
"no-content": "此笔记没有内容。"
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "周一",
|
||||
"tuesday": "周二",
|
||||
"wednesday": "周三",
|
||||
"thursday": "周四",
|
||||
"friday": "周五",
|
||||
"saturday": "周六",
|
||||
"sunday": "周日"
|
||||
},
|
||||
"weekdayNumber": "第 {weekNumber} 周",
|
||||
"months": {
|
||||
"january": "一月",
|
||||
"february": "二月",
|
||||
"march": "三月",
|
||||
"april": "四月",
|
||||
"may": "五月",
|
||||
"june": "六月",
|
||||
"july": "七月",
|
||||
"august": "八月",
|
||||
"september": "九月",
|
||||
"october": "十月",
|
||||
"november": "十一月",
|
||||
"december": "十二月"
|
||||
},
|
||||
"quarterNumber": "第 {quarterNumber} 季度",
|
||||
"special_notes": {
|
||||
"search_prefix": "搜索:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "同步服务器主机未配置。请先配置同步。",
|
||||
"successful": "同步服务器握手成功,同步已开始。"
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "隐藏的笔记",
|
||||
"search-history-title": "搜索历史",
|
||||
"note-map-title": "笔记地图",
|
||||
"sql-console-history-title": "SQL 控制台历史",
|
||||
"shared-notes-title": "共享笔记",
|
||||
"bulk-action-title": "批量操作",
|
||||
"backend-log-title": "后端日志",
|
||||
"user-hidden-title": "隐藏的用户",
|
||||
"launch-bar-templates-title": "启动栏模板",
|
||||
"base-abstract-launcher-title": "基础摘要启动器",
|
||||
"command-launcher-title": "命令启动器",
|
||||
"note-launcher-title": "笔记启动器",
|
||||
"script-launcher-title": "脚本启动器",
|
||||
"built-in-widget-title": "内置小组件",
|
||||
"spacer-title": "空白占位",
|
||||
"custom-widget-title": "自定义小组件",
|
||||
"launch-bar-title": "启动栏",
|
||||
"available-launchers-title": "可用启动器",
|
||||
"go-to-previous-note-title": "跳转到上一条笔记",
|
||||
"go-to-next-note-title": "跳转到下一条笔记",
|
||||
"new-note-title": "新建笔记",
|
||||
"search-notes-title": "搜索笔记",
|
||||
"calendar-title": "日历",
|
||||
"recent-changes-title": "最近更改",
|
||||
"bookmarks-title": "书签",
|
||||
"open-today-journal-note-title": "打开今天的日记笔记",
|
||||
"quick-search-title": "快速搜索",
|
||||
"protected-session-title": "受保护的会话",
|
||||
"sync-status-title": "同步状态",
|
||||
"settings-title": "设置",
|
||||
"options-title": "选项",
|
||||
"appearance-title": "外观",
|
||||
"shortcuts-title": "快捷键",
|
||||
"text-notes": "文本笔记",
|
||||
"code-notes-title": "代码笔记",
|
||||
"images-title": "图片",
|
||||
"spellcheck-title": "拼写检查",
|
||||
"password-title": "密码",
|
||||
"multi-factor-authentication-title": "多因素认证",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "备份",
|
||||
"sync-title": "同步",
|
||||
"other": "其他",
|
||||
"advanced-title": "高级",
|
||||
"visible-launchers-title": "可见启动器",
|
||||
"user-guide": "用户指南",
|
||||
"localization": "语言和区域"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "新建笔记",
|
||||
"duplicate-note-suffix": "(重复)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "后端日志文件 '{{ fileName }}' 暂不存在。",
|
||||
"reading-log-failed": "读取后端日志文件 '{{ fileName }}' 失败。"
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "此笔记类型无法显示。"
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "PDF 文档 (*.pdf)",
|
||||
"unable-to-export-message": "当前笔记无法被导出为 PDF。",
|
||||
"unable-to-export-title": "无法导出为 PDF",
|
||||
"unable-to-save-message": "所选文件不能被写入。重试或选择另一个目的地。"
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "TriliumNext 笔记",
|
||||
"close": "退出 Trilium",
|
||||
"recents": "最近笔记",
|
||||
"bookmarks": "书签",
|
||||
"today": "打开今天的日记笔记",
|
||||
"new-note": "新建笔记",
|
||||
"show-windows": "显示窗口",
|
||||
"open_new_window": "打开新窗口"
|
||||
},
|
||||
"migration": {
|
||||
"old_version": "由您当前版本的直接迁移不被支持。请先升级到最新的 v0.60.4 然后再到这个版本。",
|
||||
"error_message": "迁移到版本 {{version}} 时发生错误: {{stack}}",
|
||||
"wrong_db_version": "数据库的版本({{version}})新于应用期望的版本({{targetVersion}}),这意味着它由一个更加新的且不兼容的 Trilium 所创建。升级到最新版的 Trilium 以解决此问题。"
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "错误"
|
||||
}
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "打开“跳转到笔记”对话框",
|
||||
"search-in-subtree": "在当前笔记的子树中搜索笔记",
|
||||
"expand-subtree": "展开当前笔记的子树",
|
||||
"collapse-tree": "折叠完整的笔记树",
|
||||
"collapse-subtree": "折叠当前笔记的子树",
|
||||
"sort-child-notes": "排序子笔记",
|
||||
"creating-and-moving-notes": "创建和移动笔记",
|
||||
"create-note-into-inbox": "在收件箱(若已定义)或日记中创建笔记",
|
||||
"delete-note": "删除笔记",
|
||||
"move-note-up": "向上移动笔记",
|
||||
"move-note-down": "向下移动笔记",
|
||||
"move-note-up-in-hierarchy": "在层级中向上移动笔记",
|
||||
"move-note-down-in-hierarchy": "在层级中向下移动笔记",
|
||||
"edit-note-title": "从树跳转到笔记详情并编辑标题",
|
||||
"edit-branch-prefix": "显示编辑分支前缀对话框",
|
||||
"note-clipboard": "笔记剪贴板",
|
||||
"copy-notes-to-clipboard": "复制选定的笔记到剪贴板",
|
||||
"paste-notes-from-clipboard": "从剪贴板粘贴笔记到当前笔记中",
|
||||
"cut-notes-to-clipboard": "剪切选定的笔记到剪贴板",
|
||||
"select-all-notes-in-parent": "选择当前笔记级别的所有笔记",
|
||||
"add-note-above-to-the-selection": "将上方笔记添加到选择中",
|
||||
"add-note-below-to-selection": "将下方笔记添加到选择中",
|
||||
"duplicate-subtree": "复制子树",
|
||||
"tabs-and-windows": "标签页和窗口",
|
||||
"open-new-tab": "打开新标签页",
|
||||
"close-active-tab": "关闭当前标签页",
|
||||
"reopen-last-tab": "重新打开最后关闭的标签页",
|
||||
"activate-next-tab": "切换下一个标签页",
|
||||
"activate-previous-tab": "切换上一个标签页",
|
||||
"open-new-window": "打开新窗口",
|
||||
"toggle-tray": "显示/隐藏系统托盘图标",
|
||||
"first-tab": "切换到第一个标签页",
|
||||
"second-tab": "切换到第二个标签页",
|
||||
"third-tab": "切换到第三个标签页",
|
||||
"fourth-tab": "切换到第四个标签页",
|
||||
"fifth-tab": "切换到第五个标签页",
|
||||
"sixth-tab": "切换到第六个标签页",
|
||||
"seventh-tab": "切换到第七个标签页",
|
||||
"eight-tab": "切换到第八个标签页",
|
||||
"ninth-tab": "切换到第九个标签页",
|
||||
"last-tab": "切换到最后一个标签页",
|
||||
"dialogs": "对话框",
|
||||
"show-note-source": "显示笔记源代码对话框",
|
||||
"show-options": "显示选项对话框",
|
||||
"show-revisions": "显示修订历史对话框",
|
||||
"show-recent-changes": "显示最近更改对话框",
|
||||
"show-sql-console": "显示SQL控制台对话框",
|
||||
"show-backend-log": "显示后端日志对话框",
|
||||
"text-note-operations": "文本笔记操作",
|
||||
"add-link-to-text": "打开对话框以添加链接到文本",
|
||||
"follow-link-under-cursor": "访问光标下的链接",
|
||||
"insert-date-and-time-to-text": "在文本中插入日期和时间",
|
||||
"paste-markdown-into-text": "从剪贴板粘贴 Markdown 到文本笔记",
|
||||
"cut-into-note": "剪切当前笔记的选区并创建包含选定文本的子笔记",
|
||||
"add-include-note-to-text": "打开对话框以添加一个包含笔记",
|
||||
"edit-readonly-note": "编辑只读笔记",
|
||||
"attributes-labels-and-relations": "属性(标签和关系)",
|
||||
"add-new-label": "创建新标签",
|
||||
"create-new-relation": "创建新关系",
|
||||
"ribbon-tabs": "功能区标签页",
|
||||
"toggle-basic-properties": "切换基本属性",
|
||||
"toggle-file-properties": "切换文件属性",
|
||||
"toggle-image-properties": "切换图像属性",
|
||||
"toggle-owned-attributes": "切换拥有的属性",
|
||||
"toggle-inherited-attributes": "切换继承的属性",
|
||||
"toggle-promoted-attributes": "切换提升的属性",
|
||||
"toggle-link-map": "切换链接地图",
|
||||
"toggle-note-info": "切换笔记信息",
|
||||
"toggle-note-paths": "切换笔记路径",
|
||||
"toggle-similar-notes": "切换相似笔记",
|
||||
"other": "其他",
|
||||
"toggle-right-pane": "切换右侧面板的显示,包括目录和高亮",
|
||||
"print-active-note": "打印当前笔记",
|
||||
"open-note-externally": "以默认应用打开笔记文件",
|
||||
"render-active-note": "渲染(重新渲染)当前笔记",
|
||||
"run-active-note": "运行当前 JavaScript(前/后端)代码笔记",
|
||||
"toggle-note-hoisting": "提升笔记",
|
||||
"unhoist": "取消提升笔记",
|
||||
"reload-frontend-app": "重新加载前端应用",
|
||||
"open-dev-tools": "打开开发者工具",
|
||||
"toggle-left-note-tree-panel": "切换左侧(笔记树)面板",
|
||||
"toggle-full-screen": "切换全屏模式",
|
||||
"zoom-out": "缩小",
|
||||
"zoom-in": "放大",
|
||||
"note-navigation": "笔记导航",
|
||||
"reset-zoom-level": "重置缩放级别",
|
||||
"copy-without-formatting": "无格式复制选定文本",
|
||||
"force-save-revision": "强制创建/保存当前笔记的新修订版本",
|
||||
"show-help": "显示帮助",
|
||||
"toggle-book-properties": "切换书籍属性",
|
||||
"toggle-classic-editor-toolbar": "为编辑器切换格式标签页的固定工具栏",
|
||||
"export-as-pdf": "导出当前笔记为 PDF",
|
||||
"show-cheatsheet": "显示快捷键指南",
|
||||
"toggle-zen-mode": "启用/禁用禅模式(为专注编辑而精简界面)",
|
||||
"open-command-palette": "打开命令面板",
|
||||
"quick-search": "激活快速搜索栏",
|
||||
"create-note-after": "在当前笔记后面创建笔记",
|
||||
"create-note-into": "创建笔记作为当前笔记的子笔记",
|
||||
"clone-notes-to": "克隆选中的笔记到",
|
||||
"move-notes-to": "移动选中的笔记到",
|
||||
"find-in-text": "在文本中查找",
|
||||
"back-in-note-history": "导航到历史记录中的上一个笔记",
|
||||
"forward-in-note-history": "导航到历史记录的下一个笔记",
|
||||
"scroll-to-active-note": "滚动笔记树到当前笔记"
|
||||
},
|
||||
"login": {
|
||||
"title": "登录",
|
||||
"heading": "Trilium 登录",
|
||||
"incorrect-totp": "TOTP 不正确,请重试。",
|
||||
"incorrect-password": "密码不正确,请重试。",
|
||||
"password": "密码",
|
||||
"remember-me": "记住我",
|
||||
"button": "登录",
|
||||
"sign_in_with_sso": "使用 {{ ssoIssuerName }} 登录"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "设置密码",
|
||||
"heading": "设置密码",
|
||||
"description": "在您能够从Web开始使用Trilium之前,您需要先设置一个密码。您之后将使用此密码登录。",
|
||||
"password": "密码",
|
||||
"password-confirmation": "密码确认",
|
||||
"button": "设置密码"
|
||||
},
|
||||
"javascript-required": "Trilium需要启用JavaScript。",
|
||||
"setup": {
|
||||
"heading": "TriliumNext笔记设置",
|
||||
"new-document": "我是新用户,我想为我的笔记创建一个新的Trilium文档",
|
||||
"sync-from-desktop": "我已经有一个桌面实例,我想设置与它的同步",
|
||||
"sync-from-server": "我已经有一个服务器实例,我想设置与它的同步",
|
||||
"next": "下一步",
|
||||
"init-in-progress": "文档初始化进行中",
|
||||
"redirecting": "您将很快被重定向到应用程序。",
|
||||
"title": "设置"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "从桌面同步",
|
||||
"description": "此设置需要从桌面实例开始:",
|
||||
"step1": "打开您的TriliumNext笔记桌面实例。",
|
||||
"step2": "从Trilium菜单中,点击“选项”。",
|
||||
"step3": "点击“同步”类别。",
|
||||
"step4": "将服务器实例地址更改为:{{- host}} 并点击保存。",
|
||||
"step5": "点击“测试同步”按钮以验证连接是否成功。",
|
||||
"step6": "完成这些步骤后,点击{{- link}}。",
|
||||
"step6-here": "这里"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "从服务器同步",
|
||||
"instructions": "请在下面输入Trilium服务器地址和凭据。这将从服务器下载整个Trilium文档并设置与它的同步。根据文档大小和您的连接的速度,这可能需要一段时间。",
|
||||
"server-host": "Trilium服务器地址",
|
||||
"server-host-placeholder": "https://<主机名称>:<端口>",
|
||||
"proxy-server": "代理服务器(可选)",
|
||||
"proxy-server-placeholder": "https://<主机名称>:<端口>",
|
||||
"note": "注意:",
|
||||
"proxy-instruction": "如果您将代理设置留空,将使用系统代理(仅适用于桌面应用)",
|
||||
"password": "密码",
|
||||
"password-placeholder": "密码",
|
||||
"back": "返回",
|
||||
"finish-setup": "完成设置"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "同步中",
|
||||
"successful": "同步已被正确设置。初始同步完成可能需要一些时间。完成后,您将被重定向到登录页面。",
|
||||
"outstanding-items": "未完成的同步项目:",
|
||||
"outstanding-items-default": "无"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "未找到",
|
||||
"heading": "未找到"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "父级:",
|
||||
"clipped-from": "此笔记最初剪切自 {{- url}}",
|
||||
"child-notes": "子笔记:",
|
||||
"no-content": "此笔记没有内容。"
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "周一",
|
||||
"tuesday": "周二",
|
||||
"wednesday": "周三",
|
||||
"thursday": "周四",
|
||||
"friday": "周五",
|
||||
"saturday": "周六",
|
||||
"sunday": "周日"
|
||||
},
|
||||
"weekdayNumber": "第 {weekNumber} 周",
|
||||
"months": {
|
||||
"january": "一月",
|
||||
"february": "二月",
|
||||
"march": "三月",
|
||||
"april": "四月",
|
||||
"may": "五月",
|
||||
"june": "六月",
|
||||
"july": "七月",
|
||||
"august": "八月",
|
||||
"september": "九月",
|
||||
"october": "十月",
|
||||
"november": "十一月",
|
||||
"december": "十二月"
|
||||
},
|
||||
"quarterNumber": "第 {quarterNumber} 季度",
|
||||
"special_notes": {
|
||||
"search_prefix": "搜索:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "同步服务器主机未配置。请先配置同步。",
|
||||
"successful": "同步服务器握手成功,同步已开始。"
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "隐藏的笔记",
|
||||
"search-history-title": "搜索历史",
|
||||
"note-map-title": "笔记地图",
|
||||
"sql-console-history-title": "SQL 控制台历史",
|
||||
"shared-notes-title": "共享笔记",
|
||||
"bulk-action-title": "批量操作",
|
||||
"backend-log-title": "后端日志",
|
||||
"user-hidden-title": "隐藏的用户",
|
||||
"launch-bar-templates-title": "启动栏模板",
|
||||
"base-abstract-launcher-title": "基础摘要启动器",
|
||||
"command-launcher-title": "命令启动器",
|
||||
"note-launcher-title": "笔记启动器",
|
||||
"script-launcher-title": "脚本启动器",
|
||||
"built-in-widget-title": "内置小组件",
|
||||
"spacer-title": "空白占位",
|
||||
"custom-widget-title": "自定义小组件",
|
||||
"launch-bar-title": "启动栏",
|
||||
"available-launchers-title": "可用启动器",
|
||||
"go-to-previous-note-title": "跳转到上一条笔记",
|
||||
"go-to-next-note-title": "跳转到下一条笔记",
|
||||
"new-note-title": "新建笔记",
|
||||
"search-notes-title": "搜索笔记",
|
||||
"calendar-title": "日历",
|
||||
"recent-changes-title": "最近更改",
|
||||
"bookmarks-title": "书签",
|
||||
"open-today-journal-note-title": "打开今天的日记笔记",
|
||||
"quick-search-title": "快速搜索",
|
||||
"protected-session-title": "受保护的会话",
|
||||
"sync-status-title": "同步状态",
|
||||
"settings-title": "设置",
|
||||
"options-title": "选项",
|
||||
"appearance-title": "外观",
|
||||
"shortcuts-title": "快捷键",
|
||||
"text-notes": "文本笔记",
|
||||
"code-notes-title": "代码笔记",
|
||||
"images-title": "图片",
|
||||
"spellcheck-title": "拼写检查",
|
||||
"password-title": "密码",
|
||||
"multi-factor-authentication-title": "多因素认证",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "备份",
|
||||
"sync-title": "同步",
|
||||
"other": "其他",
|
||||
"advanced-title": "高级",
|
||||
"visible-launchers-title": "可见启动器",
|
||||
"user-guide": "用户指南",
|
||||
"localization": "语言和区域",
|
||||
"jump-to-note-title": "跳转至...",
|
||||
"llm-chat-title": "与笔记聊天",
|
||||
"ai-llm-title": "AI/LLM",
|
||||
"inbox-title": "收件箱"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "新建笔记",
|
||||
"duplicate-note-suffix": "(重复)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "后端日志文件 '{{ fileName }}' 暂不存在。",
|
||||
"reading-log-failed": "读取后端日志文件 '{{ fileName }}' 失败。"
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "此笔记类型无法显示。"
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "PDF 文档 (*.pdf)",
|
||||
"unable-to-export-message": "当前笔记无法被导出为 PDF。",
|
||||
"unable-to-export-title": "无法导出为 PDF",
|
||||
"unable-to-save-message": "所选文件不能被写入。重试或选择另一个目的地。"
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "TriliumNext 笔记",
|
||||
"close": "退出 Trilium",
|
||||
"recents": "最近笔记",
|
||||
"bookmarks": "书签",
|
||||
"today": "打开今天的日记笔记",
|
||||
"new-note": "新建笔记",
|
||||
"show-windows": "显示窗口",
|
||||
"open_new_window": "打开新窗口"
|
||||
},
|
||||
"migration": {
|
||||
"old_version": "由您当前版本的直接迁移不被支持。请先升级到最新的 v0.60.4 然后再到这个版本。",
|
||||
"error_message": "迁移到版本 {{version}} 时发生错误: {{stack}}",
|
||||
"wrong_db_version": "数据库的版本({{version}})新于应用期望的版本({{targetVersion}}),这意味着它由一个更加新的且不兼容的 Trilium 所创建。升级到最新版的 Trilium 以解决此问题。"
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "错误"
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"back-in-note-history": "返回笔记历史",
|
||||
"forward-in-note-history": "前进笔记历史",
|
||||
"jump-to-note": "跳转到...",
|
||||
"command-palette": "命令面板",
|
||||
"scroll-to-active-note": "滚动到当前笔记",
|
||||
"quick-search": "快速搜索",
|
||||
"search-in-subtree": "在子树中搜索",
|
||||
"expand-subtree": "展开子树",
|
||||
"collapse-tree": "折叠整个树",
|
||||
"collapse-subtree": "折叠子树",
|
||||
"sort-child-notes": "排序子笔记",
|
||||
"create-note-after": "在后面创建笔记",
|
||||
"create-note-into": "创建笔记到",
|
||||
"create-note-into-inbox": "创建笔记到收件箱",
|
||||
"delete-notes": "删除笔记",
|
||||
"move-note-up": "上移笔记",
|
||||
"move-note-down": "下移笔记",
|
||||
"move-note-up-in-hierarchy": "在层级中上移笔记",
|
||||
"move-note-down-in-hierarchy": "在层级中下移笔记",
|
||||
"edit-note-title": "编辑笔记标题",
|
||||
"edit-branch-prefix": "编辑分支前缀",
|
||||
"clone-notes-to": "克隆笔记到",
|
||||
"move-notes-to": "移动笔记到",
|
||||
"copy-notes-to-clipboard": "复制笔记到剪贴板",
|
||||
"paste-notes-from-clipboard": "从剪贴板粘贴笔记",
|
||||
"cut-notes-to-clipboard": "剪切笔记到剪贴板",
|
||||
"select-all-notes-in-parent": "选择父节点中所有笔记",
|
||||
"zoom-in": "放大",
|
||||
"zoom-out": "缩小",
|
||||
"reset-zoom-level": "重置缩放级别",
|
||||
"copy-without-formatting": "无格式复制",
|
||||
"force-save-revision": "强制保存修订版本",
|
||||
"add-note-above-to-selection": "将上方笔记添加到选择",
|
||||
"add-note-below-to-selection": "将下方笔记添加到选择",
|
||||
"duplicate-subtree": "复制子树",
|
||||
"open-new-tab": "打开新标签页",
|
||||
"close-active-tab": "关闭当前标签页",
|
||||
"reopen-last-tab": "重新打开最后关闭的标签页",
|
||||
"activate-next-tab": "切换下一个标签页",
|
||||
"activate-previous-tab": "切换上一个标签页",
|
||||
"open-new-window": "打开新窗口",
|
||||
"toggle-system-tray-icon": "显示/隐藏系统托盘图标",
|
||||
"toggle-zen-mode": "启用/禁用禅模式",
|
||||
"switch-to-first-tab": "切换到第一个标签页",
|
||||
"switch-to-second-tab": "切换到第二个标签页",
|
||||
"switch-to-third-tab": "切换到第三个标签页",
|
||||
"switch-to-fourth-tab": "切换到第四个标签页",
|
||||
"switch-to-fifth-tab": "切换到第五个标签页",
|
||||
"switch-to-sixth-tab": "切换到第六个标签页",
|
||||
"switch-to-seventh-tab": "切换到第七个标签页",
|
||||
"switch-to-eighth-tab": "切换到第八个标签页",
|
||||
"switch-to-ninth-tab": "切换到第九个标签页",
|
||||
"switch-to-last-tab": "切换到最后一个标签页",
|
||||
"show-note-source": "显示笔记源代码",
|
||||
"show-options": "显示选项",
|
||||
"show-revisions": "显示修订历史",
|
||||
"show-recent-changes": "显示最近更改",
|
||||
"show-sql-console": "显示SQL控制台",
|
||||
"show-backend-log": "显示后端日志",
|
||||
"show-help": "显示帮助",
|
||||
"show-cheatsheet": "显示快捷键指南",
|
||||
"add-link-to-text": "为文本添加链接",
|
||||
"follow-link-under-cursor": "访问光标下的链接",
|
||||
"insert-date-and-time-to-text": "在文本中插入日期和时间",
|
||||
"paste-markdown-into-text": "粘贴Markdown到文本",
|
||||
"cut-into-note": "剪切到笔记",
|
||||
"add-include-note-to-text": "在文本中添加包含笔记",
|
||||
"edit-read-only-note": "编辑只读笔记",
|
||||
"add-new-label": "添加新标签",
|
||||
"add-new-relation": "添加新关系",
|
||||
"toggle-ribbon-tab-classic-editor": "切换功能区标签:经典编辑器",
|
||||
"toggle-ribbon-tab-basic-properties": "切换功能区标签:基本属性",
|
||||
"toggle-ribbon-tab-book-properties": "切换功能区标签:书籍属性",
|
||||
"toggle-ribbon-tab-file-properties": "切换功能区标签:文件属性",
|
||||
"toggle-ribbon-tab-image-properties": "切换功能区标签:图片属性",
|
||||
"toggle-ribbon-tab-owned-attributes": "切换功能区标签:自有属性",
|
||||
"toggle-ribbon-tab-inherited-attributes": "切换功能区标签:继承属性",
|
||||
"toggle-ribbon-tab-promoted-attributes": "切换功能区标签:提升属性",
|
||||
"toggle-ribbon-tab-note-map": "切换功能区标签:笔记地图",
|
||||
"toggle-ribbon-tab-note-info": "切换功能区标签:笔记信息",
|
||||
"toggle-ribbon-tab-note-paths": "切换功能区标签:笔记路径",
|
||||
"toggle-ribbon-tab-similar-notes": "切换功能区标签:相似笔记",
|
||||
"toggle-right-pane": "切换右侧面板",
|
||||
"print-active-note": "打印当前笔记",
|
||||
"export-active-note-as-pdf": "导出当前笔记为 PDF",
|
||||
"open-note-externally": "在外部打开笔记",
|
||||
"render-active-note": "渲染当前笔记",
|
||||
"run-active-note": "运行当前笔记",
|
||||
"toggle-note-hoisting": "提升笔记",
|
||||
"unhoist-note": "取消提升笔记",
|
||||
"reload-frontend-app": "重新加载前端应用",
|
||||
"open-developer-tools": "打开开发者工具",
|
||||
"find-in-text": "在文本中查找",
|
||||
"toggle-left-pane": "切换左侧面板",
|
||||
"toggle-full-screen": "切换全屏模式"
|
||||
},
|
||||
"share_theme": {
|
||||
"site-theme": "网站主题",
|
||||
"search_placeholder": "搜索...",
|
||||
"image_alt": "文章图片",
|
||||
"last-updated": "最后更新于 {{- date}}",
|
||||
"subpages": "子页面:",
|
||||
"on-this-page": "本页内容",
|
||||
"expand": "展开"
|
||||
},
|
||||
"hidden_subtree_templates": {
|
||||
"text-snippet": "文本片段",
|
||||
"description": "描述",
|
||||
"list-view": "列表视图",
|
||||
"grid-view": "网格视图",
|
||||
"calendar": "日历",
|
||||
"table": "表格",
|
||||
"geo-map": "地理地图",
|
||||
"start-date": "开始日期",
|
||||
"end-date": "结束日期",
|
||||
"start-time": "开始时间",
|
||||
"end-time": "结束时间",
|
||||
"geolocation": "地理位置",
|
||||
"built-in-templates": "内置模板",
|
||||
"board": "看板",
|
||||
"status": "状态",
|
||||
"board_note_first": "第一个笔记",
|
||||
"board_note_second": "第二个笔记",
|
||||
"board_note_third": "第三个笔记",
|
||||
"board_status_todo": "待办",
|
||||
"board_status_progress": "进行中",
|
||||
"board_status_done": "已完成"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,266 +1,277 @@
|
||||
{
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "Öffne das Dialogfeld \"Zu Notiz springen\"",
|
||||
"search-in-subtree": "Nach Notizen im Unterbaum der aktuellen Notiz suchen",
|
||||
"expand-subtree": "Unterbaum der aktuellen Notiz ausklappen",
|
||||
"collapse-tree": "Gesamten Notizbaum einklappen",
|
||||
"collapse-subtree": "Unterbaum der aktuellen Notiz einklappen",
|
||||
"sort-child-notes": "Untergeordnete Notizen sortieren",
|
||||
"creating-and-moving-notes": "Notizen erstellen und verschieben",
|
||||
"create-note-into-inbox": "Erstelle eine Notiz im Posteingang (falls definiert) oder in der Tagesnotiz",
|
||||
"delete-note": "Notiz löschen",
|
||||
"move-note-up": "Notiz nach oben verschieben",
|
||||
"move-note-down": "Notiz nach unten verschieben",
|
||||
"move-note-up-in-hierarchy": "Notiz in der Hierarchie nach oben verschieben",
|
||||
"move-note-down-in-hierarchy": "Notiz in der Hierarchie nach unten verschieben",
|
||||
"edit-note-title": "Vom Notiz-Baum zur Notiz-Detailansicht springen und den Titel bearbeiten",
|
||||
"edit-branch-prefix": "Dialog zum Bearbeiten des Zweigpräfixes anzeigen",
|
||||
"note-clipboard": "Notiz-Zwischenablage",
|
||||
"copy-notes-to-clipboard": "Ausgewählte Notizen in die Zwischenablage kopieren",
|
||||
"paste-notes-from-clipboard": "Notizen aus der Zwischenablage in die aktive Notiz einfügen",
|
||||
"cut-notes-to-clipboard": "Ausgewählte Notizen in die Zwischenablage ausschneiden",
|
||||
"select-all-notes-in-parent": "Alle Notizen der aktuellen Notizenebene auswählen",
|
||||
"add-note-above-to-the-selection": "Notiz oberhalb der Auswahl hinzufügen",
|
||||
"add-note-below-to-selection": "Notiz unterhalb der Auswahl hinzufügen",
|
||||
"duplicate-subtree": "Unterbaum duplizieren",
|
||||
"tabs-and-windows": "Tabs & Fenster",
|
||||
"open-new-tab": "Neuen Tab öffnen",
|
||||
"close-active-tab": "Aktiven Tab schließen",
|
||||
"reopen-last-tab": "Zuletzt geschlossenen Tab wieder öffnen",
|
||||
"activate-next-tab": "Rechten Tab aktivieren",
|
||||
"activate-previous-tab": "Linken Tab aktivieren",
|
||||
"open-new-window": "Neues leeres Fenster öffnen",
|
||||
"toggle-tray": "Anwendung im Systemtray anzeigen/verstecken",
|
||||
"first-tab": "Ersten Tab in der Liste aktivieren",
|
||||
"second-tab": " Zweiten Tab in der Liste aktivieren",
|
||||
"third-tab": "Dritten Tab in der Liste aktivieren",
|
||||
"fourth-tab": "Vierten Tab in der Liste aktivieren",
|
||||
"fifth-tab": "Fünften Tab in der Liste aktivieren",
|
||||
"sixth-tab": "Sechsten Tab in der Liste aktivieren",
|
||||
"seventh-tab": "Siebten Tab in der Liste aktivieren",
|
||||
"eight-tab": "Achten Tab in der Liste aktivieren",
|
||||
"ninth-tab": "Neunten Tab in der Liste aktivieren",
|
||||
"last-tab": "Letzten Tab in der Liste aktivieren",
|
||||
"dialogs": "Dialoge",
|
||||
"show-note-source": "Notizquellen-Dialog anzeigen",
|
||||
"show-options": "Optionen-Dialog anzeigen",
|
||||
"show-revisions": "Notizrevisionen-Dialog anzeigen",
|
||||
"show-recent-changes": "Letzte Änderungen-Dialog anzeigen",
|
||||
"show-sql-console": "SQL-Konsole-Dialog anzeigen",
|
||||
"show-backend-log": "Backend-Logs-Dialog anzeigen",
|
||||
"text-note-operations": "Textnotiz-Operationen",
|
||||
"add-link-to-text": "Dialogfeld zum Hinzufügen eines Links zum Text öffnen",
|
||||
"follow-link-under-cursor": "Folge dem Link, unter dem Mauszeiger",
|
||||
"insert-date-and-time-to-text": "Aktuelles Datum & Uhrzeit in den Text einfügen",
|
||||
"paste-markdown-into-text": "Markdown aus der Zwischenablage in die Textnotiz einfügen",
|
||||
"cut-into-note": "Auswahl aus der aktuellen Notiz ausschneiden und eine Unternotiz mit dem ausgewählten Text erstellen",
|
||||
"add-include-note-to-text": "Notiz-Einfügen-Dialog öffnen",
|
||||
"edit-readonly-note": "Schreibgeschützte Notiz bearbeiten",
|
||||
"attributes-labels-and-relations": "Attribute (Labels & Verknüpfungen)",
|
||||
"add-new-label": "Neues Label erstellen",
|
||||
"create-new-relation": "Neue Verknüpfungen",
|
||||
"ribbon-tabs": "Ribbon-Tabs",
|
||||
"toggle-basic-properties": "Grundattribute umschalten",
|
||||
"toggle-file-properties": "Dateiattribute umschalten",
|
||||
"toggle-image-properties": "Bildattribute umschalten",
|
||||
"toggle-owned-attributes": "Eigene Attribute umschalten",
|
||||
"toggle-inherited-attributes": "Vererbte Attribute umschalten",
|
||||
"toggle-promoted-attributes": "Beworbene Attribute umschalten",
|
||||
"toggle-link-map": "Link-Karte umschalten",
|
||||
"toggle-note-info": "Notizinformationen umschalten",
|
||||
"toggle-note-paths": "Notizpfade umschalten",
|
||||
"toggle-similar-notes": "Ähnliche Notizen umschalten",
|
||||
"other": "Sonstige",
|
||||
"toggle-right-pane": "Anzeige der rechten Leiste umschalten, das Inhaltsverzeichnis und Markierungen enthält",
|
||||
"print-active-note": "Aktive Notiz drucken",
|
||||
"open-note-externally": "Notiz als Datei mit Standardanwendung öffnen",
|
||||
"render-active-note": "Aktive Notiz rendern (erneut rendern)",
|
||||
"run-active-note": "Aktive JavaScript(Frontend/Backend)-Codenotiz ausführen",
|
||||
"toggle-note-hoisting": "Notiz-Fokus der aktiven Notiz umschalten",
|
||||
"unhoist": "Notiz-Fokus aufheben",
|
||||
"reload-frontend-app": "Frontend-App neuladen",
|
||||
"open-dev-tools": "Entwicklertools öffnen",
|
||||
"toggle-left-note-tree-panel": "Linke Notizbaum-Leiste umschalten",
|
||||
"toggle-full-screen": "Vollbildmodus umschalten",
|
||||
"zoom-out": "Herauszoomen",
|
||||
"zoom-in": "Hineinzoomen",
|
||||
"note-navigation": "Notiznavigation",
|
||||
"reset-zoom-level": "Zoomlevel zurücksetzen",
|
||||
"copy-without-formatting": "Ausgewählten Text ohne Formatierung kopieren",
|
||||
"force-save-revision": "Erstellen / Speichern einer neuen Notizrevision der aktiven Notiz erzwingen",
|
||||
"show-help": "Eingebaute Hilfe / Cheat-Sheet anzeigen",
|
||||
"toggle-book-properties": "Buch-Eigenschaften umschalten"
|
||||
},
|
||||
"login": {
|
||||
"title": "Anmeldung",
|
||||
"heading": "Trilium Anmeldung",
|
||||
"incorrect-password": "Das Passwort ist falsch. Bitte versuche es erneut.",
|
||||
"password": "Passwort",
|
||||
"remember-me": "Angemeldet bleiben",
|
||||
"button": "Anmelden"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Passwort festlegen",
|
||||
"heading": "Passwort festlegen",
|
||||
"description": "Bevor du Trilium im Web verwenden kannst, musst du zuerst ein Passwort festlegen. Du wirst dieses Passwort dann zur Anmeldung verwenden.",
|
||||
"password": "Passwort",
|
||||
"password-confirmation": "Passwortbestätigung",
|
||||
"button": "Passwort festlegen"
|
||||
},
|
||||
"javascript-required": "Trilium erfordert, dass JavaScript aktiviert ist.",
|
||||
"setup": {
|
||||
"heading": "Trilium Notes Setup",
|
||||
"new-document": "Ich bin ein neuer Benutzer und möchte ein neues Trilium-Dokument für meine Notizen erstellen",
|
||||
"sync-from-desktop": "Ich habe bereits eine Desktop-Instanz und möchte die Synchronisierung damit einrichten",
|
||||
"sync-from-server": "Ich habe bereits eine Server-Instanz und möchte die Synchronisierung damit einrichten",
|
||||
"next": "Weiter",
|
||||
"init-in-progress": "Dokumenteninitialisierung läuft",
|
||||
"redirecting": "Du wirst in Kürze zur Anwendung weitergeleitet.",
|
||||
"title": "Setup"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Synchronisation vom Desktop",
|
||||
"description": "Dieses Setup muss von der Desktop-Instanz aus initiiert werden:",
|
||||
"step1": "Öffne deine Trilium Notes Desktop-Instanz.",
|
||||
"step2": "Klicke im Trilium-Menü auf Optionen.",
|
||||
"step3": "Klicke auf die Kategorie Synchronisation.",
|
||||
"step4": "Ändere die Server-Instanzadresse auf: {{- host}} und klicke auf Speichern.",
|
||||
"step5": "Klicke auf den Button \"Test-Synchronisation\", um zu überprüfen, ob die Verbindung erfolgreich ist.",
|
||||
"step6": "Sobald du diese Schritte abgeschlossen hast, klicke auf {{- link}}.",
|
||||
"step6-here": "hier"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Synchronisation vom Server",
|
||||
"instructions": "Bitte gib unten die Trilium-Server-Adresse und die Zugangsdaten ein. Dies wird das gesamte Trilium-Dokument vom Server herunterladen und die Synchronisation einrichten. Je nach Dokumentgröße und Verbindungsgeschwindigkeit kann dies eine Weile dauern.",
|
||||
"server-host": "Trilium Server-Adresse",
|
||||
"server-host-placeholder": "https://<hostname>:<port>",
|
||||
"proxy-server": "Proxy-Server (optional)",
|
||||
"proxy-server-placeholder": "https://<hostname>:<port>",
|
||||
"note": "Hinweis:",
|
||||
"proxy-instruction": "Wenn du die Proxy-Einstellung leer lässt, wird der System-Proxy verwendet (gilt nur für die Desktop-Anwendung)",
|
||||
"password": "Passwort",
|
||||
"password-placeholder": "Passwort",
|
||||
"back": "Zurück",
|
||||
"finish-setup": "Setup abschließen"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Synchronisation läuft",
|
||||
"successful": "Die Synchronisation wurde erfolgreich eingerichtet. Es wird eine Weile dauern, bis die erste Synchronisation abgeschlossen ist. Sobald dies erledigt ist, wirst du zur Anmeldeseite weitergeleitet.",
|
||||
"outstanding-items": "Ausstehende Synchronisationselemente:",
|
||||
"outstanding-items-default": "N/A"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "Nicht gefunden",
|
||||
"heading": "Nicht gefunden"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "Übergeordnete Notiz:",
|
||||
"clipped-from": "Diese Notiz wurde ursprünglich von {{- url}} ausgeschnitten",
|
||||
"child-notes": "Untergeordnete Notizen:",
|
||||
"no-content": "Diese Notiz hat keinen Inhalt."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Montag",
|
||||
"tuesday": "Dienstag",
|
||||
"wednesday": "Mittwoch",
|
||||
"thursday": "Donnerstag",
|
||||
"friday": "Freitag",
|
||||
"saturday": "Samstag",
|
||||
"sunday": "Sonntag"
|
||||
},
|
||||
"months": {
|
||||
"january": "Januar",
|
||||
"february": "Februar",
|
||||
"march": "März",
|
||||
"april": "April",
|
||||
"may": "Mai",
|
||||
"june": "Juni",
|
||||
"july": "Juli",
|
||||
"august": "August",
|
||||
"september": "September",
|
||||
"october": "Oktober",
|
||||
"november": "November",
|
||||
"december": "Dezember"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "Suche:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "Der Synchronisations-Server-Host ist nicht konfiguriert. Bitte konfiguriere zuerst die Synchronisation.",
|
||||
"successful": "Die Server-Verbindung wurde erfolgreich hergestellt, die Synchronisation wurde gestartet."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "Versteckte Notizen",
|
||||
"search-history-title": "Suchverlauf",
|
||||
"note-map-title": "Notiz Karte",
|
||||
"sql-console-history-title": "SQL Konsolen Verlauf",
|
||||
"shared-notes-title": "Geteilte Notizen",
|
||||
"bulk-action-title": "Massenverarbeitung",
|
||||
"backend-log-title": "Backend Log",
|
||||
"user-hidden-title": "Versteckt vom Nutzer",
|
||||
"launch-bar-templates-title": "Startleiste Vorlagen",
|
||||
"base-abstract-launcher-title": "Basis Abstrakte Startleiste",
|
||||
"command-launcher-title": "Befehlslauncher",
|
||||
"note-launcher-title": "Notiz Launcher",
|
||||
"script-launcher-title": "Script Launcher",
|
||||
"built-in-widget-title": "Eingebautes Widget",
|
||||
"spacer-title": "Freifeld",
|
||||
"custom-widget-title": "Custom Widget",
|
||||
"launch-bar-title": "Launchbar",
|
||||
"available-launchers-title": "Verfügbare Launchers",
|
||||
"go-to-previous-note-title": "Zur vorherigen Notiz gehen",
|
||||
"go-to-next-note-title": "Zur nächsten Notiz gehen",
|
||||
"new-note-title": "Neue Notiz",
|
||||
"search-notes-title": "Notizen durchsuchen",
|
||||
"calendar-title": "Kalender",
|
||||
"recent-changes-title": "neue Änderungen",
|
||||
"bookmarks-title": "Lesezeichen",
|
||||
"open-today-journal-note-title": "Heutigen Journaleintrag öffnen",
|
||||
"quick-search-title": "Schnellsuche",
|
||||
"protected-session-title": "Geschützte Sitzung",
|
||||
"sync-status-title": "Sync Status",
|
||||
"settings-title": "Einstellungen",
|
||||
"options-title": "Optionen",
|
||||
"appearance-title": "Erscheinungsbild",
|
||||
"shortcuts-title": "Tastaturkürzel",
|
||||
"text-notes": "Text Notizen",
|
||||
"code-notes-title": "Code Notizen",
|
||||
"images-title": "Bilder",
|
||||
"spellcheck-title": "Rechtschreibprüfung",
|
||||
"password-title": "Passwort",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Sicherung",
|
||||
"sync-title": "Sync",
|
||||
"other": "Weitere",
|
||||
"advanced-title": "Erweitert",
|
||||
"visible-launchers-title": "Sichtbare Launcher",
|
||||
"user-guide": "Nutzerhandbuch"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Neue Notiz",
|
||||
"duplicate-note-suffix": "(dup)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "Die Backend-Log-Datei '{{ fileName }}' existiert (noch) nicht.",
|
||||
"reading-log-failed": "Das Lesen der Backend-Log-Datei '{{ fileName }}' ist fehlgeschlagen."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Dieser Notiztyp kann nicht angezeigt werden."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "PDF Dokument (*.pdf)",
|
||||
"unable-to-export-message": "Die aktuelle Notiz konnte nicht als PDF exportiert werden.",
|
||||
"unable-to-export-title": "Export als PDF fehlgeschlagen",
|
||||
"unable-to-save-message": "Die ausgewählte Datei konnte nicht beschrieben werden. Erneut versuchen oder ein anderes Ziel auswählen."
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "Trilium Notes",
|
||||
"close": "Trilium schließen",
|
||||
"recents": "Kürzliche Notizen",
|
||||
"bookmarks": "Lesezeichen",
|
||||
"today": "Heutigen Journal Eintrag öffnen",
|
||||
"new-note": "Neue Notiz",
|
||||
"show-windows": "Fenster anzeigen"
|
||||
}
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "Öffne das Dialogfeld \"Zu Notiz springen\"",
|
||||
"search-in-subtree": "Nach Notizen im Unterbaum der aktuellen Notiz suchen",
|
||||
"expand-subtree": "Unterbaum der aktuellen Notiz ausklappen",
|
||||
"collapse-tree": "Gesamten Notizbaum einklappen",
|
||||
"collapse-subtree": "Unterbaum der aktuellen Notiz einklappen",
|
||||
"sort-child-notes": "Untergeordnete Notizen sortieren",
|
||||
"creating-and-moving-notes": "Notizen erstellen und verschieben",
|
||||
"create-note-into-inbox": "Erstelle eine Notiz im Posteingang (falls definiert) oder in der Tagesnotiz",
|
||||
"delete-note": "Notiz löschen",
|
||||
"move-note-up": "Notiz nach oben verschieben",
|
||||
"move-note-down": "Notiz nach unten verschieben",
|
||||
"move-note-up-in-hierarchy": "Notiz in der Hierarchie nach oben verschieben",
|
||||
"move-note-down-in-hierarchy": "Notiz in der Hierarchie nach unten verschieben",
|
||||
"edit-note-title": "Vom Notiz-Baum zur Notiz-Detailansicht springen und den Titel bearbeiten",
|
||||
"edit-branch-prefix": "Dialog zum Bearbeiten des Zweigpräfixes anzeigen",
|
||||
"note-clipboard": "Notiz-Zwischenablage",
|
||||
"copy-notes-to-clipboard": "Ausgewählte Notizen in die Zwischenablage kopieren",
|
||||
"paste-notes-from-clipboard": "Notizen aus der Zwischenablage in die aktive Notiz einfügen",
|
||||
"cut-notes-to-clipboard": "Ausgewählte Notizen in die Zwischenablage ausschneiden",
|
||||
"select-all-notes-in-parent": "Alle Notizen der aktuellen Notizenebene auswählen",
|
||||
"add-note-above-to-the-selection": "Notiz oberhalb der Auswahl hinzufügen",
|
||||
"add-note-below-to-selection": "Notiz unterhalb der Auswahl hinzufügen",
|
||||
"duplicate-subtree": "Unterbaum duplizieren",
|
||||
"tabs-and-windows": "Tabs & Fenster",
|
||||
"open-new-tab": "Neuen Tab öffnen",
|
||||
"close-active-tab": "Aktiven Tab schließen",
|
||||
"reopen-last-tab": "Zuletzt geschlossenen Tab wieder öffnen",
|
||||
"activate-next-tab": "Rechten Tab aktivieren",
|
||||
"activate-previous-tab": "Linken Tab aktivieren",
|
||||
"open-new-window": "Neues leeres Fenster öffnen",
|
||||
"toggle-tray": "Anwendung im Systemtray anzeigen/verstecken",
|
||||
"first-tab": "Ersten Tab in der Liste aktivieren",
|
||||
"second-tab": " Zweiten Tab in der Liste aktivieren",
|
||||
"third-tab": "Dritten Tab in der Liste aktivieren",
|
||||
"fourth-tab": "Vierten Tab in der Liste aktivieren",
|
||||
"fifth-tab": "Fünften Tab in der Liste aktivieren",
|
||||
"sixth-tab": "Sechsten Tab in der Liste aktivieren",
|
||||
"seventh-tab": "Siebten Tab in der Liste aktivieren",
|
||||
"eight-tab": "Achten Tab in der Liste aktivieren",
|
||||
"ninth-tab": "Neunten Tab in der Liste aktivieren",
|
||||
"last-tab": "Letzten Tab in der Liste aktivieren",
|
||||
"dialogs": "Dialoge",
|
||||
"show-note-source": "Notizquellen-Dialog anzeigen",
|
||||
"show-options": "Optionen-Dialog anzeigen",
|
||||
"show-revisions": "Notizrevisionen-Dialog anzeigen",
|
||||
"show-recent-changes": "Letzte Änderungen-Dialog anzeigen",
|
||||
"show-sql-console": "SQL-Konsole-Dialog anzeigen",
|
||||
"show-backend-log": "Backend-Logs-Dialog anzeigen",
|
||||
"text-note-operations": "Textnotiz-Operationen",
|
||||
"add-link-to-text": "Dialogfeld zum Hinzufügen eines Links zum Text öffnen",
|
||||
"follow-link-under-cursor": "Folge dem Link, unter dem Mauszeiger",
|
||||
"insert-date-and-time-to-text": "Aktuelles Datum & Uhrzeit in den Text einfügen",
|
||||
"paste-markdown-into-text": "Markdown aus der Zwischenablage in die Textnotiz einfügen",
|
||||
"cut-into-note": "Auswahl aus der aktuellen Notiz ausschneiden und eine Unternotiz mit dem ausgewählten Text erstellen",
|
||||
"add-include-note-to-text": "Notiz-Einfügen-Dialog öffnen",
|
||||
"edit-readonly-note": "Schreibgeschützte Notiz bearbeiten",
|
||||
"attributes-labels-and-relations": "Attribute (Labels & Verknüpfungen)",
|
||||
"add-new-label": "Neues Label erstellen",
|
||||
"create-new-relation": "Neue Verknüpfungen",
|
||||
"ribbon-tabs": "Ribbon-Tabs",
|
||||
"toggle-basic-properties": "Grundattribute umschalten",
|
||||
"toggle-file-properties": "Dateiattribute umschalten",
|
||||
"toggle-image-properties": "Bildattribute umschalten",
|
||||
"toggle-owned-attributes": "Eigene Attribute umschalten",
|
||||
"toggle-inherited-attributes": "Vererbte Attribute umschalten",
|
||||
"toggle-promoted-attributes": "Beworbene Attribute umschalten",
|
||||
"toggle-link-map": "Link-Karte umschalten",
|
||||
"toggle-note-info": "Notizinformationen umschalten",
|
||||
"toggle-note-paths": "Notizpfade umschalten",
|
||||
"toggle-similar-notes": "Ähnliche Notizen umschalten",
|
||||
"other": "Sonstige",
|
||||
"toggle-right-pane": "Anzeige der rechten Leiste umschalten, das Inhaltsverzeichnis und Markierungen enthält",
|
||||
"print-active-note": "Aktive Notiz drucken",
|
||||
"open-note-externally": "Notiz als Datei mit Standardanwendung öffnen",
|
||||
"render-active-note": "Aktive Notiz rendern (erneut rendern)",
|
||||
"run-active-note": "Aktive JavaScript(Frontend/Backend)-Codenotiz ausführen",
|
||||
"toggle-note-hoisting": "Notiz-Fokus der aktiven Notiz umschalten",
|
||||
"unhoist": "Notiz-Fokus aufheben",
|
||||
"reload-frontend-app": "Frontend-App neuladen",
|
||||
"open-dev-tools": "Entwicklertools öffnen",
|
||||
"toggle-left-note-tree-panel": "Linke Notizbaum-Leiste umschalten",
|
||||
"toggle-full-screen": "Vollbildmodus umschalten",
|
||||
"zoom-out": "Herauszoomen",
|
||||
"zoom-in": "Hineinzoomen",
|
||||
"note-navigation": "Notiznavigation",
|
||||
"reset-zoom-level": "Zoomlevel zurücksetzen",
|
||||
"copy-without-formatting": "Ausgewählten Text ohne Formatierung kopieren",
|
||||
"force-save-revision": "Erstellen / Speichern einer neuen Notizrevision der aktiven Notiz erzwingen",
|
||||
"show-help": "Eingebaute Hilfe / Cheat-Sheet anzeigen",
|
||||
"toggle-book-properties": "Buch-Eigenschaften umschalten",
|
||||
"clone-notes-to": "Ausgewählte Notizen duplizieren",
|
||||
"open-command-palette": "Kommandopalette öffnen",
|
||||
"export-as-pdf": "Aktuelle Notiz als PDF exportieren"
|
||||
},
|
||||
"login": {
|
||||
"title": "Anmeldung",
|
||||
"heading": "Trilium Anmeldung",
|
||||
"incorrect-password": "Das Passwort ist falsch. Bitte versuche es erneut.",
|
||||
"password": "Passwort",
|
||||
"remember-me": "Angemeldet bleiben",
|
||||
"button": "Anmelden"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Passwort festlegen",
|
||||
"heading": "Passwort festlegen",
|
||||
"description": "Bevor du Trilium im Web verwenden kannst, musst du zuerst ein Passwort festlegen. Du wirst dieses Passwort dann zur Anmeldung verwenden.",
|
||||
"password": "Passwort",
|
||||
"password-confirmation": "Passwortbestätigung",
|
||||
"button": "Passwort festlegen"
|
||||
},
|
||||
"javascript-required": "Trilium erfordert, dass JavaScript aktiviert ist.",
|
||||
"setup": {
|
||||
"heading": "Trilium Notes Setup",
|
||||
"new-document": "Ich bin ein neuer Benutzer und möchte ein neues Trilium-Dokument für meine Notizen erstellen",
|
||||
"sync-from-desktop": "Ich habe bereits eine Desktop-Instanz und möchte die Synchronisierung damit einrichten",
|
||||
"sync-from-server": "Ich habe bereits eine Server-Instanz und möchte die Synchronisierung damit einrichten",
|
||||
"next": "Weiter",
|
||||
"init-in-progress": "Dokumenteninitialisierung läuft",
|
||||
"redirecting": "Du wirst in Kürze zur Anwendung weitergeleitet.",
|
||||
"title": "Setup"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Synchronisation vom Desktop",
|
||||
"description": "Dieses Setup muss von der Desktop-Instanz aus initiiert werden:",
|
||||
"step1": "Öffne deine Trilium Notes Desktop-Instanz.",
|
||||
"step2": "Klicke im Trilium-Menü auf Optionen.",
|
||||
"step3": "Klicke auf die Kategorie Synchronisation.",
|
||||
"step4": "Ändere die Server-Instanzadresse auf: {{- host}} und klicke auf Speichern.",
|
||||
"step5": "Klicke auf den Button \"Test-Synchronisation\", um zu überprüfen, ob die Verbindung erfolgreich ist.",
|
||||
"step6": "Sobald du diese Schritte abgeschlossen hast, klicke auf {{- link}}.",
|
||||
"step6-here": "hier"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Synchronisation vom Server",
|
||||
"instructions": "Bitte gib unten die Trilium-Server-Adresse und die Zugangsdaten ein. Dies wird das gesamte Trilium-Dokument vom Server herunterladen und die Synchronisation einrichten. Je nach Dokumentgröße und Verbindungsgeschwindigkeit kann dies eine Weile dauern.",
|
||||
"server-host": "Trilium Server-Adresse",
|
||||
"server-host-placeholder": "https://<hostname>:<port>",
|
||||
"proxy-server": "Proxy-Server (optional)",
|
||||
"proxy-server-placeholder": "https://<hostname>:<port>",
|
||||
"note": "Hinweis:",
|
||||
"proxy-instruction": "Wenn du die Proxy-Einstellung leer lässt, wird der System-Proxy verwendet (gilt nur für die Desktop-Anwendung)",
|
||||
"password": "Passwort",
|
||||
"password-placeholder": "Passwort",
|
||||
"back": "Zurück",
|
||||
"finish-setup": "Setup abschließen"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Synchronisation läuft",
|
||||
"successful": "Die Synchronisation wurde erfolgreich eingerichtet. Es wird eine Weile dauern, bis die erste Synchronisation abgeschlossen ist. Sobald dies erledigt ist, wirst du zur Anmeldeseite weitergeleitet.",
|
||||
"outstanding-items": "Ausstehende Synchronisationselemente:",
|
||||
"outstanding-items-default": "N/A"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "Nicht gefunden",
|
||||
"heading": "Nicht gefunden"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "Übergeordnete Notiz:",
|
||||
"clipped-from": "Diese Notiz wurde ursprünglich von {{- url}} ausgeschnitten",
|
||||
"child-notes": "Untergeordnete Notizen:",
|
||||
"no-content": "Diese Notiz hat keinen Inhalt."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Montag",
|
||||
"tuesday": "Dienstag",
|
||||
"wednesday": "Mittwoch",
|
||||
"thursday": "Donnerstag",
|
||||
"friday": "Freitag",
|
||||
"saturday": "Samstag",
|
||||
"sunday": "Sonntag"
|
||||
},
|
||||
"months": {
|
||||
"january": "Januar",
|
||||
"february": "Februar",
|
||||
"march": "März",
|
||||
"april": "April",
|
||||
"may": "Mai",
|
||||
"june": "Juni",
|
||||
"july": "Juli",
|
||||
"august": "August",
|
||||
"september": "September",
|
||||
"october": "Oktober",
|
||||
"november": "November",
|
||||
"december": "Dezember"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "Suche:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "Der Synchronisations-Server-Host ist nicht konfiguriert. Bitte konfiguriere zuerst die Synchronisation.",
|
||||
"successful": "Die Server-Verbindung wurde erfolgreich hergestellt, die Synchronisation wurde gestartet."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "Versteckte Notizen",
|
||||
"search-history-title": "Suchverlauf",
|
||||
"note-map-title": "Notiz Karte",
|
||||
"sql-console-history-title": "SQL Konsolen Verlauf",
|
||||
"shared-notes-title": "Geteilte Notizen",
|
||||
"bulk-action-title": "Massenverarbeitung",
|
||||
"backend-log-title": "Backend Log",
|
||||
"user-hidden-title": "Versteckt vom Nutzer",
|
||||
"launch-bar-templates-title": "Startleiste Vorlagen",
|
||||
"base-abstract-launcher-title": "Basis Abstrakte Startleiste",
|
||||
"command-launcher-title": "Befehlslauncher",
|
||||
"note-launcher-title": "Notiz Launcher",
|
||||
"script-launcher-title": "Script Launcher",
|
||||
"built-in-widget-title": "Eingebautes Widget",
|
||||
"spacer-title": "Freifeld",
|
||||
"custom-widget-title": "Custom Widget",
|
||||
"launch-bar-title": "Launchbar",
|
||||
"available-launchers-title": "Verfügbare Launchers",
|
||||
"go-to-previous-note-title": "Zur vorherigen Notiz gehen",
|
||||
"go-to-next-note-title": "Zur nächsten Notiz gehen",
|
||||
"new-note-title": "Neue Notiz",
|
||||
"search-notes-title": "Notizen durchsuchen",
|
||||
"calendar-title": "Kalender",
|
||||
"recent-changes-title": "neue Änderungen",
|
||||
"bookmarks-title": "Lesezeichen",
|
||||
"open-today-journal-note-title": "Heutigen Journaleintrag öffnen",
|
||||
"quick-search-title": "Schnellsuche",
|
||||
"protected-session-title": "Geschützte Sitzung",
|
||||
"sync-status-title": "Sync Status",
|
||||
"settings-title": "Einstellungen",
|
||||
"options-title": "Optionen",
|
||||
"appearance-title": "Erscheinungsbild",
|
||||
"shortcuts-title": "Tastaturkürzel",
|
||||
"text-notes": "Text Notizen",
|
||||
"code-notes-title": "Code Notizen",
|
||||
"images-title": "Bilder",
|
||||
"spellcheck-title": "Rechtschreibprüfung",
|
||||
"password-title": "Passwort",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Sicherung",
|
||||
"sync-title": "Sync",
|
||||
"other": "Weitere",
|
||||
"advanced-title": "Erweitert",
|
||||
"visible-launchers-title": "Sichtbare Launcher",
|
||||
"user-guide": "Nutzerhandbuch"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Neue Notiz",
|
||||
"duplicate-note-suffix": "(dup)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "Die Backend-Log-Datei '{{ fileName }}' existiert (noch) nicht.",
|
||||
"reading-log-failed": "Das Lesen der Backend-Log-Datei '{{ fileName }}' ist fehlgeschlagen."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Dieser Notiztyp kann nicht angezeigt werden."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "PDF Dokument (*.pdf)",
|
||||
"unable-to-export-message": "Die aktuelle Notiz konnte nicht als PDF exportiert werden.",
|
||||
"unable-to-export-title": "Export als PDF fehlgeschlagen",
|
||||
"unable-to-save-message": "Die ausgewählte Datei konnte nicht beschrieben werden. Erneut versuchen oder ein anderes Ziel auswählen."
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "Trilium Notes",
|
||||
"close": "Trilium schließen",
|
||||
"recents": "Kürzliche Notizen",
|
||||
"bookmarks": "Lesezeichen",
|
||||
"today": "Heutigen Journal Eintrag öffnen",
|
||||
"new-note": "Neue Notiz",
|
||||
"show-windows": "Fenster anzeigen"
|
||||
},
|
||||
"hidden_subtree_templates": {
|
||||
"table": "Tabelle",
|
||||
"board_status_done": "Erledigt"
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"copy-notes-to-clipboard": "Notizen in Zwischenablage kopieren",
|
||||
"paste-notes-from-clipboard": "Notizen aus Zwischenablage einfügen"
|
||||
}
|
||||
}
|
||||
|
||||
1
apps/server/src/assets/translations/el/server.json
Normal file
1
apps/server/src/assets/translations/el/server.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -1,306 +1,430 @@
|
||||
{
|
||||
"keyboard_actions": {
|
||||
"back-in-note-history": "Navegar a la nota previa en el historial",
|
||||
"forward-in-note-history": "Navegar a la nota siguiente en el historial",
|
||||
"open-jump-to-note-dialog": "Abrir cuadro de diálogo \"Saltar a nota\"",
|
||||
"scroll-to-active-note": "Desplazarse a la nota activa en el árbol de notas",
|
||||
"quick-search": "Activar barra de búisqueda rápida",
|
||||
"search-in-subtree": "Buscar notas en el subárbol de la nota activa",
|
||||
"expand-subtree": "Expandir el subárbol de la nota actual",
|
||||
"collapse-tree": "Colapsa el árbol de notas completo",
|
||||
"collapse-subtree": "Colapsa el subárbol de la nota actual",
|
||||
"sort-child-notes": "Ordenar subnotas",
|
||||
"creating-and-moving-notes": "Creando y moviendo notas",
|
||||
"create-note-after": "Crear nota después de la nota activa",
|
||||
"create-note-into": "Crear nota como subnota de la nota activa",
|
||||
"create-note-into-inbox": "Crear una nota en la bandeja de entrada (si está definida) o nota del día",
|
||||
"delete-note": "Eliminar nota",
|
||||
"move-note-up": "Mover nota hacia arriba",
|
||||
"move-note-down": "Mover nota hacia abajo",
|
||||
"move-note-up-in-hierarchy": "Mover nota hacia arriba en la jerarquía",
|
||||
"move-note-down-in-hierarchy": "Mover nota hacia abajo en la jerarquía",
|
||||
"edit-note-title": "Saltar del árbol al detalle de la nota y editar el título",
|
||||
"edit-branch-prefix": "Mostrar cuadro de diálogo Editar prefijo de rama",
|
||||
"cloneNotesTo": "Clonar notas seleccionadas",
|
||||
"moveNotesTo": "Mover notas seleccionadas",
|
||||
"note-clipboard": "Portapapeles de notas",
|
||||
"copy-notes-to-clipboard": "Copiar las notas seleccionadas al portapapeles",
|
||||
"paste-notes-from-clipboard": "Pegar las notas del portapapeles en una nota activa",
|
||||
"cut-notes-to-clipboard": "Cortar las notas seleccionadas al portapapeles",
|
||||
"select-all-notes-in-parent": "Seleccionar todas las notas del nivel de la nota actual",
|
||||
"add-note-above-to-the-selection": "Agregar nota arriba de la selección",
|
||||
"add-note-below-to-selection": "Agregar nota arriba de la selección",
|
||||
"duplicate-subtree": "Duplicar subárbol",
|
||||
"tabs-and-windows": "Pestañas y ventanas",
|
||||
"open-new-tab": "Abre una nueva pestaña",
|
||||
"close-active-tab": "Cierra la pestaña activa",
|
||||
"reopen-last-tab": "Vuelve a abrir la última pestaña cerrada",
|
||||
"activate-next-tab": "Activa la pestaña de la derecha",
|
||||
"activate-previous-tab": "Activa la pestaña de la izquierda",
|
||||
"open-new-window": "Abrir nueva ventana vacía",
|
||||
"toggle-tray": "Muestra/Oculta la aplicación en la bandeja del sistema",
|
||||
"first-tab": "Activa la primera pestaña de la lista",
|
||||
"second-tab": "Activa la segunda pestaña de la lista",
|
||||
"third-tab": "Activa la tercera pestaña de la lista",
|
||||
"fourth-tab": "Activa la cuarta pestaña de la lista",
|
||||
"fifth-tab": "Activa la quinta pestaña de la lista",
|
||||
"sixth-tab": "Activa la sexta pestaña de la lista",
|
||||
"seventh-tab": "Activa la séptima pestaña de la lista",
|
||||
"eight-tab": "Activa la octava pestaña de la lista",
|
||||
"ninth-tab": "Activa la novena pestaña de la lista",
|
||||
"last-tab": "Activa la última pestaña de la lista",
|
||||
"dialogs": "Diálogos",
|
||||
"show-note-source": "Muestra el cuadro de diálogo Fuente de nota",
|
||||
"show-options": "Muestra el cuadro de diálogo Opciones",
|
||||
"show-revisions": "Muestra el cuadro de diálogo Revisiones de notas",
|
||||
"show-recent-changes": "Muestra el cuadro de diálogo Cambios recientes",
|
||||
"show-sql-console": "Muestra el cuadro de diálogo Consola SQL",
|
||||
"show-backend-log": "Muestra el cuadro de diálogo Registro de backend",
|
||||
"show-help": "Muestra ayuda/hoja de referencia integrada",
|
||||
"show-cheatsheet": "Muestra un modal con operaciones de teclado comunes",
|
||||
"text-note-operations": "Operaciones de notas de texto",
|
||||
"add-link-to-text": "Abrir cuadro de diálogo para agregar un enlace al texto",
|
||||
"follow-link-under-cursor": "Seguir el enlace dentro del cual se coloca el cursor",
|
||||
"insert-date-and-time-to-text": "Insertar fecha y hora actuales en el texto",
|
||||
"paste-markdown-into-text": "Pega Markdown del portapapeles en la nota de texto",
|
||||
"cut-into-note": "Corta la selección de la nota actual y crea una subnota con el texto seleccionado",
|
||||
"add-include-note-to-text": "Abre el cuadro de diálogo para incluir una nota",
|
||||
"edit-readonly-note": "Editar una nota de sólo lectura",
|
||||
"attributes-labels-and-relations": "Atributos (etiquetas y relaciones)",
|
||||
"add-new-label": "Crear nueva etiqueta",
|
||||
"create-new-relation": "Crear nueva relación",
|
||||
"ribbon-tabs": "Pestañas de cinta",
|
||||
"toggle-basic-properties": "Alternar propiedades básicas",
|
||||
"toggle-file-properties": "Alternar propiedades de archivo",
|
||||
"toggle-image-properties": "Alternar propiedades de imagen",
|
||||
"toggle-owned-attributes": "Alternar atributos de propiedad",
|
||||
"toggle-inherited-attributes": "Alternar atributos heredados",
|
||||
"toggle-promoted-attributes": "Alternar atributos promocionados",
|
||||
"toggle-link-map": "Alternar mapa de enlaces",
|
||||
"toggle-note-info": "Alternar información de nota",
|
||||
"toggle-note-paths": "Alternar rutas de notas",
|
||||
"toggle-similar-notes": "Alternar notas similares",
|
||||
"other": "Otro",
|
||||
"toggle-right-pane": "Alternar la visualización del panel derecho, que incluye la tabla de contenidos y aspectos destacados",
|
||||
"print-active-note": "Imprimir nota activa",
|
||||
"open-note-externally": "Abrir nota como un archivo con la aplicación predeterminada",
|
||||
"render-active-note": "Renderizar (volver a renderizar) nota activa",
|
||||
"run-active-note": "Ejecutar nota de código JavaScript activa (frontend/backend)",
|
||||
"toggle-note-hoisting": "Alterna la elevación de la nota activa",
|
||||
"unhoist": "Bajar desde cualquier lugar",
|
||||
"reload-frontend-app": "Recargar frontend de la aplicación",
|
||||
"open-dev-tools": "Abrir herramientas de desarrollo",
|
||||
"find-in-text": "Alternar panel de búsqueda",
|
||||
"toggle-left-note-tree-panel": "Alternar panel izquierdo (árbol de notas)",
|
||||
"toggle-full-screen": "Alternar pantalla completa",
|
||||
"zoom-out": "Alejar",
|
||||
"zoom-in": "Acercar",
|
||||
"note-navigation": "Navegación de notas",
|
||||
"reset-zoom-level": "Restablecer nivel de zoom",
|
||||
"copy-without-formatting": "Copiar el texto seleccionado sin formatear",
|
||||
"force-save-revision": "Forzar la creación/guardado de una nueva revisión de nota de la nota activa",
|
||||
"toggle-book-properties": "Alternar propiedades del libro",
|
||||
"toggle-classic-editor-toolbar": "Alternar la pestaña de formato por el editor con barra de herramientas fija",
|
||||
"export-as-pdf": "Exporta la nota actual como un PDF",
|
||||
"toggle-zen-mode": "Habilita/Deshabilita el modo Zen (IU mínima para edición sin distracciones)"
|
||||
},
|
||||
"login": {
|
||||
"title": "Iniciar sesión",
|
||||
"heading": "Iniciar sesión en Trilium",
|
||||
"incorrect-totp": "El TOTP es incorrecto. Por favor, intente de nuevo.",
|
||||
"incorrect-password": "La contraseña es incorrecta. Por favor inténtalo de nuevo.",
|
||||
"password": "Contraseña",
|
||||
"remember-me": "Recordarme",
|
||||
"button": "Iniciar sesión",
|
||||
"sign_in_with_sso": "Iniciar sesión con {{ ssoIssuerName }}"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Establecer contraseña",
|
||||
"heading": "Establecer contraseña",
|
||||
"description": "Antes de poder comenzar a usar Trilium desde la web, primero debe establecer una contraseña. Luego utilizará esta contraseña para iniciar sesión.",
|
||||
"password": "Contraseña",
|
||||
"password-confirmation": "Confirmación de contraseña",
|
||||
"button": "Establecer contraseña"
|
||||
},
|
||||
"javascript-required": "Trilium requiere que JavaScript esté habilitado.",
|
||||
"setup": {
|
||||
"heading": "Configuración de Trilium Notes",
|
||||
"new-document": "Soy un usuario nuevo y quiero crear un nuevo documento de Trilium para mis notas",
|
||||
"sync-from-desktop": "Ya tengo una instancia de escritorio y quiero configurar la sincronización con ella",
|
||||
"sync-from-server": "Ya tengo una instancia de servidor y quiero configurar la sincronización con ella",
|
||||
"next": "Siguiente",
|
||||
"init-in-progress": "Inicialización del documento en curso",
|
||||
"redirecting": "En breve será redirigido a la aplicación.",
|
||||
"title": "Configuración"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Sincronizar desde el escritorio",
|
||||
"description": "Esta configuración debe iniciarse desde la instancia de escritorio:",
|
||||
"step1": "Abra su instancia de escritorio de Trilium Notes.",
|
||||
"step2": "En el menú Trilium, dé clic en Opciones.",
|
||||
"step3": "Dé clic en la categoría Sincronizar.",
|
||||
"step4": "Cambie la dirección de la instancia del servidor a: {{- host}} y dé clic en Guardar.",
|
||||
"step5": "Dé clic en el botón \"Probar sincronización\" para verificar que la conexión fue exitosa.",
|
||||
"step6": "Una vez que haya completado estos pasos, dé clic en {{- link}}.",
|
||||
"step6-here": "aquí"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Sincronización desde el servidor",
|
||||
"instructions": "Por favor, ingrese la dirección y las credenciales del servidor Trilium a continuación. Esto descargará todo el documento de Trilium desde el servidor y configurará la sincronización. Dependiendo del tamaño del documento y de la velocidad de su conexión, esto puede tardar un poco.",
|
||||
"server-host": "Dirección del servidor Trilium",
|
||||
"server-host-placeholder": "https://<hostname>:<port>",
|
||||
"proxy-server": "Servidor proxy (opcional)",
|
||||
"proxy-server-placeholder": "https://<hostname>:<port>",
|
||||
"note": "Nota:",
|
||||
"proxy-instruction": "Si deja la configuración de proxy en blanco, se utilizará el proxy del sistema (aplica únicamente a la aplicación de escritorio)",
|
||||
"password": "Contraseña",
|
||||
"password-placeholder": "Contraseña",
|
||||
"back": "Atrás",
|
||||
"finish-setup": "Finalizar la configuración"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Sincronización en progreso",
|
||||
"successful": "La sincronización se ha configurado correctamente. La sincronización inicial tardará algún tiempo en finalizar. Una vez hecho esto, será redirigido a la página de inicio de sesión.",
|
||||
"outstanding-items": "Elementos de sincronización destacados:",
|
||||
"outstanding-items-default": "N/A"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "No encontrado",
|
||||
"heading": "No encontrado"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "padre:",
|
||||
"clipped-from": "Esta nota fue recortada originalmente de {{- url}}",
|
||||
"child-notes": "Subnotas:",
|
||||
"no-content": "Esta nota no tiene contenido."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Lunes",
|
||||
"tuesday": "Martes",
|
||||
"wednesday": "Miércoles",
|
||||
"thursday": "Jueves",
|
||||
"friday": "Viernes",
|
||||
"saturday": "Sábado",
|
||||
"sunday": "Domingo"
|
||||
},
|
||||
"weekdayNumber": "Semana {weekNumber}",
|
||||
"months": {
|
||||
"january": "Enero",
|
||||
"february": "Febrero",
|
||||
"march": "Marzo",
|
||||
"april": "Abril",
|
||||
"may": "Mayo",
|
||||
"june": "Junio",
|
||||
"july": "Julio",
|
||||
"august": "Agosto",
|
||||
"september": "Septiembre",
|
||||
"october": "Octubre",
|
||||
"november": "Noviembre",
|
||||
"december": "Diciembre"
|
||||
},
|
||||
"quarterNumber": "Cuarto {quarterNumber}",
|
||||
"special_notes": {
|
||||
"search_prefix": "Buscar:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "El servidor de sincronización no está configurado. Por favor configure primero la sincronización.",
|
||||
"successful": "El protocolo de enlace del servidor de sincronización ha sido exitoso, la sincronización ha comenzado."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "Notas ocultas",
|
||||
"search-history-title": "Buscar historial",
|
||||
"note-map-title": "Mapa de nota",
|
||||
"sql-console-history-title": "Historial de consola SQL",
|
||||
"shared-notes-title": "Notas compartidas",
|
||||
"bulk-action-title": "Acción en lote",
|
||||
"backend-log-title": "Registro de Backend",
|
||||
"user-hidden-title": "Usuario oculto",
|
||||
"launch-bar-templates-title": "Plantillas de barra de lanzamiento",
|
||||
"base-abstract-launcher-title": "Lanzador abstracto base",
|
||||
"command-launcher-title": "Lanzador de comando",
|
||||
"note-launcher-title": "Lanzador de nota",
|
||||
"script-launcher-title": "Lanzador de script",
|
||||
"built-in-widget-title": "Widget integrado",
|
||||
"spacer-title": "Espaciador",
|
||||
"custom-widget-title": "Widget personalizado",
|
||||
"launch-bar-title": "Barra de lanzamiento",
|
||||
"available-launchers-title": "Lanzadores disponibles",
|
||||
"go-to-previous-note-title": "Ir a nota previa",
|
||||
"go-to-next-note-title": "Ir a nota siguiente",
|
||||
"new-note-title": "Nueva nota",
|
||||
"search-notes-title": "Buscar notas",
|
||||
"calendar-title": "Calendario",
|
||||
"recent-changes-title": "Cambios recientes",
|
||||
"bookmarks-title": "Marcadores",
|
||||
"open-today-journal-note-title": "Abrir nota del diario de hoy",
|
||||
"quick-search-title": "Búsqueda rápida",
|
||||
"protected-session-title": "Sesión protegida",
|
||||
"sync-status-title": "Sincronizar estado",
|
||||
"settings-title": "Ajustes",
|
||||
"llm-chat-title": "Chat con notas",
|
||||
"options-title": "Opciones",
|
||||
"appearance-title": "Apariencia",
|
||||
"shortcuts-title": "Atajos",
|
||||
"text-notes": "Notas de texto",
|
||||
"code-notes-title": "Notas de código",
|
||||
"images-title": "Imágenes",
|
||||
"spellcheck-title": "Corrección ortográfica",
|
||||
"password-title": "Contraseña",
|
||||
"multi-factor-authentication-title": "MFA",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Respaldo",
|
||||
"sync-title": "Sincronizar",
|
||||
"ai-llm-title": "IA/LLM",
|
||||
"other": "Otros",
|
||||
"advanced-title": "Avanzado",
|
||||
"visible-launchers-title": "Lanzadores visibles",
|
||||
"user-guide": "Guía de Usuario",
|
||||
"localization": "Idioma y Región",
|
||||
"inbox-title": "Bandeja"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Nueva nota",
|
||||
"duplicate-note-suffix": "(dup)",
|
||||
"duplicate-note-title": "{{- noteTitle}} {{duplicateNoteSuffix}}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "El archivo de registro del backend '{{fileName}}' no existe (aún).",
|
||||
"reading-log-failed": "Leer el archivo de registro del backend '{{fileName}}' falló."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Este tipo de nota no puede ser mostrado."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "Documento PDF (*.pdf)",
|
||||
"unable-to-export-message": "La nota actual no pudo ser exportada como PDF.",
|
||||
"unable-to-export-title": "No es posible exportar como PDF",
|
||||
"unable-to-save-message": "No se pudo escribir en el archivo seleccionado. Intente de nuevo o seleccione otro destino."
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "Trilium Notes",
|
||||
"close": "Cerrar Trilium",
|
||||
"recents": "Notas recientes",
|
||||
"bookmarks": "Marcadores",
|
||||
"today": "Abrir nota del diario de hoy",
|
||||
"new-note": "Nueva nota",
|
||||
"show-windows": "Mostrar ventanas",
|
||||
"open_new_window": "Abrir nueva ventana"
|
||||
},
|
||||
"migration": {
|
||||
"old_version": "La migración directa desde tu versión actual no está soportada. Por favor actualice a v0.60.4 primero y solo después a esta versión.",
|
||||
"error_message": "Error durante la migración a la versión {{version}}: {{stack}}",
|
||||
"wrong_db_version": "La versión de la DB {{version}} es más nueva que la versión de la DB actual {{targetVersion}}, lo que significa que fue creada por una versión más reciente e incompatible de Trilium. Actualice a la última versión de Trilium para resolver este problema."
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Error"
|
||||
},
|
||||
"share_theme": {
|
||||
"site-theme": "Tema de sitio",
|
||||
"search_placeholder": "Búsqueda...",
|
||||
"image_alt": "Imagen de artículo",
|
||||
"last-updated": "Última actualización en {{-date}}",
|
||||
"subpages": "Subpáginas:",
|
||||
"on-this-page": "En esta página",
|
||||
"expand": "Expandir"
|
||||
}
|
||||
"keyboard_actions": {
|
||||
"back-in-note-history": "Navegar a la nota previa en el historial",
|
||||
"forward-in-note-history": "Navegar a la nota siguiente en el historial",
|
||||
"open-jump-to-note-dialog": "Abrir cuadro de diálogo \"Saltar a nota\"",
|
||||
"scroll-to-active-note": "Desplazarse a la nota activa en el árbol de notas",
|
||||
"quick-search": "Activar barra de búsqueda rápida",
|
||||
"search-in-subtree": "Buscar notas en el subárbol de la nota activa",
|
||||
"expand-subtree": "Expandir el subárbol de la nota actual",
|
||||
"collapse-tree": "Colapsa el árbol de notas completo",
|
||||
"collapse-subtree": "Colapsa el subárbol de la nota actual",
|
||||
"sort-child-notes": "Ordenar subnotas",
|
||||
"creating-and-moving-notes": "Creando y moviendo notas",
|
||||
"create-note-after": "Crear nota después de la nota activa",
|
||||
"create-note-into": "Crear nota como subnota de la nota activa",
|
||||
"create-note-into-inbox": "Crear una nota en la bandeja de entrada (si está definida) o nota del día",
|
||||
"delete-note": "Eliminar nota",
|
||||
"move-note-up": "Subir nota",
|
||||
"move-note-down": "Bajar nota",
|
||||
"move-note-up-in-hierarchy": "Subir nota en la jerarquía",
|
||||
"move-note-down-in-hierarchy": "Bajar nota en la jerarquía",
|
||||
"edit-note-title": "Saltar del árbol al detalle de la nota y editar el título",
|
||||
"edit-branch-prefix": "Mostrar cuadro de diálogo Editar prefijo de rama",
|
||||
"cloneNotesTo": "Clonar notas seleccionadas",
|
||||
"moveNotesTo": "Mover notas seleccionadas",
|
||||
"note-clipboard": "Portapapeles de notas",
|
||||
"copy-notes-to-clipboard": "Copiar las notas seleccionadas al portapapeles",
|
||||
"paste-notes-from-clipboard": "Pegar las notas del portapapeles en una nota activa",
|
||||
"cut-notes-to-clipboard": "Cortar las notas seleccionadas al portapapeles",
|
||||
"select-all-notes-in-parent": "Seleccionar todas las notas del nivel de la nota actual",
|
||||
"add-note-above-to-the-selection": "Agregar nota arriba de la selección",
|
||||
"add-note-below-to-selection": "Agregar nota arriba de la selección",
|
||||
"duplicate-subtree": "Duplicar subárbol",
|
||||
"tabs-and-windows": "Pestañas y ventanas",
|
||||
"open-new-tab": "Abre una nueva pestaña",
|
||||
"close-active-tab": "Cierra la pestaña activa",
|
||||
"reopen-last-tab": "Vuelve a abrir la última pestaña cerrada",
|
||||
"activate-next-tab": "Activa la pestaña de la derecha",
|
||||
"activate-previous-tab": "Activa la pestaña de la izquierda",
|
||||
"open-new-window": "Abrir nueva ventana vacía",
|
||||
"toggle-tray": "Muestra/Oculta la aplicación en la bandeja del sistema",
|
||||
"first-tab": "Activa la primera pestaña de la lista",
|
||||
"second-tab": "Activa la segunda pestaña de la lista",
|
||||
"third-tab": "Activa la tercera pestaña de la lista",
|
||||
"fourth-tab": "Activa la cuarta pestaña de la lista",
|
||||
"fifth-tab": "Activa la quinta pestaña de la lista",
|
||||
"sixth-tab": "Activa la sexta pestaña de la lista",
|
||||
"seventh-tab": "Activa la séptima pestaña de la lista",
|
||||
"eight-tab": "Activa la octava pestaña de la lista",
|
||||
"ninth-tab": "Activa la novena pestaña de la lista",
|
||||
"last-tab": "Activa la última pestaña de la lista",
|
||||
"dialogs": "Diálogos",
|
||||
"show-note-source": "Muestra el cuadro de diálogo Fuente de nota",
|
||||
"show-options": "Muestra el cuadro de diálogo Opciones",
|
||||
"show-revisions": "Muestra el cuadro de diálogo Revisiones de notas",
|
||||
"show-recent-changes": "Muestra el cuadro de diálogo Cambios recientes",
|
||||
"show-sql-console": "Muestra el cuadro de diálogo Consola SQL",
|
||||
"show-backend-log": "Muestra el cuadro de diálogo Registro de backend",
|
||||
"show-help": "Muestra ayuda/hoja de referencia integrada",
|
||||
"show-cheatsheet": "Muestra un modal con operaciones de teclado comunes",
|
||||
"text-note-operations": "Operaciones de notas de texto",
|
||||
"add-link-to-text": "Abrir cuadro de diálogo para agregar un enlace al texto",
|
||||
"follow-link-under-cursor": "Seguir el enlace dentro del cual se coloca el cursor",
|
||||
"insert-date-and-time-to-text": "Insertar fecha y hora actuales en el texto",
|
||||
"paste-markdown-into-text": "Pega Markdown del portapapeles en la nota de texto",
|
||||
"cut-into-note": "Corta la selección de la nota actual y crea una subnota con el texto seleccionado",
|
||||
"add-include-note-to-text": "Abre el cuadro de diálogo para incluir una nota",
|
||||
"edit-readonly-note": "Editar una nota de sólo lectura",
|
||||
"attributes-labels-and-relations": "Atributos (etiquetas y relaciones)",
|
||||
"add-new-label": "Crear nueva etiqueta",
|
||||
"create-new-relation": "Crear nueva relación",
|
||||
"ribbon-tabs": "Pestañas de cinta",
|
||||
"toggle-basic-properties": "Alternar propiedades básicas",
|
||||
"toggle-file-properties": "Alternar propiedades de archivo",
|
||||
"toggle-image-properties": "Alternar propiedades de imagen",
|
||||
"toggle-owned-attributes": "Alternar atributos de propiedad",
|
||||
"toggle-inherited-attributes": "Alternar atributos heredados",
|
||||
"toggle-promoted-attributes": "Alternar atributos destacados",
|
||||
"toggle-link-map": "Alternar mapa de enlaces",
|
||||
"toggle-note-info": "Alternar información de nota",
|
||||
"toggle-note-paths": "Alternar rutas de notas",
|
||||
"toggle-similar-notes": "Alternar notas similares",
|
||||
"other": "Otro",
|
||||
"toggle-right-pane": "Alternar la visualización del panel derecho, que incluye la tabla de contenidos y aspectos destacados",
|
||||
"print-active-note": "Imprimir nota activa",
|
||||
"open-note-externally": "Abrir nota como un archivo con la aplicación predeterminada",
|
||||
"render-active-note": "Renderizar (volver a renderizar) nota activa",
|
||||
"run-active-note": "Ejecutar nota de código JavaScript activa (frontend/backend)",
|
||||
"toggle-note-hoisting": "Alterna la elevación de la nota activa",
|
||||
"unhoist": "Bajar desde cualquier lugar",
|
||||
"reload-frontend-app": "Recargar frontend de la aplicación",
|
||||
"open-dev-tools": "Abrir herramientas de desarrollo",
|
||||
"find-in-text": "Alternar panel de búsqueda",
|
||||
"toggle-left-note-tree-panel": "Alternar panel izquierdo (árbol de notas)",
|
||||
"toggle-full-screen": "Alternar pantalla completa",
|
||||
"zoom-out": "Alejar",
|
||||
"zoom-in": "Acercar",
|
||||
"note-navigation": "Navegación de notas",
|
||||
"reset-zoom-level": "Restablecer nivel de zoom",
|
||||
"copy-without-formatting": "Copiar el texto seleccionado sin formatear",
|
||||
"force-save-revision": "Forzar la creación/guardado de una nueva revisión de nota de la nota activa",
|
||||
"toggle-book-properties": "Alternar propiedades del libro",
|
||||
"toggle-classic-editor-toolbar": "Alternar la pestaña de formato por el editor con barra de herramientas fija",
|
||||
"export-as-pdf": "Exporta la nota actual como un PDF",
|
||||
"toggle-zen-mode": "Habilita/Deshabilita el modo Zen (IU mínima para edición sin distracciones)",
|
||||
"open-command-palette": "Abrir paleta de comandos",
|
||||
"clone-notes-to": "Clonar notas seleccionadas",
|
||||
"move-notes-to": "Mover notas seleccionadas"
|
||||
},
|
||||
"login": {
|
||||
"title": "Iniciar sesión",
|
||||
"heading": "Iniciar sesión en Trilium",
|
||||
"incorrect-totp": "El TOTP es incorrecto. Por favor, intente de nuevo.",
|
||||
"incorrect-password": "La contraseña es incorrecta. Por favor inténtalo de nuevo.",
|
||||
"password": "Contraseña",
|
||||
"remember-me": "Recordarme",
|
||||
"button": "Iniciar sesión",
|
||||
"sign_in_with_sso": "Iniciar sesión con {{ ssoIssuerName }}"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Establecer contraseña",
|
||||
"heading": "Establecer contraseña",
|
||||
"description": "Antes de poder comenzar a usar Trilium desde la web, primero debe establecer una contraseña. Luego utilizará esta contraseña para iniciar sesión.",
|
||||
"password": "Contraseña",
|
||||
"password-confirmation": "Confirmación de contraseña",
|
||||
"button": "Establecer contraseña"
|
||||
},
|
||||
"javascript-required": "Trilium requiere que JavaScript esté habilitado.",
|
||||
"setup": {
|
||||
"heading": "Configuración de Trilium Notes",
|
||||
"new-document": "Soy un usuario nuevo y quiero crear un nuevo documento de Trilium para mis notas",
|
||||
"sync-from-desktop": "Ya tengo una instancia de escritorio y quiero configurar la sincronización con ella",
|
||||
"sync-from-server": "Ya tengo una instancia de servidor y quiero configurar la sincronización con ella",
|
||||
"next": "Siguiente",
|
||||
"init-in-progress": "Inicialización del documento en curso",
|
||||
"redirecting": "En breve será redirigido a la aplicación.",
|
||||
"title": "Configuración"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Sincronizar desde el escritorio",
|
||||
"description": "Esta configuración debe iniciarse desde la instancia de escritorio:",
|
||||
"step1": "Abra su instancia de escritorio de Trilium Notes.",
|
||||
"step2": "En el menú Trilium, dé clic en Opciones.",
|
||||
"step3": "Dé clic en la categoría Sincronizar.",
|
||||
"step4": "Cambie la dirección de la instancia del servidor a: {{- host}} y dé clic en Guardar.",
|
||||
"step5": "Dé clic en el botón \"Probar sincronización\" para verificar que la conexión fue exitosa.",
|
||||
"step6": "Una vez que haya completado estos pasos, dé clic en {{- link}}.",
|
||||
"step6-here": "aquí"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Sincronización desde el servidor",
|
||||
"instructions": "Por favor, ingrese la dirección y las credenciales del servidor Trilium a continuación. Esto descargará todo el documento de Trilium desde el servidor y configurará la sincronización. Dependiendo del tamaño del documento y de la velocidad de su conexión, esto puede tardar un poco.",
|
||||
"server-host": "Dirección del servidor Trilium",
|
||||
"server-host-placeholder": "https://<hostname>:<port>",
|
||||
"proxy-server": "Servidor proxy (opcional)",
|
||||
"proxy-server-placeholder": "https://<hostname>:<port>",
|
||||
"note": "Nota:",
|
||||
"proxy-instruction": "Si deja la configuración de proxy en blanco, se utilizará el proxy del sistema (aplica únicamente a la aplicación de escritorio)",
|
||||
"password": "Contraseña",
|
||||
"password-placeholder": "Contraseña",
|
||||
"back": "Atrás",
|
||||
"finish-setup": "Finalizar la configuración"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Sincronización en progreso",
|
||||
"successful": "La sincronización se ha configurado correctamente. La sincronización inicial tardará algún tiempo en finalizar. Una vez hecho esto, será redirigido a la página de inicio de sesión.",
|
||||
"outstanding-items": "Elementos de sincronización destacados:",
|
||||
"outstanding-items-default": "N/A"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "No encontrado",
|
||||
"heading": "No encontrado"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "padre:",
|
||||
"clipped-from": "Esta nota fue recortada originalmente de {{- url}}",
|
||||
"child-notes": "Subnotas:",
|
||||
"no-content": "Esta nota no tiene contenido."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Lunes",
|
||||
"tuesday": "Martes",
|
||||
"wednesday": "Miércoles",
|
||||
"thursday": "Jueves",
|
||||
"friday": "Viernes",
|
||||
"saturday": "Sábado",
|
||||
"sunday": "Domingo"
|
||||
},
|
||||
"weekdayNumber": "Semana {weekNumber}",
|
||||
"months": {
|
||||
"january": "Enero",
|
||||
"february": "Febrero",
|
||||
"march": "Marzo",
|
||||
"april": "Abril",
|
||||
"may": "Mayo",
|
||||
"june": "Junio",
|
||||
"july": "Julio",
|
||||
"august": "Agosto",
|
||||
"september": "Septiembre",
|
||||
"october": "Octubre",
|
||||
"november": "Noviembre",
|
||||
"december": "Diciembre"
|
||||
},
|
||||
"quarterNumber": "Cuarto {quarterNumber}",
|
||||
"special_notes": {
|
||||
"search_prefix": "Buscar:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "El servidor de sincronización no está configurado. Por favor configure primero la sincronización.",
|
||||
"successful": "El protocolo de enlace del servidor de sincronización ha sido exitoso, la sincronización ha comenzado."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "Notas ocultas",
|
||||
"search-history-title": "Buscar historial",
|
||||
"note-map-title": "Mapa de nota",
|
||||
"sql-console-history-title": "Historial de consola SQL",
|
||||
"shared-notes-title": "Notas compartidas",
|
||||
"bulk-action-title": "Acción en lote",
|
||||
"backend-log-title": "Registro de Backend",
|
||||
"user-hidden-title": "Usuario oculto",
|
||||
"launch-bar-templates-title": "Plantillas de barra de lanzamiento",
|
||||
"base-abstract-launcher-title": "Lanzador abstracto base",
|
||||
"command-launcher-title": "Lanzador de comando",
|
||||
"note-launcher-title": "Lanzador de nota",
|
||||
"script-launcher-title": "Lanzador de script",
|
||||
"built-in-widget-title": "Widget integrado",
|
||||
"spacer-title": "Espaciador",
|
||||
"custom-widget-title": "Widget personalizado",
|
||||
"launch-bar-title": "Barra de lanzamiento",
|
||||
"available-launchers-title": "Lanzadores disponibles",
|
||||
"go-to-previous-note-title": "Ir a nota previa",
|
||||
"go-to-next-note-title": "Ir a nota siguiente",
|
||||
"new-note-title": "Nueva nota",
|
||||
"search-notes-title": "Buscar notas",
|
||||
"calendar-title": "Calendario",
|
||||
"recent-changes-title": "Cambios recientes",
|
||||
"bookmarks-title": "Marcadores",
|
||||
"open-today-journal-note-title": "Abrir nota del diario de hoy",
|
||||
"quick-search-title": "Búsqueda rápida",
|
||||
"protected-session-title": "Sesión protegida",
|
||||
"sync-status-title": "Sincronizar estado",
|
||||
"settings-title": "Ajustes",
|
||||
"llm-chat-title": "Chat con notas",
|
||||
"options-title": "Opciones",
|
||||
"appearance-title": "Apariencia",
|
||||
"shortcuts-title": "Atajos",
|
||||
"text-notes": "Notas de texto",
|
||||
"code-notes-title": "Notas de código",
|
||||
"images-title": "Imágenes",
|
||||
"spellcheck-title": "Corrección ortográfica",
|
||||
"password-title": "Contraseña",
|
||||
"multi-factor-authentication-title": "MFA",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Respaldo",
|
||||
"sync-title": "Sincronizar",
|
||||
"ai-llm-title": "IA/LLM",
|
||||
"other": "Otros",
|
||||
"advanced-title": "Avanzado",
|
||||
"visible-launchers-title": "Lanzadores visibles",
|
||||
"user-guide": "Guía de Usuario",
|
||||
"localization": "Idioma y Región",
|
||||
"inbox-title": "Bandeja",
|
||||
"jump-to-note-title": "Saltar a..."
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Nueva nota",
|
||||
"duplicate-note-suffix": "(dup)",
|
||||
"duplicate-note-title": "{{- noteTitle}} {{duplicateNoteSuffix}}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "El archivo de registro del backend '{{fileName}}' no existe (aún).",
|
||||
"reading-log-failed": "Leer el archivo de registro del backend '{{fileName}}' falló."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Este tipo de nota no puede ser mostrado."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "Documento PDF (*.pdf)",
|
||||
"unable-to-export-message": "La nota actual no pudo ser exportada como PDF.",
|
||||
"unable-to-export-title": "No es posible exportar como PDF",
|
||||
"unable-to-save-message": "No se pudo escribir en el archivo seleccionado. Intente de nuevo o seleccione otro destino."
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "Trilium Notes",
|
||||
"close": "Cerrar Trilium",
|
||||
"recents": "Notas recientes",
|
||||
"bookmarks": "Marcadores",
|
||||
"today": "Abrir nota del diario de hoy",
|
||||
"new-note": "Nueva nota",
|
||||
"show-windows": "Mostrar ventanas",
|
||||
"open_new_window": "Abrir nueva ventana"
|
||||
},
|
||||
"migration": {
|
||||
"old_version": "La migración directa desde tu versión actual no está soportada. Por favor actualice a v0.60.4 primero y solo después a esta versión.",
|
||||
"error_message": "Error durante la migración a la versión {{version}}: {{stack}}",
|
||||
"wrong_db_version": "La versión de la DB {{version}} es más nueva que la versión de la DB actual {{targetVersion}}, lo que significa que fue creada por una versión más reciente e incompatible de Trilium. Actualice a la última versión de Trilium para resolver este problema."
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Error"
|
||||
},
|
||||
"share_theme": {
|
||||
"site-theme": "Tema de sitio",
|
||||
"search_placeholder": "Búsqueda...",
|
||||
"image_alt": "Imagen de artículo",
|
||||
"last-updated": "Última actualización en {{-date}}",
|
||||
"subpages": "Subpáginas:",
|
||||
"on-this-page": "En esta página",
|
||||
"expand": "Expandir"
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"jump-to-note": "Saltar a...",
|
||||
"command-palette": "Paleta de comandos",
|
||||
"scroll-to-active-note": "Desplazarse a la nota activa",
|
||||
"quick-search": "Búsqueda rápida",
|
||||
"search-in-subtree": "Buscar en subárbol",
|
||||
"expand-subtree": "Expandir subárbol",
|
||||
"collapse-tree": "Colapsar árbol",
|
||||
"collapse-subtree": "Colapsar subárbol",
|
||||
"sort-child-notes": "Ordenar nodos hijos",
|
||||
"create-note-after": "Crear nota tras",
|
||||
"create-note-into": "Crear nota en",
|
||||
"create-note-into-inbox": "Crear nota en bandeja de entrada",
|
||||
"delete-notes": "Eliminar notas",
|
||||
"move-note-up": "Subir nota",
|
||||
"move-note-down": "Bajar nota",
|
||||
"move-note-up-in-hierarchy": "Subir nota en la jerarquía",
|
||||
"move-note-down-in-hierarchy": "Bajar nota en la jerarquía",
|
||||
"edit-note-title": "Editar título de nota",
|
||||
"edit-branch-prefix": "Editar prefijo de rama",
|
||||
"clone-notes-to": "Clonar notas a",
|
||||
"move-notes-to": "Mover notas a",
|
||||
"copy-notes-to-clipboard": "Copiar notas al portapapeles",
|
||||
"paste-notes-from-clipboard": "Pegar notas del portapapeles",
|
||||
"add-note-above-to-selection": "Añadir nota superior a la selección",
|
||||
"add-note-below-to-selection": "Añadir nota inferior a la selección",
|
||||
"duplicate-subtree": "Duplicar subárbol",
|
||||
"open-new-tab": "Abrir nueva pestaña",
|
||||
"close-active-tab": "Cerrar pestaña activa",
|
||||
"reopen-last-tab": "Reabrir última pestaña",
|
||||
"activate-next-tab": "Activar siguiente pestaña",
|
||||
"activate-previous-tab": "Activar pestaña anterior",
|
||||
"open-new-window": "Abrir nueva ventana",
|
||||
"show-options": "Mostrar opciones",
|
||||
"show-revisions": "Mostrar revisiones",
|
||||
"show-recent-changes": "Mostrar cambios recientes",
|
||||
"show-sql-console": "Mostrar consola SQL",
|
||||
"switch-to-first-tab": "Ir a la primera pestaña",
|
||||
"switch-to-second-tab": "Ir a la segunda pestaña",
|
||||
"switch-to-third-tab": "Ir a la tercera pestaña",
|
||||
"switch-to-fourth-tab": "Ir a la cuarta pestaña",
|
||||
"switch-to-fifth-tab": "Ir a la quinta pestaña",
|
||||
"switch-to-sixth-tab": "Ir a la sexta pestaña",
|
||||
"switch-to-seventh-tab": "Ir a la séptima pestaña",
|
||||
"switch-to-eighth-tab": "Ir a la octava pestaña",
|
||||
"switch-to-ninth-tab": "Ir a la novena pestaña",
|
||||
"switch-to-last-tab": "Ir a la última pestaña",
|
||||
"show-note-source": "Mostrar nota fuente",
|
||||
"show-help": "Mostrar ayuda",
|
||||
"add-new-label": "Añadir nueva etiqueta",
|
||||
"add-new-relation": "Añadir nueva relación",
|
||||
"print-active-note": "Imprimir nota activa",
|
||||
"export-active-note-as-pdf": "Exportar nota activa como PDF",
|
||||
"open-note-externally": "Abrir nota externamente",
|
||||
"find-in-text": "Encontrar en texto",
|
||||
"copy-without-formatting": "Copiar sin formato",
|
||||
"reset-zoom-level": "Restablecer el nivel de zoom",
|
||||
"open-developer-tools": "Abrir herramientas de desarrollo",
|
||||
"insert-date-and-time-to-text": "Insertar fecha y hora al texto",
|
||||
"edit-read-only-note": "Editar nota de solo lectura",
|
||||
"toggle-system-tray-icon": "Mostrar/ocultar icono en la bandeja del sistema",
|
||||
"toggle-zen-mode": "Activar/desactivar modo Zen",
|
||||
"add-link-to-text": "Añadir enlace al texto",
|
||||
"zoom-in": "Acercar",
|
||||
"zoom-out": "Alejar",
|
||||
"toggle-full-screen": "Activar/desactivar pantalla completa",
|
||||
"toggle-left-pane": "Abrir/cerrar panel izquierdo",
|
||||
"toggle-right-pane": "Mostrar/ocultar panel derecho",
|
||||
"unhoist-note": "Desanclar nota",
|
||||
"toggle-note-hoisting": "Activar/desactivar anclaje de nota",
|
||||
"show-cheatsheet": "Mostrar hoja de referencia",
|
||||
"follow-link-under-cursor": "Seguir enlace bajo cursor",
|
||||
"reload-frontend-app": "Recargar aplicación del cliente",
|
||||
"run-active-note": "Ejecutar nota activa",
|
||||
"render-active-note": "Generar nota activa",
|
||||
"back-in-note-history": "Anterior en el historial de notas",
|
||||
"forward-in-note-history": "Posterior en el historial de notas",
|
||||
"cut-notes-to-clipboard": "Cortar notas al portapapeles",
|
||||
"select-all-notes-in-parent": "Seleccionar todas las notas en padre",
|
||||
"show-backend-log": "Mostrar registro del servidor",
|
||||
"paste-markdown-into-text": "Pegar Markdown en el texto",
|
||||
"cut-into-note": "Cortar en la nota",
|
||||
"add-include-note-to-text": "Agregar nota incluida al texto",
|
||||
"force-save-revision": "Forzar guardado de revisión",
|
||||
"toggle-ribbon-tab-classic-editor": "Mostrar pestaña de la cinta de opciones: Editor clásico",
|
||||
"toggle-ribbon-tab-basic-properties": "Mostrar pestaña de la cinta de opciones: Propiedades básicas",
|
||||
"toggle-ribbon-tab-book-properties": "Mostrar pestaña de la cinta de opciones: Propiedades de libro",
|
||||
"toggle-ribbon-tab-file-properties": "Mostrar pestaña de la cinta de opciones: Propiedades de archivo",
|
||||
"toggle-ribbon-tab-image-properties": "Mostrar pestaña de la cinta de opciones: Propiedades de imagen",
|
||||
"toggle-ribbon-tab-inherited-attributes": "Mostrar pestaña de la cinta de opciones: Atributos heredados",
|
||||
"toggle-ribbon-tab-note-map": "Mostrar pestaña de la cinta de opciones: Mapa de notas",
|
||||
"toggle-ribbon-tab-note-info": "Mostrar pestaña de la cinta de opciones: Información de nota",
|
||||
"toggle-ribbon-tab-note-paths": "Mostrar pestaña de la cinta de opciones: Rutas de nota",
|
||||
"toggle-ribbon-tab-similar-notes": "Mostrar pestaña de la cinta de opciones: Notas similares",
|
||||
"toggle-ribbon-tab-owned-attributes": "Mostrar pestaña de la cinta de opciones: Propiedades asignadas",
|
||||
"toggle-ribbon-tab-promoted-attributes": "Mostrar pestaña de la cinta de opciones: Atributos destacados"
|
||||
},
|
||||
"hidden_subtree_templates": {
|
||||
"board_note_first": "Primera nota",
|
||||
"board_note_second": "Segunda nota",
|
||||
"board_note_third": "Tercera nota",
|
||||
"board_status_progress": "En progreso",
|
||||
"calendar": "Calendario",
|
||||
"description": "Descripción",
|
||||
"list-view": "Vista de lista",
|
||||
"grid-view": "Vista de cuadrícula",
|
||||
"status": "Estado",
|
||||
"table": "Tabla",
|
||||
"text-snippet": "Fragmento de texto",
|
||||
"geo-map": "Mapa Geo",
|
||||
"start-date": "Fecha de inicio",
|
||||
"end-date": "Fecha de finalización",
|
||||
"start-time": "Hora de inicio",
|
||||
"end-time": "Hora de finalización",
|
||||
"geolocation": "Geolocalización",
|
||||
"built-in-templates": "Plantillas predefinidas",
|
||||
"board_status_todo": "Por hacer",
|
||||
"board_status_done": "Hecho",
|
||||
"board": "Tablero"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,278 +1,287 @@
|
||||
{
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "Ouvrir la boîte de dialogue \"Aller à la note\"",
|
||||
"search-in-subtree": "Rechercher des notes dans les sous-arbres de la note active",
|
||||
"expand-subtree": "Développer le sous-arbre de la note actuelle",
|
||||
"collapse-tree": "Réduire toute l'arborescence des notes",
|
||||
"collapse-subtree": "Réduire le sous-arbre de la note actuelle",
|
||||
"sort-child-notes": "Trier les notes enfants",
|
||||
"creating-and-moving-notes": "Créer et déplacer des notes",
|
||||
"create-note-into-inbox": "Créer une note dans l'emplacement par défaut (si défini) ou une note journalière",
|
||||
"delete-note": "Supprimer la note",
|
||||
"move-note-up": "Déplacer la note vers le haut",
|
||||
"move-note-down": "Déplacer la note vers le bas",
|
||||
"move-note-up-in-hierarchy": "Déplacer la note vers le haut dans la hiérarchie",
|
||||
"move-note-down-in-hierarchy": "Déplacer la note vers le bas dans la hiérarchie",
|
||||
"edit-note-title": "Passer de l'arborescence aux détails d'une note et éditer le titre",
|
||||
"edit-branch-prefix": "Afficher la fenêtre Éditer le préfixe de branche",
|
||||
"note-clipboard": "Note presse-papiers",
|
||||
"copy-notes-to-clipboard": "Copier les notes sélectionnées dans le presse-papiers",
|
||||
"paste-notes-from-clipboard": "Coller les notes depuis le presse-papiers dans la note active",
|
||||
"cut-notes-to-clipboard": "Couper les notes sélectionnées dans le presse-papiers",
|
||||
"select-all-notes-in-parent": "Sélectionner toutes les notes du niveau de la note active",
|
||||
"add-note-above-to-the-selection": "Ajouter la note au-dessus de la sélection",
|
||||
"add-note-below-to-selection": "Ajouter la note en dessous de la sélection",
|
||||
"duplicate-subtree": "Dupliquer le sous-arbre",
|
||||
"tabs-and-windows": "Onglets et fenêtres",
|
||||
"open-new-tab": "Ouvrir un nouvel onglet",
|
||||
"close-active-tab": "Fermer l'onglet actif",
|
||||
"reopen-last-tab": "Rouvrir le dernier onglet fermé",
|
||||
"activate-next-tab": "Basculer vers l'onglet à droite de l'onglet actif",
|
||||
"activate-previous-tab": "Basculer vers l'onglet à gauche de l'onglet actif",
|
||||
"open-new-window": "Ouvrir une nouvelle fenêtre vide",
|
||||
"toggle-tray": "Afficher/masquer l'application dans la barre des tâches",
|
||||
"first-tab": "Basculer vers le premier onglet dans la liste",
|
||||
"second-tab": "Basculer vers le deuxième onglet dans la liste",
|
||||
"third-tab": "Basculer vers le troisième onglet dans la liste",
|
||||
"fourth-tab": "Basculer vers le quatrième onglet dans la liste",
|
||||
"fifth-tab": "Basculer vers le cinquième onglet dans la liste",
|
||||
"sixth-tab": "Basculer vers le sixième onglet dans la liste",
|
||||
"seventh-tab": "Basculer vers le septième onglet dans la liste",
|
||||
"eight-tab": "Basculer vers le huitième onglet dans la liste",
|
||||
"ninth-tab": "Basculer vers le neuvième onglet dans la liste",
|
||||
"last-tab": "Basculer vers le dernier onglet dans la liste",
|
||||
"dialogs": "Boîtes de dialogue",
|
||||
"show-note-source": "Affiche la boîte de dialogue Source de la note",
|
||||
"show-options": "Afficher les Options",
|
||||
"show-revisions": "Afficher la boîte de dialogue Versions de la note",
|
||||
"show-recent-changes": "Afficher la boîte de dialogue Modifications récentes",
|
||||
"show-sql-console": "Afficher la boîte de dialogue Console SQL",
|
||||
"show-backend-log": "Afficher la boîte de dialogue Journal du backend",
|
||||
"text-note-operations": "Opérations sur les notes textuelles",
|
||||
"add-link-to-text": "Ouvrir la boîte de dialogue pour ajouter un lien dans le texte",
|
||||
"follow-link-under-cursor": "Suivre le lien sous le curseur",
|
||||
"insert-date-and-time-to-text": "Insérer la date et l'heure dans le texte",
|
||||
"paste-markdown-into-text": "Coller du texte au format Markdown dans la note depuis le presse-papiers",
|
||||
"cut-into-note": "Couper la sélection depuis la note actuelle et créer une sous-note avec le texte sélectionné",
|
||||
"add-include-note-to-text": "Ouvrir la boîte de dialogue pour Inclure une note",
|
||||
"edit-readonly-note": "Éditer une note en lecture seule",
|
||||
"attributes-labels-and-relations": "Attributs (labels et relations)",
|
||||
"add-new-label": "Créer un nouveau label",
|
||||
"create-new-relation": "Créer une nouvelle relation",
|
||||
"ribbon-tabs": "Onglets du ruban",
|
||||
"toggle-basic-properties": "Afficher/masquer les Propriétés de base de la note",
|
||||
"toggle-file-properties": "Afficher/masquer les Propriétés du fichier",
|
||||
"toggle-image-properties": "Afficher/masquer les Propriétés de l'image",
|
||||
"toggle-owned-attributes": "Afficher/masquer les Attributs propres",
|
||||
"toggle-inherited-attributes": "Afficher/masquer les Attributs hérités",
|
||||
"toggle-promoted-attributes": "Afficher/masquer les Attributs promus",
|
||||
"toggle-link-map": "Afficher/masquer la Carte de la note",
|
||||
"toggle-note-info": "Afficher/masquer les Informations de la note",
|
||||
"toggle-note-paths": "Afficher/masquer les Emplacements de la note",
|
||||
"toggle-similar-notes": "Afficher/masquer les Notes similaires",
|
||||
"other": "Autre",
|
||||
"toggle-right-pane": "Afficher/masquer le volet droit, qui inclut la Table des matières et les Accentuations",
|
||||
"print-active-note": "Imprimer la note active",
|
||||
"open-note-externally": "Ouvrir la note comme fichier avec l'application par défaut",
|
||||
"render-active-note": "Rendre (ou re-rendre) la note active",
|
||||
"run-active-note": "Exécuter le code JavaScript (frontend/backend) de la note active",
|
||||
"toggle-note-hoisting": "Activer le focus sur la note active",
|
||||
"unhoist": "Désactiver tout focus",
|
||||
"reload-frontend-app": "Recharger l'application",
|
||||
"open-dev-tools": "Ouvrir les outils de développement",
|
||||
"toggle-left-note-tree-panel": "Basculer le panneau gauche (arborescence des notes)",
|
||||
"toggle-full-screen": "Basculer en plein écran",
|
||||
"zoom-out": "Dézoomer",
|
||||
"zoom-in": "Zoomer",
|
||||
"note-navigation": "Navigation dans les notes",
|
||||
"reset-zoom-level": "Réinitialiser le niveau de zoom",
|
||||
"copy-without-formatting": "Copier le texte sélectionné sans mise en forme",
|
||||
"force-save-revision": "Forcer la création / sauvegarde d'une nouvelle version de la note active",
|
||||
"show-help": "Affiche le guide de l'utilisateur intégré",
|
||||
"toggle-book-properties": "Afficher/masquer les Propriétés du Livre",
|
||||
"toggle-classic-editor-toolbar": "Activer/désactiver l'onglet Mise en forme de l'éditeur avec la barre d'outils fixe",
|
||||
"export-as-pdf": "Exporte la note actuelle en PDF",
|
||||
"show-cheatsheet": "Affiche une fenêtre modale avec des opérations de clavier courantes",
|
||||
"toggle-zen-mode": "Active/désactive le mode zen (interface réduite pour favoriser la concentration)"
|
||||
},
|
||||
"login": {
|
||||
"title": "Connexion",
|
||||
"heading": "Connexion à Trilium",
|
||||
"incorrect-password": "Le mot de passe est incorrect. Veuillez réessayer.",
|
||||
"password": "Mot de passe",
|
||||
"remember-me": "Se souvenir de moi",
|
||||
"button": "Connexion"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Définir un mot de passe",
|
||||
"heading": "Définir un mot de passe",
|
||||
"description": "Avant de pouvoir commencer à utiliser Trilium depuis le web, vous devez d'abord définir un mot de passe. Vous utiliserez ensuite ce mot de passe pour vous connecter.",
|
||||
"password": "Mot de passe",
|
||||
"password-confirmation": "Confirmation du mot de passe",
|
||||
"button": "Définir le mot de passe"
|
||||
},
|
||||
"javascript-required": "Trilium nécessite que JavaScript soit activé.",
|
||||
"setup": {
|
||||
"heading": "Configuration de Trilium Notes",
|
||||
"new-document": "Je suis un nouvel utilisateur et je souhaite créer un nouveau document Trilium pour mes notes",
|
||||
"sync-from-desktop": "J'ai déjà l'application de bureau et je souhaite configurer la synchronisation avec celle-ci",
|
||||
"sync-from-server": "J'ai déjà un serveur et je souhaite configurer la synchronisation avec celui-ci",
|
||||
"next": "Suivant",
|
||||
"init-in-progress": "Initialisation du document en cours",
|
||||
"redirecting": "Vous serez bientôt redirigé vers l'application.",
|
||||
"title": "Configuration"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Synchroniser depuis une application de bureau",
|
||||
"description": "Cette procédure doit être réalisée depuis l'application de bureau installée sur votre ordinateur:",
|
||||
"step1": "Ouvrez l'application Trilium Notes.",
|
||||
"step2": "Dans le menu Trilium, cliquez sur Options.",
|
||||
"step3": "Cliquez sur la catégorie Synchroniser.",
|
||||
"step4": "Remplacez l'adresse de l'instance de serveur par : {{- host}} et cliquez sur Enregistrer.",
|
||||
"step5": "Cliquez sur le bouton 'Tester la synchronisation' pour vérifier que la connexion fonctionne.",
|
||||
"step6": "Une fois que vous avez terminé ces étapes, cliquez sur {{- link}}.",
|
||||
"step6-here": "ici"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Synchroniser depuis le serveur",
|
||||
"instructions": "Veuillez saisir l'adresse du serveur Trilium et les informations d'identification ci-dessous. Cela téléchargera l'intégralité du document Trilium à partir du serveur et configurera la synchronisation avec celui-ci. En fonction de la taille du document et de votre vitesse de connexion, cela peut prendre un plusieurs minutes.",
|
||||
"server-host": "Adresse du serveur Trilium",
|
||||
"server-host-placeholder": "https://<nom d'hôte>:<port>",
|
||||
"proxy-server": "Serveur proxy (facultatif)",
|
||||
"proxy-server-placeholder": "https://<nom d'hôte>:<port>",
|
||||
"note": "Note :",
|
||||
"proxy-instruction": "Si vous laissez le paramètre de proxy vide, le proxy du système sera utilisé (s'applique uniquement à l'application de bureau)",
|
||||
"password": "Mot de passe",
|
||||
"password-placeholder": "Mot de passe",
|
||||
"back": "Retour",
|
||||
"finish-setup": "Terminer"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Synchronisation en cours",
|
||||
"successful": "La synchronisation a été correctement configurée. La synchronisation initiale prendra un certain temps. Une fois terminée, vous serez redirigé vers la page de connexion.",
|
||||
"outstanding-items": "Éléments de synchronisation exceptionnels :",
|
||||
"outstanding-items-default": "N/A"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "Page non trouvée",
|
||||
"heading": "Page non trouvée"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "parent :",
|
||||
"clipped-from": "Cette note a été initialement extraite de {{- url}}",
|
||||
"child-notes": "Notes enfants :",
|
||||
"no-content": "Cette note n'a aucun contenu."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Lundi",
|
||||
"tuesday": "Mardi",
|
||||
"wednesday": "Mercredi",
|
||||
"thursday": "Jeudi",
|
||||
"friday": "Vendredi",
|
||||
"saturday": "Samedi",
|
||||
"sunday": "Dimanche"
|
||||
},
|
||||
"months": {
|
||||
"january": "Janvier",
|
||||
"february": "Février",
|
||||
"march": "Mars",
|
||||
"april": "Avril",
|
||||
"may": "Mai",
|
||||
"june": "Juin",
|
||||
"july": "Juillet",
|
||||
"august": "Août",
|
||||
"september": "Septembre",
|
||||
"october": "Octobre",
|
||||
"november": "Novembre",
|
||||
"december": "Décembre"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "Recherche :"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "L'hôte du serveur de synchronisation n'est pas configuré. Veuillez d'abord configurer la synchronisation.",
|
||||
"successful": "L'établissement de liaison du serveur de synchronisation a été réussi, la synchronisation a été démarrée."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "Notes cachées",
|
||||
"search-history-title": "Historique de recherche",
|
||||
"note-map-title": "Carte de la Note",
|
||||
"sql-console-history-title": "Historique de la console SQL",
|
||||
"shared-notes-title": "Notes partagées",
|
||||
"bulk-action-title": "Action groupée",
|
||||
"backend-log-title": "Journal Backend",
|
||||
"user-hidden-title": "Utilisateur masqué",
|
||||
"launch-bar-templates-title": "Modèles de barre de raccourcis",
|
||||
"base-abstract-launcher-title": "Raccourci Base abstraite",
|
||||
"command-launcher-title": "Raccourci Commande",
|
||||
"note-launcher-title": "Raccourci Note",
|
||||
"script-launcher-title": "Raccourci Script",
|
||||
"built-in-widget-title": "Widget intégré",
|
||||
"spacer-title": "Séparateur",
|
||||
"custom-widget-title": "Widget personnalisé",
|
||||
"launch-bar-title": "Barre de lancement",
|
||||
"available-launchers-title": "Raccourcis disponibles",
|
||||
"go-to-previous-note-title": "Aller à la note précédente",
|
||||
"go-to-next-note-title": "Aller à la note suivante",
|
||||
"new-note-title": "Nouvelle note",
|
||||
"search-notes-title": "Rechercher des notes",
|
||||
"calendar-title": "Calendrier",
|
||||
"recent-changes-title": "Modifications récentes",
|
||||
"bookmarks-title": "Signets",
|
||||
"open-today-journal-note-title": "Ouvrir la note du journal du jour",
|
||||
"quick-search-title": "Recherche rapide",
|
||||
"protected-session-title": "Session protégée",
|
||||
"sync-status-title": "État de la synchronisation",
|
||||
"settings-title": "Réglages",
|
||||
"options-title": "Options",
|
||||
"appearance-title": "Apparence",
|
||||
"shortcuts-title": "Raccourcis",
|
||||
"text-notes": "Notes de texte",
|
||||
"code-notes-title": "Notes de code",
|
||||
"images-title": "Images",
|
||||
"spellcheck-title": "Correcteur orthographique",
|
||||
"password-title": "Mot de passe",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Sauvegarde",
|
||||
"sync-title": "Synchronisation",
|
||||
"other": "Autre",
|
||||
"advanced-title": "Avancé",
|
||||
"visible-launchers-title": "Raccourcis visibles",
|
||||
"user-guide": "Guide de l'utilisateur"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Nouvelle note",
|
||||
"duplicate-note-suffix": "(dup)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "Le fichier journal '{{ fileName }}' n'existe pas (encore).",
|
||||
"reading-log-failed": "La lecture du fichier journal d'administration '{{ fileName }}' a échoué."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Ce type de note ne peut pas être affiché."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "Document PDF (*.pdf)",
|
||||
"unable-to-export-message": "La note actuelle n'a pas pu être exportée en format PDF.",
|
||||
"unable-to-export-title": "Impossible d'exporter au format PDF",
|
||||
"unable-to-save-message": "Le fichier sélectionné n'a pas pu être écrit. Réessayez ou sélectionnez une autre destination."
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "Trilium Notes",
|
||||
"close": "Quitter Trilium",
|
||||
"recents": "Notes récentes",
|
||||
"bookmarks": "Signets",
|
||||
"today": "Ouvrir la note du journal du jour",
|
||||
"new-note": "Nouvelle note",
|
||||
"show-windows": "Afficher les fenêtres"
|
||||
},
|
||||
"migration": {
|
||||
"old_version": "La migration directe à partir de votre version actuelle n'est pas prise en charge. Veuillez d'abord mettre à jour vers la version v0.60.4, puis vers cette nouvelle version.",
|
||||
"error_message": "Erreur lors de la migration vers la version {{version}}: {{stack}}",
|
||||
"wrong_db_version": "La version de la base de données ({{version}}) est plus récente que ce que l'application supporte actuellement ({{targetVersion}}), ce qui signifie qu'elle a été créée par une version plus récente et incompatible de Trilium. Mettez à jour vers la dernière version de Trilium pour résoudre ce problème."
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Erreur"
|
||||
}
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "Ouvrir la boîte de dialogue \"Aller à la note\"",
|
||||
"search-in-subtree": "Rechercher des notes dans les sous-arbres de la note active",
|
||||
"expand-subtree": "Développer le sous-arbre de la note actuelle",
|
||||
"collapse-tree": "Réduire toute l'arborescence des notes",
|
||||
"collapse-subtree": "Réduire le sous-arbre de la note actuelle",
|
||||
"sort-child-notes": "Trier les notes enfants",
|
||||
"creating-and-moving-notes": "Créer et déplacer des notes",
|
||||
"create-note-into-inbox": "Créer une note dans l'emplacement par défaut (si défini) ou une note journalière",
|
||||
"delete-note": "Supprimer la note",
|
||||
"move-note-up": "Déplacer la note vers le haut",
|
||||
"move-note-down": "Déplacer la note vers le bas",
|
||||
"move-note-up-in-hierarchy": "Déplacer la note vers le haut dans la hiérarchie",
|
||||
"move-note-down-in-hierarchy": "Déplacer la note vers le bas dans la hiérarchie",
|
||||
"edit-note-title": "Passer de l'arborescence aux détails d'une note et éditer le titre",
|
||||
"edit-branch-prefix": "Afficher la fenêtre Éditer le préfixe de branche",
|
||||
"note-clipboard": "Note presse-papiers",
|
||||
"copy-notes-to-clipboard": "Copier les notes sélectionnées dans le presse-papiers",
|
||||
"paste-notes-from-clipboard": "Coller les notes depuis le presse-papiers dans la note active",
|
||||
"cut-notes-to-clipboard": "Couper les notes sélectionnées dans le presse-papiers",
|
||||
"select-all-notes-in-parent": "Sélectionner toutes les notes du niveau de la note active",
|
||||
"add-note-above-to-the-selection": "Ajouter la note au-dessus de la sélection",
|
||||
"add-note-below-to-selection": "Ajouter la note en dessous de la sélection",
|
||||
"duplicate-subtree": "Dupliquer le sous-arbre",
|
||||
"tabs-and-windows": "Onglets et fenêtres",
|
||||
"open-new-tab": "Ouvrir un nouvel onglet",
|
||||
"close-active-tab": "Fermer l'onglet actif",
|
||||
"reopen-last-tab": "Rouvrir le dernier onglet fermé",
|
||||
"activate-next-tab": "Basculer vers l'onglet à droite de l'onglet actif",
|
||||
"activate-previous-tab": "Basculer vers l'onglet à gauche de l'onglet actif",
|
||||
"open-new-window": "Ouvrir une nouvelle fenêtre vide",
|
||||
"toggle-tray": "Afficher/masquer l'application dans la barre des tâches",
|
||||
"first-tab": "Basculer vers le premier onglet dans la liste",
|
||||
"second-tab": "Basculer vers le deuxième onglet dans la liste",
|
||||
"third-tab": "Basculer vers le troisième onglet dans la liste",
|
||||
"fourth-tab": "Basculer vers le quatrième onglet dans la liste",
|
||||
"fifth-tab": "Basculer vers le cinquième onglet dans la liste",
|
||||
"sixth-tab": "Basculer vers le sixième onglet dans la liste",
|
||||
"seventh-tab": "Basculer vers le septième onglet dans la liste",
|
||||
"eight-tab": "Basculer vers le huitième onglet dans la liste",
|
||||
"ninth-tab": "Basculer vers le neuvième onglet dans la liste",
|
||||
"last-tab": "Basculer vers le dernier onglet dans la liste",
|
||||
"dialogs": "Boîtes de dialogue",
|
||||
"show-note-source": "Affiche la boîte de dialogue Source de la note",
|
||||
"show-options": "Afficher les Options",
|
||||
"show-revisions": "Afficher la boîte de dialogue Versions de la note",
|
||||
"show-recent-changes": "Afficher la boîte de dialogue Modifications récentes",
|
||||
"show-sql-console": "Afficher la boîte de dialogue Console SQL",
|
||||
"show-backend-log": "Afficher la boîte de dialogue Journal du backend",
|
||||
"text-note-operations": "Opérations sur les notes textuelles",
|
||||
"add-link-to-text": "Ouvrir la boîte de dialogue pour ajouter un lien dans le texte",
|
||||
"follow-link-under-cursor": "Suivre le lien sous le curseur",
|
||||
"insert-date-and-time-to-text": "Insérer la date et l'heure dans le texte",
|
||||
"paste-markdown-into-text": "Coller du texte au format Markdown dans la note depuis le presse-papiers",
|
||||
"cut-into-note": "Couper la sélection depuis la note actuelle et créer une sous-note avec le texte sélectionné",
|
||||
"add-include-note-to-text": "Ouvrir la boîte de dialogue pour Inclure une note",
|
||||
"edit-readonly-note": "Éditer une note en lecture seule",
|
||||
"attributes-labels-and-relations": "Attributs (labels et relations)",
|
||||
"add-new-label": "Créer un nouveau label",
|
||||
"create-new-relation": "Créer une nouvelle relation",
|
||||
"ribbon-tabs": "Onglets du ruban",
|
||||
"toggle-basic-properties": "Afficher/masquer les Propriétés de base de la note",
|
||||
"toggle-file-properties": "Afficher/masquer les Propriétés du fichier",
|
||||
"toggle-image-properties": "Afficher/masquer les Propriétés de l'image",
|
||||
"toggle-owned-attributes": "Afficher/masquer les Attributs propres",
|
||||
"toggle-inherited-attributes": "Afficher/masquer les Attributs hérités",
|
||||
"toggle-promoted-attributes": "Afficher/masquer les Attributs promus",
|
||||
"toggle-link-map": "Afficher/masquer la Carte de la note",
|
||||
"toggle-note-info": "Afficher/masquer les Informations de la note",
|
||||
"toggle-note-paths": "Afficher/masquer les Emplacements de la note",
|
||||
"toggle-similar-notes": "Afficher/masquer les Notes similaires",
|
||||
"other": "Autre",
|
||||
"toggle-right-pane": "Afficher/masquer le volet droit, qui inclut la Table des matières et les Accentuations",
|
||||
"print-active-note": "Imprimer la note active",
|
||||
"open-note-externally": "Ouvrir la note comme fichier avec l'application par défaut",
|
||||
"render-active-note": "Rendre (ou re-rendre) la note active",
|
||||
"run-active-note": "Exécuter le code JavaScript (frontend/backend) de la note active",
|
||||
"toggle-note-hoisting": "Activer le focus sur la note active",
|
||||
"unhoist": "Désactiver tout focus",
|
||||
"reload-frontend-app": "Recharger l'application",
|
||||
"open-dev-tools": "Ouvrir les outils de développement",
|
||||
"toggle-left-note-tree-panel": "Basculer le panneau gauche (arborescence des notes)",
|
||||
"toggle-full-screen": "Basculer en plein écran",
|
||||
"zoom-out": "Dézoomer",
|
||||
"zoom-in": "Zoomer",
|
||||
"note-navigation": "Navigation dans les notes",
|
||||
"reset-zoom-level": "Réinitialiser le niveau de zoom",
|
||||
"copy-without-formatting": "Copier le texte sélectionné sans mise en forme",
|
||||
"force-save-revision": "Forcer la création / sauvegarde d'une nouvelle version de la note active",
|
||||
"show-help": "Affiche le guide de l'utilisateur intégré",
|
||||
"toggle-book-properties": "Afficher/masquer les Propriétés du Livre",
|
||||
"toggle-classic-editor-toolbar": "Activer/désactiver l'onglet Mise en forme de l'éditeur avec la barre d'outils fixe",
|
||||
"export-as-pdf": "Exporte la note actuelle en PDF",
|
||||
"show-cheatsheet": "Affiche une fenêtre modale avec des opérations de clavier courantes",
|
||||
"toggle-zen-mode": "Active/désactive le mode zen (interface réduite pour favoriser la concentration)",
|
||||
"back-in-note-history": "Naviguer à la note précédente dans l'historique",
|
||||
"forward-in-note-history": "Naviguer a la note suivante dans l'historique",
|
||||
"open-command-palette": "Ouvrir la palette de commandes",
|
||||
"clone-notes-to": "Cloner les nœuds sélectionnés",
|
||||
"move-notes-to": "Déplacer les nœuds sélectionnés"
|
||||
},
|
||||
"login": {
|
||||
"title": "Connexion",
|
||||
"heading": "Connexion à Trilium",
|
||||
"incorrect-password": "Le mot de passe est incorrect. Veuillez réessayer.",
|
||||
"password": "Mot de passe",
|
||||
"remember-me": "Se souvenir de moi",
|
||||
"button": "Connexion"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Définir un mot de passe",
|
||||
"heading": "Définir un mot de passe",
|
||||
"description": "Avant de pouvoir commencer à utiliser Trilium depuis le web, vous devez d'abord définir un mot de passe. Vous utiliserez ensuite ce mot de passe pour vous connecter.",
|
||||
"password": "Mot de passe",
|
||||
"password-confirmation": "Confirmation du mot de passe",
|
||||
"button": "Définir le mot de passe"
|
||||
},
|
||||
"javascript-required": "Trilium nécessite que JavaScript soit activé.",
|
||||
"setup": {
|
||||
"heading": "Configuration de Trilium Notes",
|
||||
"new-document": "Je suis un nouvel utilisateur et je souhaite créer un nouveau document Trilium pour mes notes",
|
||||
"sync-from-desktop": "J'ai déjà l'application de bureau et je souhaite configurer la synchronisation avec celle-ci",
|
||||
"sync-from-server": "J'ai déjà un serveur et je souhaite configurer la synchronisation avec celui-ci",
|
||||
"next": "Suivant",
|
||||
"init-in-progress": "Initialisation du document en cours",
|
||||
"redirecting": "Vous serez bientôt redirigé vers l'application.",
|
||||
"title": "Configuration"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Synchroniser depuis une application de bureau",
|
||||
"description": "Cette procédure doit être réalisée depuis l'application de bureau installée sur votre ordinateur:",
|
||||
"step1": "Ouvrez l'application Trilium Notes.",
|
||||
"step2": "Dans le menu Trilium, cliquez sur Options.",
|
||||
"step3": "Cliquez sur la catégorie Synchroniser.",
|
||||
"step4": "Remplacez l'adresse de l'instance de serveur par : {{- host}} et cliquez sur Enregistrer.",
|
||||
"step5": "Cliquez sur le bouton 'Tester la synchronisation' pour vérifier que la connexion fonctionne.",
|
||||
"step6": "Une fois que vous avez terminé ces étapes, cliquez sur {{- link}}.",
|
||||
"step6-here": "ici"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Synchroniser depuis le serveur",
|
||||
"instructions": "Veuillez saisir l'adresse du serveur Trilium et les informations d'identification ci-dessous. Cela téléchargera l'intégralité du document Trilium à partir du serveur et configurera la synchronisation avec celui-ci. En fonction de la taille du document et de votre vitesse de connexion, cela peut prendre un plusieurs minutes.",
|
||||
"server-host": "Adresse du serveur Trilium",
|
||||
"server-host-placeholder": "https://<nom d'hôte>:<port>",
|
||||
"proxy-server": "Serveur proxy (facultatif)",
|
||||
"proxy-server-placeholder": "https://<nom d'hôte>:<port>",
|
||||
"note": "Note :",
|
||||
"proxy-instruction": "Si vous laissez le paramètre de proxy vide, le proxy du système sera utilisé (s'applique uniquement à l'application de bureau)",
|
||||
"password": "Mot de passe",
|
||||
"password-placeholder": "Mot de passe",
|
||||
"back": "Retour",
|
||||
"finish-setup": "Terminer"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Synchronisation en cours",
|
||||
"successful": "La synchronisation a été correctement configurée. La synchronisation initiale prendra un certain temps. Une fois terminée, vous serez redirigé vers la page de connexion.",
|
||||
"outstanding-items": "Éléments de synchronisation exceptionnels :",
|
||||
"outstanding-items-default": "N/A"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "Page non trouvée",
|
||||
"heading": "Page non trouvée"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "parent :",
|
||||
"clipped-from": "Cette note a été initialement extraite de {{- url}}",
|
||||
"child-notes": "Notes enfants :",
|
||||
"no-content": "Cette note n'a aucun contenu."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Lundi",
|
||||
"tuesday": "Mardi",
|
||||
"wednesday": "Mercredi",
|
||||
"thursday": "Jeudi",
|
||||
"friday": "Vendredi",
|
||||
"saturday": "Samedi",
|
||||
"sunday": "Dimanche"
|
||||
},
|
||||
"months": {
|
||||
"january": "Janvier",
|
||||
"february": "Février",
|
||||
"march": "Mars",
|
||||
"april": "Avril",
|
||||
"may": "Mai",
|
||||
"june": "Juin",
|
||||
"july": "Juillet",
|
||||
"august": "Août",
|
||||
"september": "Septembre",
|
||||
"october": "Octobre",
|
||||
"november": "Novembre",
|
||||
"december": "Décembre"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "Recherche :"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "L'hôte du serveur de synchronisation n'est pas configuré. Veuillez d'abord configurer la synchronisation.",
|
||||
"successful": "L'établissement de liaison du serveur de synchronisation a été réussi, la synchronisation a été démarrée."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"root-title": "Notes cachées",
|
||||
"search-history-title": "Historique de recherche",
|
||||
"note-map-title": "Carte de la Note",
|
||||
"sql-console-history-title": "Historique de la console SQL",
|
||||
"shared-notes-title": "Notes partagées",
|
||||
"bulk-action-title": "Action groupée",
|
||||
"backend-log-title": "Journal Backend",
|
||||
"user-hidden-title": "Utilisateur masqué",
|
||||
"launch-bar-templates-title": "Modèles de barre de raccourcis",
|
||||
"base-abstract-launcher-title": "Raccourci Base abstraite",
|
||||
"command-launcher-title": "Raccourci Commande",
|
||||
"note-launcher-title": "Raccourci Note",
|
||||
"script-launcher-title": "Raccourci Script",
|
||||
"built-in-widget-title": "Widget intégré",
|
||||
"spacer-title": "Séparateur",
|
||||
"custom-widget-title": "Widget personnalisé",
|
||||
"launch-bar-title": "Barre de lancement",
|
||||
"available-launchers-title": "Raccourcis disponibles",
|
||||
"go-to-previous-note-title": "Aller à la note précédente",
|
||||
"go-to-next-note-title": "Aller à la note suivante",
|
||||
"new-note-title": "Nouvelle note",
|
||||
"search-notes-title": "Rechercher des notes",
|
||||
"calendar-title": "Calendrier",
|
||||
"recent-changes-title": "Modifications récentes",
|
||||
"bookmarks-title": "Signets",
|
||||
"open-today-journal-note-title": "Ouvrir la note du journal du jour",
|
||||
"quick-search-title": "Recherche rapide",
|
||||
"protected-session-title": "Session protégée",
|
||||
"sync-status-title": "État de la synchronisation",
|
||||
"settings-title": "Réglages",
|
||||
"options-title": "Options",
|
||||
"appearance-title": "Apparence",
|
||||
"shortcuts-title": "Raccourcis",
|
||||
"text-notes": "Notes de texte",
|
||||
"code-notes-title": "Notes de code",
|
||||
"images-title": "Images",
|
||||
"spellcheck-title": "Correcteur orthographique",
|
||||
"password-title": "Mot de passe",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Sauvegarde",
|
||||
"sync-title": "Synchronisation",
|
||||
"other": "Autre",
|
||||
"advanced-title": "Avancé",
|
||||
"visible-launchers-title": "Raccourcis visibles",
|
||||
"user-guide": "Guide de l'utilisateur"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Nouvelle note",
|
||||
"duplicate-note-suffix": "(dup)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "Le fichier journal '{{ fileName }}' n'existe pas (encore).",
|
||||
"reading-log-failed": "La lecture du fichier journal d'administration '{{ fileName }}' a échoué."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Ce type de note ne peut pas être affiché."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "Document PDF (*.pdf)",
|
||||
"unable-to-export-message": "La note actuelle n'a pas pu être exportée en format PDF.",
|
||||
"unable-to-export-title": "Impossible d'exporter au format PDF",
|
||||
"unable-to-save-message": "Le fichier sélectionné n'a pas pu être écrit. Réessayez ou sélectionnez une autre destination."
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "Trilium Notes",
|
||||
"close": "Quitter Trilium",
|
||||
"recents": "Notes récentes",
|
||||
"bookmarks": "Signets",
|
||||
"today": "Ouvrir la note du journal du jour",
|
||||
"new-note": "Nouvelle note",
|
||||
"show-windows": "Afficher les fenêtres"
|
||||
},
|
||||
"migration": {
|
||||
"old_version": "La migration directe à partir de votre version actuelle n'est pas prise en charge. Veuillez d'abord mettre à jour vers la version v0.60.4, puis vers cette nouvelle version.",
|
||||
"error_message": "Erreur lors de la migration vers la version {{version}}: {{stack}}",
|
||||
"wrong_db_version": "La version de la base de données ({{version}}) est plus récente que ce que l'application supporte actuellement ({{targetVersion}}), ce qui signifie qu'elle a été créée par une version plus récente et incompatible de Trilium. Mettez à jour vers la dernière version de Trilium pour résoudre ce problème."
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Erreur"
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"command-palette": "Palette de commandes",
|
||||
"quick-search": "Recherche rapide"
|
||||
}
|
||||
}
|
||||
|
||||
157
apps/server/src/assets/translations/it/server.json
Normal file
157
apps/server/src/assets/translations/it/server.json
Normal file
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"keyboard_action_names": {
|
||||
"zoom-in": "Ingrandisci",
|
||||
"reset-zoom-level": "Ripristina il livello di ingrandimento",
|
||||
"zoom-out": "Riduci",
|
||||
"toggle-full-screen": "Attiva/disattiva lo Schermo Intero",
|
||||
"toggle-left-pane": "Attiva/disattiva il Pannello Sinistro",
|
||||
"toggle-zen-mode": "Attiva/disattiva la modalità zen",
|
||||
"toggle-right-pane": "Attiva/disattiva il Pannello Destro",
|
||||
"toggle-system-tray-icon": "Attiva/disattiva l'Icona nel Vassoio di Sistema",
|
||||
"toggle-note-hoisting": "Attiva/disattiva l'Ancoraggio della Nota",
|
||||
"unhoist-note": "Disancora la Nota",
|
||||
"reload-frontend-app": "Ricarica l'Applicazione Frontend",
|
||||
"open-developer-tools": "Apri gli Strumenti da Sviluppatore",
|
||||
"find-in-text": "Cerca nel Testo",
|
||||
"print-active-note": "Stampa la Nota Attiva",
|
||||
"export-active-note-as-pdf": "Esporta la nota attiva come PDF",
|
||||
"open-note-externally": "Apri Esternamente la Nota",
|
||||
"run-active-note": "Esegui la Nota Attiva",
|
||||
"render-active-note": "Presenta la Nota Attiva"
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"options-title": "Opzioni",
|
||||
"appearance-title": "Aspetto",
|
||||
"shortcuts-title": "Scorciatoie",
|
||||
"text-notes": "Note di Testo",
|
||||
"code-notes-title": "Note di Codice",
|
||||
"images-title": "Immagini",
|
||||
"spellcheck-title": "Controllo Ortografico",
|
||||
"password-title": "Password",
|
||||
"multi-factor-authentication-title": "MFA",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Archiviazione",
|
||||
"sync-title": "Sincronizza",
|
||||
"ai-llm-title": "IA/LLM",
|
||||
"other": "Altro",
|
||||
"advanced-title": "Avanzato",
|
||||
"user-guide": "Guida Utente",
|
||||
"visible-launchers-title": "Lanciatori Visibili",
|
||||
"localization": "Lingua e Regione",
|
||||
"inbox-title": "Posta in arrivo"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Nuova nota",
|
||||
"duplicate-note-suffix": "(dup)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "Il file di log del backend '{{ fileName }}' non esiste (ancora).",
|
||||
"reading-log-failed": "La lettura del file di log del backend '{{ fileName }}' è fallita."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Questo tipo di nota non può essere visualizzato."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "Documento PDF (*.pdf)",
|
||||
"unable-to-export-message": "La nota corrente non può essere esportata come PDF.",
|
||||
"unable-to-export-title": "Impossibile esportare come PDF",
|
||||
"unable-to-save-message": "Il file selezionato non può essere salvato. Prova di nuovo o seleziona un'altra destinazione."
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "Trilium Notes",
|
||||
"close": "Esci da Trilium",
|
||||
"recents": "Note recenti",
|
||||
"bookmarks": "Segnalibri",
|
||||
"today": "Apri la nota di oggi",
|
||||
"new-note": "Nuova nota",
|
||||
"show-windows": "Mostra le finestre",
|
||||
"open_new_window": "Aprire una nuova finestra"
|
||||
},
|
||||
"migration": {
|
||||
"old_version": "La migrazione diretta dalla tua versione attuale non è supportata. Si prega di aggiornare prima all'ultima versione v0.60.4 e solo dopo a questa versione.",
|
||||
"error_message": "Errore durante la migrazione alla versione {{version}}: {{stack}}",
|
||||
"wrong_db_version": "La versione del database ({{version}}) è più recente di quanto l'applicazione si aspetti ({{targetVersion}}), il che significa che è stato creato da una versione più nuova e incompatibile di Trilium. Aggiorna Trilium all'ultima versione per risolvere questo problema."
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Errore"
|
||||
},
|
||||
"share_theme": {
|
||||
"site-theme": "Tema del Sito",
|
||||
"search_placeholder": "Cerca..."
|
||||
},
|
||||
"keyboard_actions": {
|
||||
"back-in-note-history": "Navigare alla nota precedente della cronologia",
|
||||
"forward-in-note-history": "Navigare alla prossima nota della cronologia",
|
||||
"open-jump-to-note-dialog": "Apri la finestra di dialogo \"Salta alla nota\"",
|
||||
"open-command-palette": "Apri la palette dei comandi",
|
||||
"scroll-to-active-note": "Scorri l'albero fino alla nota attiva",
|
||||
"quick-search": "Attiva la barra di ricerca rapida",
|
||||
"search-in-subtree": "Cerca le note nel sotto albero della nota attiva",
|
||||
"expand-subtree": "Espande il sotto albero della nota corrente",
|
||||
"collapse-tree": "Contrae l'albero completo delle note",
|
||||
"collapse-subtree": "Contrae il sotto albero della nota corrente",
|
||||
"sort-child-notes": "Ordina le note figlio",
|
||||
"creating-and-moving-notes": "Crea e sposta le note",
|
||||
"create-note-after": "Crea una nota dopo quella attiva",
|
||||
"create-note-into": "Crea una nota come figlia di quella attiva",
|
||||
"create-note-into-inbox": "Creare una nota nella casella di posta (se definita) o nella nota del giorno",
|
||||
"delete-note": "Elimina una nota",
|
||||
"move-note-up": "Sposta su una nota",
|
||||
"move-note-down": "Sposta giù una nota",
|
||||
"move-note-up-in-hierarchy": "Sposta su la nota nella gerarchia",
|
||||
"move-note-down-in-hierarchy": "Sposta giù una nota nella gerarchia",
|
||||
"edit-note-title": "Salta dall'albero al dettaglio della nota e modifica il titolo",
|
||||
"edit-branch-prefix": "Mostra la finestra di dialogo \"Modifica il prefisso del ramo\"",
|
||||
"clone-notes-to": "Clona le note selezionate",
|
||||
"move-notes-to": "Sposta le note selezionate",
|
||||
"note-clipboard": "Appunti delle Note",
|
||||
"copy-notes-to-clipboard": "Copia le note selezionate negli appunti",
|
||||
"paste-notes-from-clipboard": "Incolla le note dagli appunti nella nota attiva",
|
||||
"cut-notes-to-clipboard": "Tagliare le note selezionate negli appunti",
|
||||
"select-all-notes-in-parent": "Seleziona tutte le note dal livello di nota corrente",
|
||||
"add-note-above-to-the-selection": "Aggiungi una nota sopra alla selezione",
|
||||
"add-note-below-to-selection": "Aggiungi una nota sotto alla selezione",
|
||||
"duplicate-subtree": "Duplica il sotto albero",
|
||||
"tabs-and-windows": "Schede e Finestre",
|
||||
"open-new-tab": "Apri una nuova scheda",
|
||||
"close-active-tab": "Chiudi la scheda attiva",
|
||||
"reopen-last-tab": "Riapri l'ultima scheda chiusa",
|
||||
"activate-next-tab": "Attiva la scheda sulla destra",
|
||||
"activate-previous-tab": "Attiva la scheda a sinistra",
|
||||
"open-new-window": "Apri una nuova finestra vuota",
|
||||
"toggle-tray": "Mostra/nascondi l'applicazione dal vassoio di sistema",
|
||||
"first-tab": "Attiva la prima scheda nell'elenco",
|
||||
"second-tab": "Attiva la seconda scheda nell'elenco",
|
||||
"third-tab": "Attiva la terza scheda nell'elenco",
|
||||
"fourth-tab": "Attiva la quarta scheda nella lista",
|
||||
"fifth-tab": "Attiva la quinta scheda nell'elenco",
|
||||
"sixth-tab": "Attiva la sesta scheda nell'elenco",
|
||||
"seventh-tab": "Attiva la settima scheda nella lista",
|
||||
"eight-tab": "Attiva l'ottava scheda nell'elenco",
|
||||
"ninth-tab": "Attiva la nona scheda nella lista",
|
||||
"last-tab": "Attiva l'ultima scheda nell'elenco",
|
||||
"dialogs": "Finestre di dialogo",
|
||||
"show-note-source": "Mostra la finestra di dialogo \"Sorgente della Nota\"",
|
||||
"show-options": "Apri la pagina \"Opzioni\"",
|
||||
"show-revisions": "Mostra la finestra di dialogo \"Revisione della Nota\"",
|
||||
"show-recent-changes": "Mostra la finestra di dialogo \"Modifiche Recenti\"",
|
||||
"show-sql-console": "Apri la pagina \"Console SQL\"",
|
||||
"show-backend-log": "Apri la pagina \"Log del Backend\"",
|
||||
"show-help": "Apri la Guida Utente integrata",
|
||||
"show-cheatsheet": "Mostra una finestra modale con le operazioni comuni da tastiera",
|
||||
"text-note-operations": "Operazioni sulle note di testo",
|
||||
"add-link-to-text": "Apri la finestra di dialogo per aggiungere il collegamento al testo",
|
||||
"follow-link-under-cursor": "Segui il collegamento all'interno del quale è il cursore",
|
||||
"insert-date-and-time-to-text": "Inserisci la data e l'ora corrente nel testo",
|
||||
"paste-markdown-into-text": "Incolla il Markdown dagli appunti nella nota di testo",
|
||||
"cut-into-note": "Taglia la selezione dalla nota corrente e crea una sotto nota col testo selezionato",
|
||||
"add-include-note-to-text": "Apre la finestra di dialogo per includere una nota",
|
||||
"edit-readonly-note": "Modifica una nota di sola lettura",
|
||||
"attributes-labels-and-relations": "Attributi (etichette e relazioni)",
|
||||
"add-new-label": "Crea una nuova etichetta",
|
||||
"create-new-relation": "Crea una nuova relazione",
|
||||
"ribbon-tabs": "Nastro delle schede",
|
||||
"toggle-basic-properties": "Mostra/nascondi le Proprietà di Base"
|
||||
}
|
||||
}
|
||||
1
apps/server/src/assets/translations/ja/server.json
Normal file
1
apps/server/src/assets/translations/ja/server.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -1,193 +1,428 @@
|
||||
{
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "Abir \"Pular para nota\" dialog",
|
||||
"search-in-subtree": "Procurar por notas na subárvore ativa",
|
||||
"expand-subtree": "Expandir subárvore da nota atual",
|
||||
"collapse-tree": "Colapsar a árvore completa de notas",
|
||||
"collapse-subtree": "Colapsar subárvore da nota atual",
|
||||
"sort-child-notes": "Ordenar notas filhas",
|
||||
"creating-and-moving-notes": "Criando e movendo notas",
|
||||
"create-note-into-inbox": "Crie uma nota na caixa de entrada (se definida) ou na nota do dia.",
|
||||
"delete-note": "Deletar nota",
|
||||
"move-note-up": "Mover nota para cima",
|
||||
"move-note-down": "Mover nota para baixo",
|
||||
"move-note-up-in-hierarchy": "Mover nota para cima em hierarquia",
|
||||
"move-note-down-in-hierarchy": "Mover nota para baixo em hierarquia",
|
||||
"edit-note-title": "Pule da árvore para os detalhes da nota e edite o título",
|
||||
"edit-branch-prefix": "Exibir o diálogo de edição do prefixo da branch",
|
||||
"note-clipboard": "Área de transferência de notas",
|
||||
"copy-notes-to-clipboard": "Copiar notas selecionadas para Área de transferência",
|
||||
"paste-notes-from-clipboard": "Colar notas da área de transferência na nota ativa",
|
||||
"cut-notes-to-clipboard": "Recortar as notas selecionadas para a área de transferência",
|
||||
"select-all-notes-in-parent": "Selecionar todas as notas do nível atual da nota",
|
||||
"add-note-above-to-the-selection": "Adicionar nota acima à seleção",
|
||||
"add-note-below-to-selection": "Adicionar nota abaixo à seleção",
|
||||
"duplicate-subtree": "Duplicar subárvores",
|
||||
"tabs-and-windows": "Abas & Janelas",
|
||||
"open-new-tab": "Abre nova aba",
|
||||
"close-active-tab": "Fecha aba ativa",
|
||||
"reopen-last-tab": "Reabre a última aba fechada",
|
||||
"activate-next-tab": "Ativa aba à direita",
|
||||
"activate-previous-tab": "Ativa aba à esquerda",
|
||||
"open-new-window": "Abre nova janela vazia",
|
||||
"toggle-tray": "Mostrar/ocultar o aplicativo da bandeja do sistema",
|
||||
"first-tab": "Ativa a primeira aba na lista",
|
||||
"second-tab": "Ativa a segunda aba na lista",
|
||||
"third-tab": "Ativa a terceira aba na lista",
|
||||
"fourth-tab": "Ativa a quarta aba na lista",
|
||||
"fifth-tab": "Ativa a quinta aba na lista",
|
||||
"sixth-tab": "Ativa a sexta aba na lista",
|
||||
"seventh-tab": "Ativa a sétima aba na lista",
|
||||
"eight-tab": "Ativa a oitava aba na lista",
|
||||
"ninth-tab": "Ativa a nona aba na lista",
|
||||
"last-tab": "Ativa a última aba na lista",
|
||||
"dialogs": "Dialogs",
|
||||
"show-note-source": "Exibe o log de origem da nota",
|
||||
"show-options": "Mostrar log de configurações",
|
||||
"show-revisions": "Exibe log de revisões de nota",
|
||||
"show-recent-changes": "Exibe o log de alterações recentes",
|
||||
"show-sql-console": "Exibe o log do console SQL",
|
||||
"show-backend-log": "Exibe o log do backend",
|
||||
"text-note-operations": "Operações de nota de texto",
|
||||
"add-link-to-text": "Abrir log e adcionar link ao texto",
|
||||
"follow-link-under-cursor": "Seguir o link sob o cursor",
|
||||
"insert-date-and-time-to-text": "Inserir data e hora atuais no texto",
|
||||
"paste-markdown-into-text": "Colar Markdown da área de transferência em nota de texto",
|
||||
"cut-into-note": "Corta a seleção da nota atual e cria uma subnota com o texto selecionado",
|
||||
"add-include-note-to-text": "Abre o log para incluir uma nota",
|
||||
"edit-readonly-note": "Editar uma nota somente leitura",
|
||||
"attributes-labels-and-relations": "Atributos (rótulos e relações)",
|
||||
"add-new-label": "Criar novo rótulo",
|
||||
"create-new-relation": "Criar nova relação",
|
||||
"ribbon-tabs": "Abas da faixa",
|
||||
"toggle-basic-properties": "Alterar Propriedades Básicas",
|
||||
"toggle-file-properties": "Alterar Propriedades do Arquivo",
|
||||
"toggle-image-properties": "Alterar Propriedades da Imagem",
|
||||
"toggle-owned-attributes": "Alterar Atributos Próprios",
|
||||
"toggle-inherited-attributes": "Alterar Atributos Herdados",
|
||||
"toggle-promoted-attributes": "Alternar Atributos Promovidos",
|
||||
"toggle-link-map": "Alternar Mapa de Links",
|
||||
"toggle-note-info": "Alternar Informações da Nota",
|
||||
"toggle-note-paths": "Alternar Caminhos da Nota",
|
||||
"toggle-similar-notes": "Alternar Notas Similares",
|
||||
"other": "Outros",
|
||||
"toggle-right-pane": "Alternar a exibição do painel direito, que inclui Sumário e Destaques",
|
||||
"print-active-note": "Imprimir nota ativa",
|
||||
"open-note-externally": "Abrir nota como arquivo no aplicativo padrão",
|
||||
"render-active-note": "Renderizar (re-renderizar) nota ativa",
|
||||
"run-active-note": "Executar código JavaScript (frontend/backend) da nota",
|
||||
"toggle-note-hoisting": "Alternar a elevação da nota ativa",
|
||||
"unhoist": "Desfazer elevação de tudo",
|
||||
"reload-frontend-app": "Recarregar Interface",
|
||||
"open-dev-tools": "Abrir ferramentas de desenvolvedor",
|
||||
"toggle-left-note-tree-panel": "Alternar painel esquerdo (árvore de notas)",
|
||||
"toggle-full-screen": "Alternar para tela cheia",
|
||||
"zoom-out": "Diminuir zoom",
|
||||
"zoom-in": "Aumentar zoom",
|
||||
"note-navigation": "Navegação de notas",
|
||||
"reset-zoom-level": "Redefinir nível de zoom",
|
||||
"copy-without-formatting": "Copiar texto selecionado sem formatação",
|
||||
"force-save-revision": "Forçar a criação/salvamento de uma nova revisão da nota ativa",
|
||||
"show-help": "Exibir Ajuda integrada / colinha",
|
||||
"toggle-book-properties": "Alternar propriedades do book",
|
||||
"toggle-classic-editor-toolbar": "Alternar a aba de Formatação no editor com barra de ferramentas fixa"
|
||||
},
|
||||
"login": {
|
||||
"title": "Login",
|
||||
"heading": "Trilium login",
|
||||
"incorrect-password": "Senha incorreta. Tente novamente.",
|
||||
"password": "Senha",
|
||||
"remember-me": "Lembrar",
|
||||
"button": "Login"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Definir senha",
|
||||
"heading": "Definir senha",
|
||||
"description": "Antes de começar a usar o Trilium web, você precisa definir uma senha. Você usará essa senha para fazer login.",
|
||||
"password": "Senha",
|
||||
"password-confirmation": "Confirmar Senha",
|
||||
"button": "Definir senha"
|
||||
},
|
||||
"javascript-required": "Trilium precisa que JavaScript esteja habilitado.",
|
||||
"setup": {
|
||||
"heading": "Trilium Notes setup",
|
||||
"new-document": "Sou um novo usuário e quero criar um novo documento Trilium para minhas notas",
|
||||
"sync-from-desktop": "Já tenho uma instância no desktop e quero configurar a sincronização com ela",
|
||||
"sync-from-server": "Já tenho uma instância no servidor e quero configurar a sincronização com ela",
|
||||
"next": "Avançar",
|
||||
"init-in-progress": "Inicialização do documento em andamento",
|
||||
"redirecting": "Você será redirecionado para o aplicativo em breve.",
|
||||
"title": "Setup"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Sincronizar com Desktop",
|
||||
"description": "Esta configuração deve ser iniciada a partir da instância do desktop:",
|
||||
"step1": "Abra sua instância do Trilium Notes no desktop.",
|
||||
"step2": "No menu do Trilium, clique em Opções.",
|
||||
"step3": "Clique na categoria Sincronização.",
|
||||
"step4": "Altere o endereço da instância do servidor para: {{- host}} e clique em Salvar.",
|
||||
"step5": "Clique no botão \"Testar sincronização\" para verificar se a conexão foi bem-sucedida.",
|
||||
"step6": "Depois de concluir essas etapas, clique em {{- link}}.",
|
||||
"step6-here": "Aqui"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Sincronizar do Servidor",
|
||||
"instructions": "Por favor, insira abaixo o endereço e as credenciais do servidor Trilium. Isso fará o download de todo o documento Trilium do servidor e configurará a sincronização com ele. Dependendo do tamanho do documento e da velocidade da conexão, isso pode levar algum tempo.",
|
||||
"server-host": "Endereço do servidor Trilium",
|
||||
"server-host-placeholder": "https://<hostname>:<port>",
|
||||
"proxy-server": "Servidor proxy (opcional)",
|
||||
"proxy-server-placeholder": "https://<hostname>:<port>",
|
||||
"note": "Nota:",
|
||||
"proxy-instruction": "Se você deixar o campo de proxy em branco, o proxy do sistema será usado (aplicável apenas ao aplicativo desktop)",
|
||||
"password": "Senha",
|
||||
"password-placeholder": "Senha",
|
||||
"back": "Voltar",
|
||||
"finish-setup": "Terminar configuração"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Sincronização em andamento",
|
||||
"successful": "A sincronização foi configurada corretamente. Levará algum tempo para que a sincronização inicial seja concluída. Quando terminar, você será redirecionado para a página de login.",
|
||||
"outstanding-items": "Itens de sincronização pendentes:",
|
||||
"outstanding-items-default": "N/A"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "Não encontrado",
|
||||
"heading": "Não encontrado"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "pai:",
|
||||
"clipped-from": "Esta nota foi originalmente extraída de {{- url}}",
|
||||
"child-notes": "Notas filhas:",
|
||||
"no-content": "Esta nota não possui conteúdo."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Segunda-feira",
|
||||
"tuesday": "Terça-feira",
|
||||
"wednesday": "Quarta-feira",
|
||||
"thursday": "Quinta-feira",
|
||||
"friday": "Sexta-feira",
|
||||
"saturday": "Sábado",
|
||||
"sunday": "Domingo"
|
||||
},
|
||||
"months": {
|
||||
"january": "Janeiro",
|
||||
"february": "Fevereiro",
|
||||
"march": "Março",
|
||||
"april": "Abril",
|
||||
"may": "Maio",
|
||||
"june": "Junho",
|
||||
"july": "Julho",
|
||||
"august": "Agosto",
|
||||
"september": "Setembro",
|
||||
"october": "Outubro",
|
||||
"november": "Novembro",
|
||||
"december": "Dezembro"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "Pesquisar:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "O host do servidor de sincronização não está configurado. Por favor, configure a sincronização primeiro.",
|
||||
"successful": "A comunicação com o servidor de sincronização foi bem-sucedida, a sincronização foi iniciada."
|
||||
}
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "Abrir diálogo \"Ir para nota\"",
|
||||
"search-in-subtree": "Buscar notas na subárvore da nota atual",
|
||||
"expand-subtree": "Expandir subárvore da nota atual",
|
||||
"collapse-tree": "Colapsar a árvore completa de notas",
|
||||
"collapse-subtree": "Colapsar subárvore da nota atual",
|
||||
"sort-child-notes": "Ordenar notas filhas",
|
||||
"creating-and-moving-notes": "Criando e movendo notas",
|
||||
"create-note-into-inbox": "Crie uma nota na caixa de entrada (se definida) ou na nota do dia",
|
||||
"delete-note": "Deletar nota",
|
||||
"move-note-up": "Mover nota para cima",
|
||||
"move-note-down": "Mover nota para baixo",
|
||||
"move-note-up-in-hierarchy": "Mover nota para cima em hierarquia",
|
||||
"move-note-down-in-hierarchy": "Mover nota para baixo em hierarquia",
|
||||
"edit-note-title": "Pule da árvore para os detalhes da nota e edite o título",
|
||||
"edit-branch-prefix": "Exibir o diálogo \"Editar prefixo da ramificação\"",
|
||||
"note-clipboard": "Área de transferência de notas",
|
||||
"copy-notes-to-clipboard": "Copiar notas selecionadas para Área de transferência",
|
||||
"paste-notes-from-clipboard": "Colar notas da área de transferência na nota atual",
|
||||
"cut-notes-to-clipboard": "Recortar as notas selecionadas para a área de transferência",
|
||||
"select-all-notes-in-parent": "Selecionar todas as notas do nível atual da nota",
|
||||
"add-note-above-to-the-selection": "Adicionar nota acima à seleção",
|
||||
"add-note-below-to-selection": "Adicionar nota abaixo à seleção",
|
||||
"duplicate-subtree": "Duplicar subárvores",
|
||||
"tabs-and-windows": "Abas & Janelas",
|
||||
"open-new-tab": "Abre nova aba",
|
||||
"close-active-tab": "Fecha aba ativa",
|
||||
"reopen-last-tab": "Reabre a última aba fechada",
|
||||
"activate-next-tab": "Ativa aba à direita",
|
||||
"activate-previous-tab": "Ativa aba à esquerda",
|
||||
"open-new-window": "Abre nova janela vazia",
|
||||
"toggle-tray": "Mostrar/ocultar o aplicativo da bandeja do sistema",
|
||||
"first-tab": "Ativa a primeira aba na lista",
|
||||
"second-tab": "Ativa a segunda aba na lista",
|
||||
"third-tab": "Ativa a terceira aba na lista",
|
||||
"fourth-tab": "Ativa a quarta aba na lista",
|
||||
"fifth-tab": "Ativa a quinta aba na lista",
|
||||
"sixth-tab": "Ativa a sexta aba na lista",
|
||||
"seventh-tab": "Ativa a sétima aba na lista",
|
||||
"eight-tab": "Ativa a oitava aba na lista",
|
||||
"ninth-tab": "Ativa a nona aba na lista",
|
||||
"last-tab": "Ativa a última aba na lista",
|
||||
"dialogs": "Diálogos",
|
||||
"show-note-source": "Exibe o log de origem da nota",
|
||||
"show-options": "Mostrar log de configurações",
|
||||
"show-revisions": "Exibe log de revisões de nota",
|
||||
"show-recent-changes": "Exibe o log de alterações recentes",
|
||||
"show-sql-console": "Exibe o log do console SQL",
|
||||
"show-backend-log": "Exibe o log do backend",
|
||||
"text-note-operations": "Operações de nota de texto",
|
||||
"add-link-to-text": "Abrir log e adcionar link ao texto",
|
||||
"follow-link-under-cursor": "Seguir o link sob o cursor",
|
||||
"insert-date-and-time-to-text": "Inserir data e hora atuais no texto",
|
||||
"paste-markdown-into-text": "Colar Markdown da área de transferência em nota de texto",
|
||||
"cut-into-note": "Corta a seleção da nota atual e cria uma subnota com o texto selecionado",
|
||||
"add-include-note-to-text": "Abre o log para incluir uma nota",
|
||||
"edit-readonly-note": "Editar uma nota somente leitura",
|
||||
"attributes-labels-and-relations": "Atributos (rótulos e relações)",
|
||||
"add-new-label": "Criar novo rótulo",
|
||||
"create-new-relation": "Criar nova relação",
|
||||
"ribbon-tabs": "Abas da faixa",
|
||||
"toggle-basic-properties": "Alterar Propriedades Básicas",
|
||||
"toggle-file-properties": "Alterar Propriedades do Arquivo",
|
||||
"toggle-image-properties": "Alterar Propriedades da Imagem",
|
||||
"toggle-owned-attributes": "Alterar Atributos Próprios",
|
||||
"toggle-inherited-attributes": "Alterar Atributos Herdados",
|
||||
"toggle-promoted-attributes": "Alternar Atributos Promovidos",
|
||||
"toggle-link-map": "Alternar Mapa de Links",
|
||||
"toggle-note-info": "Alternar Informações da Nota",
|
||||
"toggle-note-paths": "Alternar Caminhos da Nota",
|
||||
"toggle-similar-notes": "Alternar Notas Similares",
|
||||
"other": "Outros",
|
||||
"toggle-right-pane": "Alternar a exibição do painel direito, que inclui Sumário e Destaques",
|
||||
"print-active-note": "Imprimir nota atual",
|
||||
"open-note-externally": "Abrir nota como arquivo no aplicativo padrão",
|
||||
"render-active-note": "Renderizar (re-renderizar) nota atual",
|
||||
"run-active-note": "Executar código JavaScript (frontend/backend) da nota",
|
||||
"toggle-note-hoisting": "Alternar a elevação da nota atual",
|
||||
"unhoist": "Desfazer elevação de tudo",
|
||||
"reload-frontend-app": "Recarregar Interface",
|
||||
"open-dev-tools": "Abrir ferramentas de desenvolvedor",
|
||||
"toggle-left-note-tree-panel": "Alternar painel esquerdo (árvore de notas)",
|
||||
"toggle-full-screen": "Alternar para tela cheia",
|
||||
"zoom-out": "Diminuir zoom",
|
||||
"zoom-in": "Aumentar zoom",
|
||||
"note-navigation": "Navegação de notas",
|
||||
"reset-zoom-level": "Redefinir nível de zoom",
|
||||
"copy-without-formatting": "Copiar texto selecionado sem formatação",
|
||||
"force-save-revision": "Forçar a criação/salvamento de uma nova revisão da nota atual",
|
||||
"show-help": "Exibir Ajuda integrada / colinha",
|
||||
"toggle-book-properties": "Alternar propriedades do book",
|
||||
"toggle-classic-editor-toolbar": "Alternar a aba de Formatação no editor com barra de ferramentas fixa",
|
||||
"back-in-note-history": "Navegar para a nota anterior no histórico",
|
||||
"forward-in-note-history": "Navegar para a próxima nota no histórico",
|
||||
"open-command-palette": "Abrir paleta de comandos",
|
||||
"scroll-to-active-note": "Rolar a árvore de notas até a nota atual",
|
||||
"quick-search": "Ativar barra de busca rápida",
|
||||
"create-note-after": "Criar nota após nota atual",
|
||||
"create-note-into": "Criar nota como subnota da nota atual",
|
||||
"clone-notes-to": "Clonar notas selecionadas",
|
||||
"move-notes-to": "Mover notas selecionadas",
|
||||
"find-in-text": "Alternar painel de busca",
|
||||
"export-as-pdf": "Exportar a nota atual como PDF",
|
||||
"toggle-zen-mode": "Ativa/desativa o modo zen (interface mínima para uma edição mais focada)",
|
||||
"show-cheatsheet": "Exibir um modal com operações comuns de teclado"
|
||||
},
|
||||
"login": {
|
||||
"title": "Login",
|
||||
"heading": "Trilium login",
|
||||
"incorrect-password": "Senha incorreta. Tente novamente.",
|
||||
"password": "Senha",
|
||||
"remember-me": "Lembrar",
|
||||
"button": "Login",
|
||||
"incorrect-totp": "O código TOTP está incorreto. Por favor, tente novamente.",
|
||||
"sign_in_with_sso": "Fazer login com {{ ssoIssuerName }}"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Definir senha",
|
||||
"heading": "Definir senha",
|
||||
"description": "Antes de começar a usar o Trilium web, você precisa definir uma senha. Você usará essa senha para fazer login.",
|
||||
"password": "Senha",
|
||||
"password-confirmation": "Confirmar Senha",
|
||||
"button": "Definir senha"
|
||||
},
|
||||
"javascript-required": "Trilium precisa que JavaScript esteja habilitado.",
|
||||
"setup": {
|
||||
"heading": "Trilium Notes setup",
|
||||
"new-document": "Sou um novo usuário e quero criar um novo documento Trilium para minhas notas",
|
||||
"sync-from-desktop": "Já tenho uma instância no desktop e quero configurar a sincronização com ela",
|
||||
"sync-from-server": "Já tenho uma instância no servidor e quero configurar a sincronização com ela",
|
||||
"next": "Avançar",
|
||||
"init-in-progress": "Inicialização do documento em andamento",
|
||||
"redirecting": "Você será redirecionado para o aplicativo em breve.",
|
||||
"title": "Setup"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "Sincronizar com Desktop",
|
||||
"description": "Esta configuração deve ser iniciada a partir da instância do desktop:",
|
||||
"step1": "Abra sua instância do Trilium Notes no desktop.",
|
||||
"step2": "No menu do Trilium, clique em Opções.",
|
||||
"step3": "Clique na categoria Sincronização.",
|
||||
"step4": "Altere o endereço da instância do servidor para: {{- host}} e clique em Salvar.",
|
||||
"step5": "Clique no botão \"Testar sincronização\" para verificar se a conexão foi bem-sucedida.",
|
||||
"step6": "Depois de concluir essas etapas, clique em {{- link}}.",
|
||||
"step6-here": "Aqui"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "Sincronizar do Servidor",
|
||||
"instructions": "Por favor, insira abaixo o endereço e as credenciais do servidor Trilium. Isso fará o download de todo o documento Trilium do servidor e configurará a sincronização com ele. Dependendo do tamanho do documento e da velocidade da conexão, isso pode levar algum tempo.",
|
||||
"server-host": "Endereço do servidor Trilium",
|
||||
"server-host-placeholder": "https://<hostname>:<port>",
|
||||
"proxy-server": "Servidor proxy (opcional)",
|
||||
"proxy-server-placeholder": "https://<hostname>:<port>",
|
||||
"note": "Nota:",
|
||||
"proxy-instruction": "Se você deixar o campo de proxy em branco, o proxy do sistema será usado (aplicável apenas ao aplicativo desktop)",
|
||||
"password": "Senha",
|
||||
"password-placeholder": "Senha",
|
||||
"back": "Voltar",
|
||||
"finish-setup": "Terminar configuração"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Sincronização em andamento",
|
||||
"successful": "A sincronização foi configurada corretamente. Levará algum tempo para que a sincronização inicial seja concluída. Quando terminar, você será redirecionado para a página de login.",
|
||||
"outstanding-items": "Itens de sincronização pendentes:",
|
||||
"outstanding-items-default": "N/A"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "Não encontrado",
|
||||
"heading": "Não encontrado"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "pai:",
|
||||
"clipped-from": "Esta nota foi originalmente extraída de {{- url}}",
|
||||
"child-notes": "Notas filhas:",
|
||||
"no-content": "Esta nota não possui conteúdo."
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Segunda-feira",
|
||||
"tuesday": "Terça-feira",
|
||||
"wednesday": "Quarta-feira",
|
||||
"thursday": "Quinta-feira",
|
||||
"friday": "Sexta-feira",
|
||||
"saturday": "Sábado",
|
||||
"sunday": "Domingo"
|
||||
},
|
||||
"months": {
|
||||
"january": "Janeiro",
|
||||
"february": "Fevereiro",
|
||||
"march": "Março",
|
||||
"april": "Abril",
|
||||
"may": "Maio",
|
||||
"june": "Junho",
|
||||
"july": "Julho",
|
||||
"august": "Agosto",
|
||||
"september": "Setembro",
|
||||
"october": "Outubro",
|
||||
"november": "Novembro",
|
||||
"december": "Dezembro"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "Pesquisar:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "O host do servidor de sincronização não está configurado. Por favor, configure a sincronização primeiro.",
|
||||
"successful": "A comunicação com o servidor de sincronização foi bem-sucedida, a sincronização foi iniciada."
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"back-in-note-history": "Voltar no histórico da nota",
|
||||
"forward-in-note-history": "Avançar no histórico da nota",
|
||||
"jump-to-note": "Ir para...",
|
||||
"command-palette": "Paleta de Comandos",
|
||||
"scroll-to-active-note": "Rolar até a nota atual",
|
||||
"quick-search": "Busca Rápida",
|
||||
"search-in-subtree": "Buscar na subárvore",
|
||||
"expand-subtree": "Expandir subárvore",
|
||||
"collapse-tree": "Recolher Árvore",
|
||||
"collapse-subtree": "Recolher Subárvore",
|
||||
"sort-child-notes": "Ordenar Notas Filhas",
|
||||
"create-note-after": "Criar Nota Após",
|
||||
"create-note-into": "Criar Nota Dentro",
|
||||
"create-note-into-inbox": "Criar Nota na Caixa de Entrada",
|
||||
"delete-notes": "Excluir Notas",
|
||||
"move-note-up": "Mover Nota Para Cima",
|
||||
"move-note-down": "Mover Nota Para Baixo",
|
||||
"move-note-up-in-hierarchy": "Mover Nota Para Cima na Hierarquia",
|
||||
"move-note-down-in-hierarchy": "Mover Nota Para Baixo na Hierarquia",
|
||||
"edit-note-title": "Editar Título da Nota",
|
||||
"edit-branch-prefix": "Editar prefixo da ramificação",
|
||||
"clone-notes-to": "Clonar Notas Para",
|
||||
"move-notes-to": "Mover Notas Para",
|
||||
"copy-notes-to-clipboard": "Copiar Notas para a Área de Transferência",
|
||||
"paste-notes-from-clipboard": "Colar Notas da Área de Transferência",
|
||||
"cut-notes-to-clipboard": "Recortar Notas para a Área de Transferência",
|
||||
"select-all-notes-in-parent": "Selecionar Todas as Notas no Pai",
|
||||
"add-note-above-to-selection": "Adicionar Nota Acima à Seleção",
|
||||
"add-note-below-to-selection": "Adicionar Nota Abaixo à Seleção",
|
||||
"duplicate-subtree": "Duplicar Subárvore",
|
||||
"open-new-tab": "Abrir Nova Guia",
|
||||
"close-active-tab": "Fechar Guia Ativa",
|
||||
"reopen-last-tab": "Reabrir Última Guia",
|
||||
"activate-next-tab": "Ativar Próxima Guia",
|
||||
"activate-previous-tab": "Ativar Guia Anterior",
|
||||
"open-new-window": "Abrir Nova Janela",
|
||||
"toggle-system-tray-icon": "Alternar Ícone da Bandeja do Sistema",
|
||||
"toggle-zen-mode": "Alternar Modo Zen",
|
||||
"switch-to-first-tab": "Alternar para a Primeira Guia",
|
||||
"switch-to-second-tab": "Alternar para a Segunda Guia",
|
||||
"switch-to-third-tab": "Alternar para a Terceira Guia",
|
||||
"switch-to-fourth-tab": "Alternar para a Quarta Guia",
|
||||
"switch-to-fifth-tab": "Alternar para a Quinta Guia",
|
||||
"switch-to-sixth-tab": "Alternar para a Sexta Guia",
|
||||
"switch-to-seventh-tab": "Alternar para a Sétima Guia",
|
||||
"switch-to-eighth-tab": "Alternar para a Oitava Guia",
|
||||
"switch-to-ninth-tab": "Alternar para a Nona Guia",
|
||||
"switch-to-last-tab": "Alternar para a Última Guia",
|
||||
"show-note-source": "Exibir Fonte da Nota",
|
||||
"show-options": "Exibir Opções",
|
||||
"show-revisions": "Exibir Revisões",
|
||||
"show-recent-changes": "Exibir Alterações Recentes",
|
||||
"show-sql-console": "Exibir Console SQL",
|
||||
"show-backend-log": "Exibir Log do Backend",
|
||||
"show-help": "Exibir Ajuda",
|
||||
"show-cheatsheet": "Exibir Cheatsheet",
|
||||
"add-link-to-text": "Adicionar Link ao Texto",
|
||||
"follow-link-under-cursor": "Seguir Link sob o Cursor",
|
||||
"insert-date-and-time-to-text": "Inserir Data e Hora ao Texto",
|
||||
"paste-markdown-into-text": "Colar Markdown no Texto",
|
||||
"cut-into-note": "Recortar em Nota",
|
||||
"add-include-note-to-text": "Adicionar Nota de Inclusão ao Texto",
|
||||
"edit-read-only-note": "Editar Nota Somente-Leitura",
|
||||
"add-new-label": "Adicionar Nova Etiqueta",
|
||||
"add-new-relation": "Adicionar Nova Relação",
|
||||
"toggle-ribbon-tab-classic-editor": "Alternar Guia da Faixa de Opções Editor Clássico",
|
||||
"toggle-ribbon-tab-basic-properties": "Alternar Guia da Faixa de Opções Propriedades Básicas",
|
||||
"toggle-ribbon-tab-book-properties": "Alternar Guia da Faixa de Opções Propriedades do Livro",
|
||||
"toggle-ribbon-tab-file-properties": "Alternar Guia da Faixa de Opções Propriedades do Arquivo",
|
||||
"toggle-ribbon-tab-image-properties": "Alternar Guia da Faixa de Opções Propriedades da Imagem",
|
||||
"toggle-ribbon-tab-owned-attributes": "Alternar Guia da Faixa de Opções Atributos Possuídos",
|
||||
"toggle-ribbon-tab-inherited-attributes": "Alternar Guia da Faixa de Opções Atributos Herdados",
|
||||
"toggle-ribbon-tab-promoted-attributes": "Alternar Guia da Faixa de Opções Atributos Promovidos",
|
||||
"toggle-ribbon-tab-note-map": "Alternar Guia da Faixa de Opções Mapa de Notas",
|
||||
"toggle-ribbon-tab-note-info": "Alternar Guia da Faixa de Opções Informações da Nota",
|
||||
"toggle-ribbon-tab-note-paths": "Alternar Guia da Faixa de Opções Caminhos de Nota",
|
||||
"toggle-ribbon-tab-similar-notes": "Alternar Guia da Faixa de Opções Notas Semelhantes",
|
||||
"toggle-right-pane": "Alternar Painel Direito",
|
||||
"print-active-note": "Imprimir Nota Atual",
|
||||
"export-active-note-as-pdf": "Exportar Nota Atual como PDF",
|
||||
"open-note-externally": "Abrir Nota Externamente",
|
||||
"render-active-note": "Renderizar Nota Atual",
|
||||
"run-active-note": "Executar Nota Atual",
|
||||
"toggle-note-hoisting": "Alternar Elevação de Nota",
|
||||
"unhoist-note": "Desfazer Elevação de Nota",
|
||||
"reload-frontend-app": "Recarregar Frontend",
|
||||
"open-developer-tools": "Abrir Ferramentas de Desenvolvedor",
|
||||
"find-in-text": "Localizar no Texto",
|
||||
"toggle-left-pane": "Alternar Painel Esquerdo",
|
||||
"toggle-full-screen": "Alternar Tela Cheia",
|
||||
"zoom-out": "Reduzir Zoom",
|
||||
"zoom-in": "Aumentar Zoom",
|
||||
"reset-zoom-level": "Redefinir Nível de Zoom",
|
||||
"copy-without-formatting": "Copiar Sem Formatação",
|
||||
"force-save-revision": "Forçar Salvamento da Revisão"
|
||||
},
|
||||
"weekdayNumber": "Semana {weekNumber}",
|
||||
"quarterNumber": "Trimestre {quarterNumber}",
|
||||
"hidden-subtree": {
|
||||
"root-title": "Notas Ocultas",
|
||||
"search-history-title": "Histórico de Pesquisa",
|
||||
"note-map-title": "Mapa de Notas",
|
||||
"sql-console-history-title": "Histórico do Console SQL",
|
||||
"shared-notes-title": "Notas Compartilhadas",
|
||||
"bulk-action-title": "Ação em Massa",
|
||||
"backend-log-title": "Log do Backend",
|
||||
"user-hidden-title": "Usuário Oculto",
|
||||
"launch-bar-templates-title": "Modelos da Barra de Atalhos",
|
||||
"built-in-widget-title": "Widget Incorporado",
|
||||
"spacer-title": "Espaçador",
|
||||
"custom-widget-title": "Widget Personalizado",
|
||||
"go-to-previous-note-title": "Ir para Nota Anterior",
|
||||
"go-to-next-note-title": "Ir para Próxima Nota",
|
||||
"new-note-title": "Nova Nota",
|
||||
"search-notes-title": "Pesquisar Notas",
|
||||
"jump-to-note-title": "Ir para...",
|
||||
"calendar-title": "Calendário",
|
||||
"recent-changes-title": "Alterações Recentes",
|
||||
"bookmarks-title": "Favoritos",
|
||||
"open-today-journal-note-title": "Abrir Nota do Diário de Hoje",
|
||||
"quick-search-title": "Pesquisa Rápida",
|
||||
"protected-session-title": "Sessão Protegida",
|
||||
"sync-status-title": "Status de Sincronização",
|
||||
"settings-title": "Configurações",
|
||||
"llm-chat-title": "Conversar com as Notas",
|
||||
"options-title": "Opções",
|
||||
"appearance-title": "Aparência",
|
||||
"shortcuts-title": "Atalhos",
|
||||
"text-notes": "Notas de Texto",
|
||||
"code-notes-title": "Notas de Código",
|
||||
"images-title": "Imagens",
|
||||
"spellcheck-title": "Verificação Ortográfica",
|
||||
"password-title": "Senha",
|
||||
"multi-factor-authentication-title": "MFA",
|
||||
"etapi-title": "ETAPI",
|
||||
"backup-title": "Backup",
|
||||
"sync-title": "Sincronizar",
|
||||
"ai-llm-title": "AI/LLM",
|
||||
"other": "Outros",
|
||||
"advanced-title": "Avançado",
|
||||
"user-guide": "Guia do Usuário",
|
||||
"localization": "Idioma e Região",
|
||||
"inbox-title": "Inbox",
|
||||
"base-abstract-launcher-title": "Atalho Abstrato Base",
|
||||
"command-launcher-title": "Atalho de Comando",
|
||||
"note-launcher-title": "Atalho de Notas",
|
||||
"script-launcher-title": "Atalho de Script",
|
||||
"launch-bar-title": "Barra de Atalhos",
|
||||
"available-launchers-title": "Atalhos disponíveis",
|
||||
"visible-launchers-title": "Atalhos Visíveis"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Nova nota",
|
||||
"duplicate-note-suffix": "(dup)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "O arquivo de log do backend '{{ fileName }}' ainda não existe.",
|
||||
"reading-log-failed": "Falha ao ler o arquivo de log do backend '{{ fileName }}'."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Esta nota não pode ser exibida."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "Documento PDF (*.pdf)",
|
||||
"unable-to-export-message": "A nota atual não pôde ser exportada como PDF.",
|
||||
"unable-to-export-title": "Não foi possível exportar como PDF",
|
||||
"unable-to-save-message": "O arquivo selecionado não pôde ser salvo. Tente novamente ou selecione outro destino."
|
||||
},
|
||||
"tray": {
|
||||
"tooltip": "Trilium Notes",
|
||||
"close": "Sair do Trilium",
|
||||
"recents": "Notas recentes",
|
||||
"bookmarks": "Favoritos",
|
||||
"today": "Abrir a nota do diário de hoje",
|
||||
"new-note": "Nova nota",
|
||||
"show-windows": "Exibir janelas",
|
||||
"open_new_window": "Abrir nova janela"
|
||||
},
|
||||
"migration": {
|
||||
"old_version": "A migração direta da sua versão atual não é suportada. Por favor, atualize primeiro para a versão mais recente v0.60.4 e somente depois para esta versão.",
|
||||
"error_message": "Erro durante a migração para a versão {{version}}: {{stack}}",
|
||||
"wrong_db_version": "A versão do banco de dados ({{version}}) é mais recente do que a esperada pelo aplicativo ({{targetVersion}}), o que significa que ele foi criado por uma versão mais nova e incompatível do Trilium. Atualize para a versão mais recente do Trilium para resolver esse problema."
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Erro"
|
||||
},
|
||||
"share_theme": {
|
||||
"site-theme": "Tema do site",
|
||||
"search_placeholder": "Pesquisar...",
|
||||
"image_alt": "Imagem do artigo",
|
||||
"last-updated": "Atualizado pela última vez em {{- date}}",
|
||||
"subpages": "Subpáginas:",
|
||||
"on-this-page": "Nesta página",
|
||||
"expand": "Expandir"
|
||||
},
|
||||
"hidden_subtree_templates": {
|
||||
"text-snippet": "Trecho de texto",
|
||||
"description": "Descrição",
|
||||
"list-view": "Visualização em lista",
|
||||
"grid-view": "Visualização em grade",
|
||||
"calendar": "Calendário",
|
||||
"table": "Tabela",
|
||||
"geo-map": "Mapa geográfico",
|
||||
"start-date": "Data de início",
|
||||
"end-date": "Data de término",
|
||||
"start-time": "Hora de início",
|
||||
"end-time": "Hora de término",
|
||||
"geolocation": "Geolocalização",
|
||||
"built-in-templates": "Modelos integrados",
|
||||
"board": "Quadro",
|
||||
"status": "Status",
|
||||
"board_note_first": "Primeira nota",
|
||||
"board_note_second": "Segunda nota",
|
||||
"board_note_third": "Terceira nota",
|
||||
"board_status_todo": "A fazer",
|
||||
"board_status_progress": "Em andamento",
|
||||
"board_status_done": "Concluído"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,279 +1,431 @@
|
||||
{
|
||||
"keyboard_actions": {
|
||||
"activate-next-tab": "Activează tab-ul din dreapta",
|
||||
"activate-previous-tab": "Activează tab-ul din stânga",
|
||||
"add-include-note-to-text": "Deschide fereastra de includere notițe",
|
||||
"add-link-to-text": "Deschide fereastra de adaugă legături în text",
|
||||
"add-new-label": "Crează o nouă etichetă",
|
||||
"add-note-above-to-the-selection": "Adaugă notița deasupra selecției",
|
||||
"add-note-below-to-selection": "Adaugă notița deasupra selecției",
|
||||
"attributes-labels-and-relations": "Atribute (etichete și relații)",
|
||||
"close-active-tab": "Închide tab-ul activ",
|
||||
"collapse-subtree": "Minimizează ierarhia notiței curente",
|
||||
"collapse-tree": "Minimizează întreaga ierarhie de notițe",
|
||||
"copy-notes-to-clipboard": "Copiază notițele selectate în clipboard",
|
||||
"copy-without-formatting": "Copiază textul selectat fără formatare",
|
||||
"create-new-relation": "Crează o nouă relație",
|
||||
"create-note-into-inbox": "Crează o notiță și o deschide în inbox (dacă este definit) sau notița zilnică",
|
||||
"cut-notes-to-clipboard": "Decupează notițele selectate în clipboard",
|
||||
"creating-and-moving-notes": "Crearea și mutarea notițelor",
|
||||
"cut-into-note": "Decupează selecția din notița curentă și crează o subnotiță cu textul selectat",
|
||||
"delete-note": "Șterge notița",
|
||||
"dialogs": "Ferestre",
|
||||
"duplicate-subtree": "Dublifică subnotițele",
|
||||
"edit-branch-prefix": "Afișează ecranul „Editează prefixul ramurii”",
|
||||
"edit-note-title": "Sare de la arborele notițelor la detaliile notiței și editează titlul",
|
||||
"edit-readonly-note": "Editează o notiță care este în modul doar în citire",
|
||||
"eight-tab": "Activează cel de-al optelea tab din listă",
|
||||
"expand-subtree": "Expandează ierarhia notiței curente",
|
||||
"fifth-tab": "Activează cel de-al cincelea tab din listă",
|
||||
"first-tab": "Activează cel primul tab din listă",
|
||||
"follow-link-under-cursor": "Urmărește legătura de sub poziția cursorului",
|
||||
"force-save-revision": "Forțează crearea/salvarea unei noi revizii ale notiției curente",
|
||||
"fourth-tab": "Activează cel de-al patrulea tab din listă",
|
||||
"insert-date-and-time-to-text": "Inserează data curentă și timpul în text",
|
||||
"last-tab": "Activează ultimul tab din listă",
|
||||
"move-note-down": "Mută notița mai jos",
|
||||
"move-note-down-in-hierarchy": "Mută notița mai jos în ierarhie",
|
||||
"move-note-up": "Mută notița mai sus",
|
||||
"move-note-up-in-hierarchy": "Mută notița mai sus în ierarhie",
|
||||
"ninth-tab": "Activează cel de-al nouălea tab din listă",
|
||||
"note-clipboard": "Clipboard-ul notițelor",
|
||||
"note-navigation": "Navigarea printre notițe",
|
||||
"open-dev-tools": "Deschide uneltele de dezvoltator",
|
||||
"open-jump-to-note-dialog": "Deschide ecranul „Sari la notiță”",
|
||||
"open-new-tab": "Deschide un tab nou",
|
||||
"open-new-window": "Deschide o nouă fereastră goală",
|
||||
"open-note-externally": "Deschide notița ca un fișier utilizând aplicația implicită",
|
||||
"other": "Altele",
|
||||
"paste-markdown-into-text": "Lipește text Markdown din clipboard în notița de tip text",
|
||||
"paste-notes-from-clipboard": "Lipește notițele din clipboard în notița activă",
|
||||
"print-active-note": "Imprimă notița activă",
|
||||
"reload-frontend-app": "Reîncarcă interfața grafică",
|
||||
"render-active-note": "Reîmprospătează notița activă",
|
||||
"reopen-last-tab": "Deschide din nou ultimul tab închis",
|
||||
"reset-zoom-level": "Resetează nivelul de magnificare",
|
||||
"ribbon-tabs": "Tab-urile din panglică",
|
||||
"run-active-note": "Rulează notița curentă de tip JavaScript (frontend sau backend)",
|
||||
"search-in-subtree": "Caută notițe în ierarhia notiței active",
|
||||
"second-tab": "Activează cel de-al doilea tab din listă",
|
||||
"select-all-notes-in-parent": "Selectează toate notițele din nivelul notiței curente",
|
||||
"seventh-tab": "Activează cel de-al șaptelea tab din listă",
|
||||
"show-backend-log": "Afișează fereastra „Log-uri din backend”",
|
||||
"show-help": "Afișează informații utile",
|
||||
"show-note-source": "Afișează fereastra „Sursa notiței”",
|
||||
"show-options": "Afișează fereastra de opțiuni",
|
||||
"show-recent-changes": "Afișează fereastra „Schimbări recente”",
|
||||
"show-revisions": "Afișează fereastra „Revizii ale notiței”",
|
||||
"show-sql-console": "Afișează ecranul „Consolă SQL”",
|
||||
"sixth-tab": "Activează cel de-al șaselea tab din listă",
|
||||
"sort-child-notes": "Ordonează subnotițele",
|
||||
"tabs-and-windows": "Tab-uri și ferestre",
|
||||
"text-note-operations": "Operații asupra notițelor text",
|
||||
"third-tab": "Activează cel de-al treilea tab din listă",
|
||||
"toggle-basic-properties": "Comută tab-ul „Proprietăți de bază”",
|
||||
"toggle-book-properties": "Comută tab-ul „Proprietăți ale cărții”",
|
||||
"toggle-file-properties": "Comută tab-ul „Proprietăți fișier”",
|
||||
"toggle-full-screen": "Comută modul ecran complet",
|
||||
"toggle-image-properties": "Comută tab-ul „Proprietăți imagini”",
|
||||
"toggle-inherited-attributes": "Comută tab-ul „Atribute moștenite”",
|
||||
"toggle-left-note-tree-panel": "Comută panoul din stânga (arborele notițelor)",
|
||||
"toggle-link-map": "Comută harta legăturilor",
|
||||
"toggle-note-hoisting": "Comută focalizarea pe notița curentă",
|
||||
"toggle-note-info": "Comută tab-ul „Informații despre notiță”",
|
||||
"toggle-note-paths": "Comută tab-ul „Căile notiței”",
|
||||
"toggle-owned-attributes": "Comută tab-ul „Atribute proprii”",
|
||||
"toggle-promoted-attributes": "Comută tab-ul „Atribute promovate”",
|
||||
"toggle-right-pane": "Comută afișarea panoului din dreapta, ce include tabela de conținut și evidențieri",
|
||||
"toggle-similar-notes": "Comută tab-ul „Notițe similare”",
|
||||
"toggle-tray": "Afișează/ascunde aplicația din tray-ul de sistem",
|
||||
"unhoist": "Defocalizează complet",
|
||||
"zoom-in": "Mărește zoom-ul",
|
||||
"zoom-out": "Micșorează zoom-ul",
|
||||
"toggle-classic-editor-toolbar": "Comută tab-ul „Formatare” pentru editorul cu bară fixă",
|
||||
"export-as-pdf": "Exportă notița curentă ca PDF",
|
||||
"show-cheatsheet": "Afișează o fereastră cu scurtături de la tastatură comune",
|
||||
"toggle-zen-mode": "Activează/dezactivează modul zen (o interfață minimală, fără distrageri)"
|
||||
},
|
||||
"login": {
|
||||
"button": "Autentifică",
|
||||
"heading": "Autentificare în Trilium",
|
||||
"incorrect-password": "Parola nu este corectă. Încercați din nou.",
|
||||
"password": "Parolă",
|
||||
"remember-me": "Ține-mă minte",
|
||||
"title": "Autentificare"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Setare parolă",
|
||||
"heading": "Setare parolă",
|
||||
"button": "Setează parola",
|
||||
"description": "Înainte de a putea utiliza Trilium din navigator, trebuie mai întâi setată o parolă. Ulterior această parolă va fi folosită la autentificare.",
|
||||
"password": "Parolă",
|
||||
"password-confirmation": "Confirmarea parolei"
|
||||
},
|
||||
"javascript-required": "Trilium necesită JavaScript să fie activat pentru a putea funcționa.",
|
||||
"setup": {
|
||||
"heading": "Instalarea Trilium Notes",
|
||||
"init-in-progress": "Se inițializează documentul",
|
||||
"new-document": "Sunt un utilizator nou și doresc să creez un document Trilium pentru notițele mele",
|
||||
"next": "Mai departe",
|
||||
"redirecting": "În scurt timp veți fi redirecționat la aplicație.",
|
||||
"sync-from-desktop": "Am deja o instanță de desktop și aș dori o sincronizare cu aceasta",
|
||||
"sync-from-server": "Am deja o instanță de server și doresc o sincronizare cu aceasta",
|
||||
"title": "Inițializare"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"description": "Acești pași trebuie urmați de pe aplicația de desktop:",
|
||||
"heading": "Sincronizare cu aplicația desktop",
|
||||
"step1": "Deschideți aplicația Trilium Notes pentru desktop.",
|
||||
"step2": "Din meniul Trilium, dați clic pe Opțiuni.",
|
||||
"step3": "Clic pe categoria „Sincronizare”.",
|
||||
"step4": "Schimbați adresa server-ului către: {{- host}} și apăsați „Salvează”.",
|
||||
"step5": "Clic pe butonul „Testează sincronizarea” pentru a verifica dacă conexiunea a fost făcută cu succes.",
|
||||
"step6": "După ce ați completat pașii, dați click {{- link}}.",
|
||||
"step6-here": "aici"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"back": "Înapoi",
|
||||
"finish-setup": "Finalizează inițializarea",
|
||||
"heading": "Sincronizare cu server-ul",
|
||||
"instructions": "Introduceți adresa server-ului Trilium și credențialele în secțiunea de jos. Astfel se va descărca întregul document Trilium de pe server și se va configura sincronizarea cu acesta. În funcție de dimensiunea documentului și viteza rețelei, acest proces poate dura.",
|
||||
"note": "De remarcat:",
|
||||
"password": "Parolă",
|
||||
"proxy-instruction": "Dacă lăsați câmpul de proxy nesetat, proxy-ul de sistem va fi folosit (valabil doar pentru aplicația de desktop)",
|
||||
"proxy-server": "Server-ul proxy (opțional)",
|
||||
"proxy-server-placeholder": "https://<sistem>:<port>",
|
||||
"server-host": "Adresa server-ului Trilium",
|
||||
"server-host-placeholder": "https://<sistem>:<port>",
|
||||
"password-placeholder": "Parolă"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Sincronizare în curs",
|
||||
"outstanding-items": "Elemente de sincronizat:",
|
||||
"outstanding-items-default": "-",
|
||||
"successful": "Sincronizarea a fost configurată cu succes. Poate dura ceva timp pentru a finaliza sincronizarea inițială. După efectuarea ei se va redirecționa către pagina de autentificare."
|
||||
},
|
||||
"share_404": {
|
||||
"heading": "Pagină negăsită",
|
||||
"title": "Pagină negăsită"
|
||||
},
|
||||
"share_page": {
|
||||
"child-notes": "Subnotițe:",
|
||||
"clipped-from": "Această notiță a fost decupată inițial de pe {{- url}}",
|
||||
"no-content": "Această notiță nu are conținut.",
|
||||
"parent": "părinte:"
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Luni",
|
||||
"tuesday": "Marți",
|
||||
"wednesday": "Miercuri",
|
||||
"thursday": "Joi",
|
||||
"friday": "Vineri",
|
||||
"saturday": "Sâmbătă",
|
||||
"sunday": "Duminică"
|
||||
},
|
||||
"months": {
|
||||
"january": "Ianuarie",
|
||||
"february": "Februarie",
|
||||
"march": "Martie",
|
||||
"april": "Aprilie",
|
||||
"may": "Mai",
|
||||
"june": "Iunie",
|
||||
"july": "Iulie",
|
||||
"august": "August",
|
||||
"september": "Septembrie",
|
||||
"october": "Octombrie",
|
||||
"november": "Noiembrie",
|
||||
"december": "Decembrie"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "Căutare:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "Calea către serverul de sincronizare nu este configurată. Configurați sincronizarea înainte.",
|
||||
"successful": "Comunicarea cu serverul de sincronizare a avut loc cu succes, s-a început sincronizarea."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"advanced-title": "Setări avansate",
|
||||
"appearance-title": "Aspect",
|
||||
"available-launchers-title": "Lansatoare disponibile",
|
||||
"backup-title": "Copii de siguranță",
|
||||
"base-abstract-launcher-title": "Lansator abstract de bază",
|
||||
"bookmarks-title": "Semne de carte",
|
||||
"built-in-widget-title": "Widget predefinit",
|
||||
"bulk-action-title": "Acțiuni în masă",
|
||||
"calendar-title": "Calendar",
|
||||
"code-notes-title": "Notițe de cod",
|
||||
"command-launcher-title": "Lansator de comenzi",
|
||||
"custom-widget-title": "Widget personalizat",
|
||||
"etapi-title": "ETAPI",
|
||||
"go-to-previous-note-title": "Mergi la notița anterioară",
|
||||
"images-title": "Imagini",
|
||||
"launch-bar-title": "Bară de lansare",
|
||||
"new-note-title": "Notiță nouă",
|
||||
"note-launcher-title": "Lansator de notițe",
|
||||
"note-map-title": "Harta notițelor",
|
||||
"open-today-journal-note-title": "Deschide notița de astăzi",
|
||||
"options-title": "Opțiuni",
|
||||
"other": "Diverse",
|
||||
"password-title": "Parolă",
|
||||
"protected-session-title": "Sesiune protejată",
|
||||
"recent-changes-title": "Schimbări recente",
|
||||
"script-launcher-title": "Lansator de script-uri",
|
||||
"search-history-title": "Istoric de căutare",
|
||||
"settings-title": "Setări",
|
||||
"shared-notes-title": "Notițe partajate",
|
||||
"quick-search-title": "Căutare rapidă",
|
||||
"root-title": "Notițe ascunse",
|
||||
"search-notes-title": "Căutare notițe",
|
||||
"shortcuts-title": "Scurtături",
|
||||
"spellcheck-title": "Corectare gramaticală",
|
||||
"sync-status-title": "Starea sincronizării",
|
||||
"sync-title": "Sincronizare",
|
||||
"text-notes": "Notițe text",
|
||||
"user-hidden-title": "Definite de utilizator",
|
||||
"backend-log-title": "Log backend",
|
||||
"spacer-title": "Separator",
|
||||
"sql-console-history-title": "Istoricul consolei SQL",
|
||||
"go-to-next-note-title": "Mergi la notița următoare",
|
||||
"launch-bar-templates-title": "Șabloane bară de lansare",
|
||||
"visible-launchers-title": "Lansatoare vizibile",
|
||||
"user-guide": "Ghidul de utilizare"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Notiță nouă"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "Fișierul de loguri de backend „{{ fileName }}” nu există (încă).",
|
||||
"reading-log-failed": "Nu s-a putut citi fișierul de loguri de backend „{{fileName}}”."
|
||||
},
|
||||
"geo-map": {
|
||||
"create-child-note-instruction": "Clic pe hartă pentru a crea o nouă notiță la acea poziție sau apăsați Escape pentru a renunța."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Acest tip de notiță nu poate fi afișat."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "Document PDF (*.pdf)",
|
||||
"unable-to-export-message": "Notița curentă nu a putut fi exportată ca PDF.",
|
||||
"unable-to-export-title": "Nu s-a putut exporta ca PDF",
|
||||
"unable-to-save-message": "Nu s-a putut scrie fișierul selectat. Încercați din nou sau selectați altă destinație."
|
||||
},
|
||||
"tray": {
|
||||
"bookmarks": "Semne de carte",
|
||||
"close": "Închide Trilium",
|
||||
"new-note": "Notiță nouă",
|
||||
"recents": "Notițe recente",
|
||||
"today": "Mergi la notița de astăzi",
|
||||
"tooltip": "Trilium Notes",
|
||||
"show-windows": "Afișează ferestrele"
|
||||
},
|
||||
"migration": {
|
||||
"error_message": "Eroare la migrarea către versiunea {{version}}: {{stack}}",
|
||||
"old_version": "Nu se poate migra la ultima versiune direct de la versiunea dvs. Actualizați mai întâi la versiunea v0.60.4 și ulterior la această versiune.",
|
||||
"wrong_db_version": "Versiunea actuală a bazei de date ({{version}}) este mai nouă decât versiunea de bază de date suportată de aplicație ({{targetVersion}}), ceea ce înseamnă că a fost creată de către o versiune mai nouă de Trilium. Actualizați aplicația la ultima versiune pentru a putea continua."
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Eroare"
|
||||
}
|
||||
"keyboard_actions": {
|
||||
"activate-next-tab": "Activează tab-ul din dreapta",
|
||||
"activate-previous-tab": "Activează tab-ul din stânga",
|
||||
"add-include-note-to-text": "Deschide fereastra de includere notițe",
|
||||
"add-link-to-text": "Deschide fereastra de adaugă legături în text",
|
||||
"add-new-label": "Crează o nouă etichetă",
|
||||
"add-note-above-to-the-selection": "Adaugă notița deasupra selecției",
|
||||
"add-note-below-to-selection": "Adaugă notița deasupra selecției",
|
||||
"attributes-labels-and-relations": "Atribute (etichete și relații)",
|
||||
"close-active-tab": "Închide tab-ul activ",
|
||||
"collapse-subtree": "Minimizează ierarhia notiței curente",
|
||||
"collapse-tree": "Minimizează întreaga ierarhie de notițe",
|
||||
"copy-notes-to-clipboard": "Copiază notițele selectate în clipboard",
|
||||
"copy-without-formatting": "Copiază textul selectat fără formatare",
|
||||
"create-new-relation": "Crează o nouă relație",
|
||||
"create-note-into-inbox": "Crează o notiță și o deschide în inbox (dacă este definit) sau notița zilnică",
|
||||
"cut-notes-to-clipboard": "Decupează notițele selectate în clipboard",
|
||||
"creating-and-moving-notes": "Crearea și mutarea notițelor",
|
||||
"cut-into-note": "Decupează selecția din notița curentă și crează o subnotiță cu textul selectat",
|
||||
"delete-note": "Șterge notița",
|
||||
"dialogs": "Ferestre",
|
||||
"duplicate-subtree": "Dublifică subnotițele",
|
||||
"edit-branch-prefix": "Afișează ecranul „Editează prefixul ramurii”",
|
||||
"edit-note-title": "Sare de la arborele notițelor la detaliile notiței și editează titlul",
|
||||
"edit-readonly-note": "Editează o notiță care este în modul doar în citire",
|
||||
"eight-tab": "Activează cel de-al optelea tab din listă",
|
||||
"expand-subtree": "Expandează ierarhia notiței curente",
|
||||
"fifth-tab": "Activează cel de-al cincelea tab din listă",
|
||||
"first-tab": "Activează cel primul tab din listă",
|
||||
"follow-link-under-cursor": "Urmărește legătura de sub poziția cursorului",
|
||||
"force-save-revision": "Forțează crearea/salvarea unei noi revizii ale notiției curente",
|
||||
"fourth-tab": "Activează cel de-al patrulea tab din listă",
|
||||
"insert-date-and-time-to-text": "Inserează data curentă și timpul în text",
|
||||
"last-tab": "Activează ultimul tab din listă",
|
||||
"move-note-down": "Mută notița mai jos",
|
||||
"move-note-down-in-hierarchy": "Mută notița mai jos în ierarhie",
|
||||
"move-note-up": "Mută notița mai sus",
|
||||
"move-note-up-in-hierarchy": "Mută notița mai sus în ierarhie",
|
||||
"ninth-tab": "Activează cel de-al nouălea tab din listă",
|
||||
"note-clipboard": "Clipboard-ul notițelor",
|
||||
"note-navigation": "Navigarea printre notițe",
|
||||
"open-dev-tools": "Deschide uneltele de dezvoltator",
|
||||
"open-jump-to-note-dialog": "Deschide ecranul „Sari la notiță”",
|
||||
"open-new-tab": "Deschide un tab nou",
|
||||
"open-new-window": "Deschide o nouă fereastră goală",
|
||||
"open-note-externally": "Deschide notița ca un fișier utilizând aplicația implicită",
|
||||
"other": "Altele",
|
||||
"paste-markdown-into-text": "Lipește text Markdown din clipboard în notița de tip text",
|
||||
"paste-notes-from-clipboard": "Lipește notițele din clipboard în notița activă",
|
||||
"print-active-note": "Imprimă notița activă",
|
||||
"reload-frontend-app": "Reîncarcă interfața grafică",
|
||||
"render-active-note": "Reîmprospătează notița activă",
|
||||
"reopen-last-tab": "Deschide din nou ultimul tab închis",
|
||||
"reset-zoom-level": "Resetează nivelul de magnificare",
|
||||
"ribbon-tabs": "Tab-urile din panglică",
|
||||
"run-active-note": "Rulează notița curentă de tip JavaScript (frontend sau backend)",
|
||||
"search-in-subtree": "Caută notițe în ierarhia notiței active",
|
||||
"second-tab": "Activează cel de-al doilea tab din listă",
|
||||
"select-all-notes-in-parent": "Selectează toate notițele din nivelul notiței curente",
|
||||
"seventh-tab": "Activează cel de-al șaptelea tab din listă",
|
||||
"show-backend-log": "Afișează fereastra „Log-uri din backend”",
|
||||
"show-help": "Afișează informații utile",
|
||||
"show-note-source": "Afișează fereastra „Sursa notiței”",
|
||||
"show-options": "Afișează fereastra de opțiuni",
|
||||
"show-recent-changes": "Afișează fereastra „Schimbări recente”",
|
||||
"show-revisions": "Afișează fereastra „Revizii ale notiței”",
|
||||
"show-sql-console": "Afișează ecranul „Consolă SQL”",
|
||||
"sixth-tab": "Activează cel de-al șaselea tab din listă",
|
||||
"sort-child-notes": "Ordonează subnotițele",
|
||||
"tabs-and-windows": "Tab-uri și ferestre",
|
||||
"text-note-operations": "Operații asupra notițelor text",
|
||||
"third-tab": "Activează cel de-al treilea tab din listă",
|
||||
"toggle-basic-properties": "Comută tab-ul „Proprietăți de bază”",
|
||||
"toggle-book-properties": "Comută tab-ul „Proprietăți ale cărții”",
|
||||
"toggle-file-properties": "Comută tab-ul „Proprietăți fișier”",
|
||||
"toggle-full-screen": "Comută modul ecran complet",
|
||||
"toggle-image-properties": "Comută tab-ul „Proprietăți imagini”",
|
||||
"toggle-inherited-attributes": "Comută tab-ul „Atribute moștenite”",
|
||||
"toggle-left-note-tree-panel": "Comută panoul din stânga (arborele notițelor)",
|
||||
"toggle-link-map": "Comută harta legăturilor",
|
||||
"toggle-note-hoisting": "Comută focalizarea pe notița curentă",
|
||||
"toggle-note-info": "Comută tab-ul „Informații despre notiță”",
|
||||
"toggle-note-paths": "Comută tab-ul „Căile notiței”",
|
||||
"toggle-owned-attributes": "Comută tab-ul „Atribute proprii”",
|
||||
"toggle-promoted-attributes": "Comută tab-ul „Atribute promovate”",
|
||||
"toggle-right-pane": "Comută afișarea panoului din dreapta, ce include tabela de conținut și evidențieri",
|
||||
"toggle-similar-notes": "Comută tab-ul „Notițe similare”",
|
||||
"toggle-tray": "Afișează/ascunde aplicația din tray-ul de sistem",
|
||||
"unhoist": "Defocalizează complet",
|
||||
"zoom-in": "Mărește zoom-ul",
|
||||
"zoom-out": "Micșorează zoom-ul",
|
||||
"toggle-classic-editor-toolbar": "Comută tab-ul „Formatare” pentru editorul cu bară fixă",
|
||||
"export-as-pdf": "Exportă notița curentă ca PDF",
|
||||
"show-cheatsheet": "Afișează o fereastră cu scurtături de la tastatură comune",
|
||||
"toggle-zen-mode": "Activează/dezactivează modul zen (o interfață minimală, fără distrageri)",
|
||||
"back-in-note-history": "Mergi la notița anterioară din istoric",
|
||||
"forward-in-note-history": "Mergi la următoarea notiță din istoric",
|
||||
"scroll-to-active-note": "Derulează la notița activă în lista de notițe",
|
||||
"quick-search": "Mergi la bara de căutare rapidă",
|
||||
"create-note-after": "Crează o notiță după cea activă",
|
||||
"create-note-into": "Crează notiță ca subnotiță a notiței active",
|
||||
"clone-notes-to": "Clonează notițele selectate",
|
||||
"move-notes-to": "Mută notițele selectate",
|
||||
"find-in-text": "Afișează/ascunde panoul de căutare",
|
||||
"open-command-palette": "Deschide paleta de comenzi"
|
||||
},
|
||||
"login": {
|
||||
"button": "Autentifică",
|
||||
"heading": "Autentificare în Trilium",
|
||||
"incorrect-password": "Parola nu este corectă. Încercați din nou.",
|
||||
"password": "Parolă",
|
||||
"remember-me": "Ține-mă minte",
|
||||
"title": "Autentificare",
|
||||
"incorrect-totp": "TOTP-ul este incorect. Încercați din nou.",
|
||||
"sign_in_with_sso": "Autentificare cu {{ ssoIssuerName }}"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "Setare parolă",
|
||||
"heading": "Setare parolă",
|
||||
"button": "Setează parola",
|
||||
"description": "Înainte de a putea utiliza Trilium din navigator, trebuie mai întâi setată o parolă. Ulterior această parolă va fi folosită la autentificare.",
|
||||
"password": "Parolă",
|
||||
"password-confirmation": "Confirmarea parolei"
|
||||
},
|
||||
"javascript-required": "Trilium necesită JavaScript să fie activat pentru a putea funcționa.",
|
||||
"setup": {
|
||||
"heading": "Instalarea Trilium Notes",
|
||||
"init-in-progress": "Se inițializează documentul",
|
||||
"new-document": "Sunt un utilizator nou și doresc să creez un document Trilium pentru notițele mele",
|
||||
"next": "Mai departe",
|
||||
"redirecting": "În scurt timp veți fi redirecționat la aplicație.",
|
||||
"sync-from-desktop": "Am deja o instanță de desktop și aș dori o sincronizare cu aceasta",
|
||||
"sync-from-server": "Am deja o instanță de server și doresc o sincronizare cu aceasta",
|
||||
"title": "Inițializare"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"description": "Acești pași trebuie urmați de pe aplicația de desktop:",
|
||||
"heading": "Sincronizare cu aplicația desktop",
|
||||
"step1": "Deschideți aplicația Trilium Notes pentru desktop.",
|
||||
"step2": "Din meniul Trilium, dați clic pe Opțiuni.",
|
||||
"step3": "Clic pe categoria „Sincronizare”.",
|
||||
"step4": "Schimbați adresa server-ului către: {{- host}} și apăsați „Salvează”.",
|
||||
"step5": "Clic pe butonul „Testează sincronizarea” pentru a verifica dacă conexiunea a fost făcută cu succes.",
|
||||
"step6": "După ce ați completat pașii, dați click {{- link}}.",
|
||||
"step6-here": "aici"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"back": "Înapoi",
|
||||
"finish-setup": "Finalizează inițializarea",
|
||||
"heading": "Sincronizare cu server-ul",
|
||||
"instructions": "Introduceți adresa server-ului Trilium și credențialele în secțiunea de jos. Astfel se va descărca întregul document Trilium de pe server și se va configura sincronizarea cu acesta. În funcție de dimensiunea documentului și viteza rețelei, acest proces poate dura.",
|
||||
"note": "De remarcat:",
|
||||
"password": "Parolă",
|
||||
"proxy-instruction": "Dacă lăsați câmpul de proxy nesetat, proxy-ul de sistem va fi folosit (valabil doar pentru aplicația de desktop)",
|
||||
"proxy-server": "Server-ul proxy (opțional)",
|
||||
"proxy-server-placeholder": "https://<sistem>:<port>",
|
||||
"server-host": "Adresa server-ului Trilium",
|
||||
"server-host-placeholder": "https://<sistem>:<port>",
|
||||
"password-placeholder": "Parolă"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "Sincronizare în curs",
|
||||
"outstanding-items": "Elemente de sincronizat:",
|
||||
"outstanding-items-default": "-",
|
||||
"successful": "Sincronizarea a fost configurată cu succes. Poate dura ceva timp pentru a finaliza sincronizarea inițială. După efectuarea ei se va redirecționa către pagina de autentificare."
|
||||
},
|
||||
"share_404": {
|
||||
"heading": "Pagină negăsită",
|
||||
"title": "Pagină negăsită"
|
||||
},
|
||||
"share_page": {
|
||||
"child-notes": "Subnotițe:",
|
||||
"clipped-from": "Această notiță a fost decupată inițial de pe {{- url}}",
|
||||
"no-content": "Această notiță nu are conținut.",
|
||||
"parent": "părinte:"
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "Luni",
|
||||
"tuesday": "Marți",
|
||||
"wednesday": "Miercuri",
|
||||
"thursday": "Joi",
|
||||
"friday": "Vineri",
|
||||
"saturday": "Sâmbătă",
|
||||
"sunday": "Duminică"
|
||||
},
|
||||
"months": {
|
||||
"january": "Ianuarie",
|
||||
"february": "Februarie",
|
||||
"march": "Martie",
|
||||
"april": "Aprilie",
|
||||
"may": "Mai",
|
||||
"june": "Iunie",
|
||||
"july": "Iulie",
|
||||
"august": "August",
|
||||
"september": "Septembrie",
|
||||
"october": "Octombrie",
|
||||
"november": "Noiembrie",
|
||||
"december": "Decembrie"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "Căutare:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "Calea către serverul de sincronizare nu este configurată. Configurați sincronizarea înainte.",
|
||||
"successful": "Comunicarea cu serverul de sincronizare a avut loc cu succes, s-a început sincronizarea."
|
||||
},
|
||||
"hidden-subtree": {
|
||||
"advanced-title": "Setări avansate",
|
||||
"appearance-title": "Aspect",
|
||||
"available-launchers-title": "Lansatoare disponibile",
|
||||
"backup-title": "Copii de siguranță",
|
||||
"base-abstract-launcher-title": "Lansator abstract de bază",
|
||||
"bookmarks-title": "Semne de carte",
|
||||
"built-in-widget-title": "Widget predefinit",
|
||||
"bulk-action-title": "Acțiuni în masă",
|
||||
"calendar-title": "Calendar",
|
||||
"code-notes-title": "Notițe de cod",
|
||||
"command-launcher-title": "Lansator de comenzi",
|
||||
"custom-widget-title": "Widget personalizat",
|
||||
"etapi-title": "ETAPI",
|
||||
"go-to-previous-note-title": "Mergi la notița anterioară",
|
||||
"images-title": "Imagini",
|
||||
"launch-bar-title": "Bară de lansare",
|
||||
"new-note-title": "Notiță nouă",
|
||||
"note-launcher-title": "Lansator de notițe",
|
||||
"note-map-title": "Harta notițelor",
|
||||
"open-today-journal-note-title": "Deschide notița de astăzi",
|
||||
"options-title": "Opțiuni",
|
||||
"other": "Diverse",
|
||||
"password-title": "Parolă",
|
||||
"protected-session-title": "Sesiune protejată",
|
||||
"recent-changes-title": "Schimbări recente",
|
||||
"script-launcher-title": "Lansator de script-uri",
|
||||
"search-history-title": "Istoric de căutare",
|
||||
"settings-title": "Setări",
|
||||
"shared-notes-title": "Notițe partajate",
|
||||
"quick-search-title": "Căutare rapidă",
|
||||
"root-title": "Notițe ascunse",
|
||||
"search-notes-title": "Căutare notițe",
|
||||
"shortcuts-title": "Scurtături",
|
||||
"spellcheck-title": "Corectare gramaticală",
|
||||
"sync-status-title": "Starea sincronizării",
|
||||
"sync-title": "Sincronizare",
|
||||
"text-notes": "Notițe text",
|
||||
"user-hidden-title": "Definite de utilizator",
|
||||
"backend-log-title": "Log backend",
|
||||
"spacer-title": "Separator",
|
||||
"sql-console-history-title": "Istoricul consolei SQL",
|
||||
"go-to-next-note-title": "Mergi la notița următoare",
|
||||
"launch-bar-templates-title": "Șabloane bară de lansare",
|
||||
"visible-launchers-title": "Lansatoare vizibile",
|
||||
"user-guide": "Ghidul de utilizare",
|
||||
"jump-to-note-title": "Sari la...",
|
||||
"llm-chat-title": "Întreabă Notes",
|
||||
"multi-factor-authentication-title": "Autentificare multi-factor",
|
||||
"ai-llm-title": "AI/LLM",
|
||||
"localization": "Limbă și regiune",
|
||||
"inbox-title": "Inbox"
|
||||
},
|
||||
"notes": {
|
||||
"new-note": "Notiță nouă",
|
||||
"duplicate-note-suffix": "(dupl.)",
|
||||
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
|
||||
},
|
||||
"backend_log": {
|
||||
"log-does-not-exist": "Fișierul de loguri de backend „{{ fileName }}” nu există (încă).",
|
||||
"reading-log-failed": "Nu s-a putut citi fișierul de loguri de backend „{{fileName}}”."
|
||||
},
|
||||
"geo-map": {
|
||||
"create-child-note-instruction": "Clic pe hartă pentru a crea o nouă notiță la acea poziție sau apăsați Escape pentru a renunța."
|
||||
},
|
||||
"content_renderer": {
|
||||
"note-cannot-be-displayed": "Acest tip de notiță nu poate fi afișat."
|
||||
},
|
||||
"pdf": {
|
||||
"export_filter": "Document PDF (*.pdf)",
|
||||
"unable-to-export-message": "Notița curentă nu a putut fi exportată ca PDF.",
|
||||
"unable-to-export-title": "Nu s-a putut exporta ca PDF",
|
||||
"unable-to-save-message": "Nu s-a putut scrie fișierul selectat. Încercați din nou sau selectați altă destinație."
|
||||
},
|
||||
"tray": {
|
||||
"bookmarks": "Semne de carte",
|
||||
"close": "Închide Trilium",
|
||||
"new-note": "Notiță nouă",
|
||||
"recents": "Notițe recente",
|
||||
"today": "Mergi la notița de astăzi",
|
||||
"tooltip": "Trilium Notes",
|
||||
"show-windows": "Afișează ferestrele",
|
||||
"open_new_window": "Deschide fereastră nouă"
|
||||
},
|
||||
"migration": {
|
||||
"error_message": "Eroare la migrarea către versiunea {{version}}: {{stack}}",
|
||||
"old_version": "Nu se poate migra la ultima versiune direct de la versiunea dvs. Actualizați mai întâi la versiunea v0.60.4 și ulterior la această versiune.",
|
||||
"wrong_db_version": "Versiunea actuală a bazei de date ({{version}}) este mai nouă decât versiunea de bază de date suportată de aplicație ({{targetVersion}}), ceea ce înseamnă că a fost creată de către o versiune mai nouă de Trilium. Actualizați aplicația la ultima versiune pentru a putea continua."
|
||||
},
|
||||
"modals": {
|
||||
"error_title": "Eroare"
|
||||
},
|
||||
"keyboard_action_names": {
|
||||
"quick-search": "Căutare rapidă",
|
||||
"back-in-note-history": "Înapoi în istoricul notițelor",
|
||||
"forward-in-note-history": "Înainte în istoricul notițelor",
|
||||
"jump-to-note": "Mergi la...",
|
||||
"scroll-to-active-note": "Derulează la notița activă",
|
||||
"search-in-subtree": "Caută în subnotițe",
|
||||
"expand-subtree": "Expandează subnotițele",
|
||||
"collapse-tree": "Minimizează arborele de notițe",
|
||||
"collapse-subtree": "Ascunde subnotițele",
|
||||
"sort-child-notes": "Ordonează subnotițele",
|
||||
"create-note-after": "Crează notiță după",
|
||||
"create-note-into": "Crează subnotiță în",
|
||||
"create-note-into-inbox": "Crează notiță în inbox",
|
||||
"delete-notes": "Șterge notițe",
|
||||
"move-note-up": "Mută notița deasupra",
|
||||
"move-note-down": "Mută notița dedesubt",
|
||||
"move-note-up-in-hierarchy": "Mută notița mai sus în ierarhie",
|
||||
"move-note-down-in-hierarchy": "Mută notița mai jos în ierarhie",
|
||||
"edit-note-title": "Editează titlul notiței",
|
||||
"edit-branch-prefix": "Editează prefixul ramurii",
|
||||
"clone-notes-to": "Clonează notițele în",
|
||||
"move-notes-to": "Mută notițele în",
|
||||
"copy-notes-to-clipboard": "Copiază notițele în clipboard",
|
||||
"paste-notes-from-clipboard": "Lipește notițele din clipboard",
|
||||
"cut-notes-to-clipboard": "Decupează notițele în clipboard",
|
||||
"select-all-notes-in-parent": "Selectează toate notițele din părinte",
|
||||
"add-note-above-to-selection": "Adaugă notița de deasupra la selecție",
|
||||
"add-note-below-to-selection": "Adaugă notița de dedesubt la selecție",
|
||||
"duplicate-subtree": "Dublifică ierarhia",
|
||||
"open-new-tab": "Deschide în tab nou",
|
||||
"close-active-tab": "Închide tab-ul activ",
|
||||
"reopen-last-tab": "Redeschide ultimul tab",
|
||||
"activate-next-tab": "Activează tab-ul următorul",
|
||||
"activate-previous-tab": "Activează tab-ul anterior",
|
||||
"open-new-window": "Deschide în fereastră nouă",
|
||||
"toggle-system-tray-icon": "Afișează/ascunde iconița din bara de sistem",
|
||||
"toggle-zen-mode": "Activează/dezactivează modul zen",
|
||||
"switch-to-first-tab": "Mergi la primul tab",
|
||||
"switch-to-second-tab": "Mergi la al doilea tab",
|
||||
"switch-to-third-tab": "Mergi la al treilea tab",
|
||||
"switch-to-fourth-tab": "Mergi la al patrulea tab",
|
||||
"switch-to-fifth-tab": "Mergi la al cincelea tab",
|
||||
"switch-to-sixth-tab": "Mergi la al șaselea tab",
|
||||
"switch-to-seventh-tab": "Mergi la al șaptelea tab",
|
||||
"switch-to-eighth-tab": "Mergi la al optelea tab",
|
||||
"switch-to-ninth-tab": "Mergi la al nouălea tab",
|
||||
"switch-to-last-tab": "Mergi la ultimul tab",
|
||||
"show-note-source": "Afișează sursa notiței",
|
||||
"show-options": "Afișează opțiunile",
|
||||
"show-revisions": "Afișează reviziile",
|
||||
"show-recent-changes": "Afișează modificările recente",
|
||||
"show-sql-console": "Afișează consola SQL",
|
||||
"show-backend-log": "Afișează log-urile din backend",
|
||||
"show-help": "Afișează ghidul",
|
||||
"show-cheatsheet": "Afișează ghidul rapid",
|
||||
"add-link-to-text": "Inserează o legătură în text",
|
||||
"follow-link-under-cursor": "Urmează legătura de la poziția curentă",
|
||||
"insert-date-and-time-to-text": "Inserează data și timpul în text",
|
||||
"paste-markdown-into-text": "Lipește Markdown în text",
|
||||
"cut-into-note": "Decupează în subnotiță",
|
||||
"add-include-note-to-text": "Adaugă o includere de notiță în text",
|
||||
"edit-read-only-note": "Editează notiță ce este în modul citire",
|
||||
"add-new-label": "Adaugă o nouă etichetă",
|
||||
"add-new-relation": "Adaugă o nouă relație",
|
||||
"toggle-ribbon-tab-classic-editor": "Comută la tab-ul de panglică pentru formatare text",
|
||||
"toggle-ribbon-tab-basic-properties": "Comută la tab-ul de panglică pentru proprietăți de bază",
|
||||
"toggle-ribbon-tab-book-properties": "Comută la tab-ul de panglică pentru proprietăți colecție",
|
||||
"toggle-ribbon-tab-file-properties": "Comută la tab-ul de panglică pentru proprietăți fișier",
|
||||
"toggle-ribbon-tab-image-properties": "Comută la tab-ul de panglică pentru proprietăți imagini",
|
||||
"toggle-ribbon-tab-owned-attributes": "Comută la tab-ul de panglică pentru atribute proprii",
|
||||
"toggle-ribbon-tab-inherited-attributes": "Comută la tab-ul de panglică pentru atribute moștenite",
|
||||
"toggle-ribbon-tab-promoted-attributes": "Comută la tab-ul de panglică pentru atribute promovate",
|
||||
"toggle-ribbon-tab-note-map": "Comută la tab-ul de panglică pentru harta notiței",
|
||||
"toggle-ribbon-tab-note-info": "Comută la tab-ul de panglică pentru informații despre notiță",
|
||||
"toggle-ribbon-tab-note-paths": "Comută la tab-ul de panglică pentru căile notiței",
|
||||
"toggle-ribbon-tab-similar-notes": "Comută la tab-ul de panglică pentru notițe similare",
|
||||
"toggle-right-pane": "Comută panoul din dreapta",
|
||||
"print-active-note": "Imprimă notița activă",
|
||||
"export-active-note-as-pdf": "Exportă notița activă ca PDF",
|
||||
"open-note-externally": "Deschide notița într-o aplicație externă",
|
||||
"render-active-note": "Randează notița activă",
|
||||
"run-active-note": "Rulează notița activă",
|
||||
"toggle-note-hoisting": "Comută focalizarea notiței",
|
||||
"unhoist-note": "Defocalizează notița",
|
||||
"reload-frontend-app": "Reîmprospătează aplicația",
|
||||
"open-developer-tools": "Deschide unelete de dezvoltare",
|
||||
"find-in-text": "Caută în text",
|
||||
"toggle-left-pane": "Comută panoul din stânga",
|
||||
"toggle-full-screen": "Comută mod pe tot ecranul",
|
||||
"zoom-out": "Micșorare",
|
||||
"zoom-in": "Mărire",
|
||||
"reset-zoom-level": "Resetează nivelul de zoom",
|
||||
"copy-without-formatting": "Copiază fără formatare",
|
||||
"force-save-revision": "Forțează salvarea unei revizii",
|
||||
"command-palette": "Paleta de comenzi"
|
||||
},
|
||||
"weekdayNumber": "Săptămâna {weekNumber}",
|
||||
"quarterNumber": "Trimestrul {quarterNumber}",
|
||||
"share_theme": {
|
||||
"site-theme": "Tema site-ului",
|
||||
"search_placeholder": "Caută...",
|
||||
"image_alt": "Imaginea articolului",
|
||||
"last-updated": "Ultima actualizare: {{- date}}",
|
||||
"subpages": "Subpagini:",
|
||||
"on-this-page": "Pe această pagină",
|
||||
"expand": "Expandează"
|
||||
},
|
||||
"hidden_subtree_templates": {
|
||||
"text-snippet": "Fragment de text",
|
||||
"description": "Descriere",
|
||||
"list-view": "Mod listă",
|
||||
"grid-view": "Mod grilă",
|
||||
"calendar": "Calendar",
|
||||
"table": "Tabel",
|
||||
"geo-map": "Hartă geografică",
|
||||
"start-date": "Dată de început",
|
||||
"end-date": "Dată de sfârșit",
|
||||
"start-time": "Timp de început",
|
||||
"end-time": "Timp de sfârșit",
|
||||
"geolocation": "Geolocație",
|
||||
"built-in-templates": "Șabloane predefinite",
|
||||
"board": "Tablă Kanban",
|
||||
"status": "Stare",
|
||||
"board_note_first": "Prima notiță",
|
||||
"board_note_second": "A doua notiță",
|
||||
"board_note_third": "A treia notiță",
|
||||
"board_status_todo": "De făcut",
|
||||
"board_status_progress": "În lucru",
|
||||
"board_status_done": "Finalizat"
|
||||
}
|
||||
}
|
||||
|
||||
1
apps/server/src/assets/translations/ru/server.json
Normal file
1
apps/server/src/assets/translations/ru/server.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
1
apps/server/src/assets/translations/sr/server.json
Normal file
1
apps/server/src/assets/translations/sr/server.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
1
apps/server/src/assets/translations/tr/server.json
Normal file
1
apps/server/src/assets/translations/tr/server.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -1,192 +1,192 @@
|
||||
{
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "打開「跳轉到筆記」對話框",
|
||||
"search-in-subtree": "在當前筆記的子樹中搜索筆記",
|
||||
"expand-subtree": "展開當前筆記的子樹",
|
||||
"collapse-tree": "折疊完整的筆記樹",
|
||||
"collapse-subtree": "折疊當前筆記的子樹",
|
||||
"sort-child-notes": "排序子筆記",
|
||||
"creating-and-moving-notes": "新增和移動筆記",
|
||||
"create-note-into-inbox": "在收件匣(如果有定義的話)或日記中新增筆記",
|
||||
"delete-note": "刪除筆記",
|
||||
"move-note-up": "上移筆記",
|
||||
"move-note-down": "下移筆記",
|
||||
"move-note-up-in-hierarchy": "上移筆記層級",
|
||||
"move-note-down-in-hierarchy": "下移筆記層級",
|
||||
"edit-note-title": "從筆記樹跳轉到筆記詳情並編輯標題",
|
||||
"edit-branch-prefix": "顯示編輯分支前綴對話框",
|
||||
"note-clipboard": "筆記剪貼簿",
|
||||
"copy-notes-to-clipboard": "複製選定的筆記到剪貼簿",
|
||||
"paste-notes-from-clipboard": "從剪貼簿粘貼筆記到活動筆記中",
|
||||
"cut-notes-to-clipboard": "剪下選定的筆記到剪貼簿",
|
||||
"select-all-notes-in-parent": "選擇當前筆記級別的所有筆記",
|
||||
"add-note-above-to-the-selection": "將上方筆記添加到選擇中",
|
||||
"add-note-below-to-selection": "將下方筆記添加到選擇中",
|
||||
"duplicate-subtree": "複製子樹",
|
||||
"tabs-and-windows": "標籤和窗口",
|
||||
"open-new-tab": "打開新標籤",
|
||||
"close-active-tab": "關閉活動標籤",
|
||||
"reopen-last-tab": "重新打開最後關閉的標籤",
|
||||
"activate-next-tab": "激活右側標籤",
|
||||
"activate-previous-tab": "激活左側標籤",
|
||||
"open-new-window": "打開新空白窗口",
|
||||
"toggle-tray": "顯示/隱藏應用程式的系統托盤",
|
||||
"first-tab": "激活列表中的第一個標籤",
|
||||
"second-tab": "激活列表中的第二個標籤",
|
||||
"third-tab": "激活列表中的第三個標籤",
|
||||
"fourth-tab": "激活列表中的第四個標籤",
|
||||
"fifth-tab": "激活列表中的第五個標籤",
|
||||
"sixth-tab": "激活列表中的第六個標籤",
|
||||
"seventh-tab": "激活列表中的第七個標籤",
|
||||
"eight-tab": "激活列表中的第八個標籤",
|
||||
"ninth-tab": "激活列表中的第九個標籤",
|
||||
"last-tab": "激活列表中的最後一個標籤",
|
||||
"dialogs": "對話框",
|
||||
"show-note-source": "顯示筆記源對話框",
|
||||
"show-options": "顯示選項對話框",
|
||||
"show-revisions": "顯示筆記歷史對話框",
|
||||
"show-recent-changes": "顯示最近更改對話框",
|
||||
"show-sql-console": "顯示SQL控制台對話框",
|
||||
"show-backend-log": "顯示後端日誌對話框",
|
||||
"text-note-operations": "文本筆記操作",
|
||||
"add-link-to-text": "打開對話框以將鏈接添加到文本",
|
||||
"follow-link-under-cursor": "跟隨遊標下的鏈接",
|
||||
"insert-date-and-time-to-text": "將當前日期和時間插入文本",
|
||||
"paste-markdown-into-text": "將剪貼簿中的Markdown粘貼到文本筆記中",
|
||||
"cut-into-note": "從當前筆記中剪下選擇並新增包含選定文本的子筆記",
|
||||
"add-include-note-to-text": "打開對話框以包含筆記",
|
||||
"edit-readonly-note": "編輯唯讀筆記",
|
||||
"attributes-labels-and-relations": "屬性(標籤和關係)",
|
||||
"add-new-label": "新增新標籤",
|
||||
"create-new-relation": "新增新關係",
|
||||
"ribbon-tabs": "功能區標籤",
|
||||
"toggle-basic-properties": "切換基本屬性",
|
||||
"toggle-file-properties": "切換文件屬性",
|
||||
"toggle-image-properties": "切換圖像屬性",
|
||||
"toggle-owned-attributes": "切換擁有的屬性",
|
||||
"toggle-inherited-attributes": "切換繼承的屬性",
|
||||
"toggle-promoted-attributes": "切換提升的屬性",
|
||||
"toggle-link-map": "切換鏈接地圖",
|
||||
"toggle-note-info": "切換筆記資訊",
|
||||
"toggle-note-paths": "切換筆記路徑",
|
||||
"toggle-similar-notes": "切換相似筆記",
|
||||
"other": "其他",
|
||||
"toggle-right-pane": "切換右側面板的顯示,包括目錄和高亮",
|
||||
"print-active-note": "打印活動筆記",
|
||||
"open-note-externally": "以預設應用程式打開筆記文件",
|
||||
"render-active-note": "渲染(重新渲染)活動筆記",
|
||||
"run-active-note": "運行主動的JavaScript(前端/後端)代碼筆記",
|
||||
"toggle-note-hoisting": "切換活動筆記的提升",
|
||||
"unhoist": "從任何地方取消提升",
|
||||
"reload-frontend-app": "重新加載前端應用",
|
||||
"open-dev-tools": "打開開發工具",
|
||||
"toggle-left-note-tree-panel": "切換左側(筆記樹)面板",
|
||||
"toggle-full-screen": "切換全熒幕",
|
||||
"zoom-out": "縮小",
|
||||
"zoom-in": "放大",
|
||||
"note-navigation": "筆記導航",
|
||||
"reset-zoom-level": "重置縮放級別",
|
||||
"copy-without-formatting": "複製不帶格式的選定文本",
|
||||
"force-save-revision": "強制新增/保存當前筆記的歷史版本",
|
||||
"show-help": "顯示內置說明/備忘單",
|
||||
"toggle-book-properties": "切換書籍屬性"
|
||||
},
|
||||
"login": {
|
||||
"title": "登入",
|
||||
"heading": "Trilium登入",
|
||||
"incorrect-password": "密碼不正確。請再試一次。",
|
||||
"password": "密碼",
|
||||
"remember-me": "記住我",
|
||||
"button": "登入"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "設定密碼",
|
||||
"heading": "設定密碼",
|
||||
"description": "在您可以從Web開始使用Trilium之前,您需要先設定一個密碼。然後您將使用此密碼登錄。",
|
||||
"password": "密碼",
|
||||
"password-confirmation": "密碼確認",
|
||||
"button": "設定密碼"
|
||||
},
|
||||
"javascript-required": "Trilium需要啓用JavaScript。",
|
||||
"setup": {
|
||||
"heading": "TriliumNext筆記設定",
|
||||
"new-document": "我是新用戶,我想為我的筆記新增一個新的Trilium檔案",
|
||||
"sync-from-desktop": "我已經有一個桌面實例,我想設定與它的同步",
|
||||
"sync-from-server": "我已經有一個伺服器實例,我想設定與它的同步",
|
||||
"next": "下一步",
|
||||
"init-in-progress": "檔案初始化進行中",
|
||||
"redirecting": "您將很快被重定向到應用程式。",
|
||||
"title": "設定"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "從桌面同步",
|
||||
"description": "此設定需要從桌面實例啓動:",
|
||||
"step1": "打開您的TriliumNext筆記桌面實例。",
|
||||
"step2": "從Trilium菜單中,點擊選項。",
|
||||
"step3": "點擊同步。",
|
||||
"step4": "將伺服器實例地址更改為:{{- host}}並點擊保存。",
|
||||
"step5": "點擊「測試同步」按鈕以驗證連接是否成功。",
|
||||
"step6": "完成這些步驟後,點擊{{- link}}。",
|
||||
"step6-here": "這裡"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "從伺服器同步",
|
||||
"instructions": "請在下面輸入Trilium伺服器地址和密碼。這將從伺服器下載整個Trilium數據庫檔案並設定同步。因應數據庫大小和您的連接速度,這可能需要一段時間。",
|
||||
"server-host": "Trilium伺服器地址",
|
||||
"server-host-placeholder": "https://<主機名稱>:<端口>",
|
||||
"proxy-server": "代理伺服器(可選)",
|
||||
"proxy-server-placeholder": "https://<主機名稱>:<端口>",
|
||||
"note": "注意:",
|
||||
"proxy-instruction": "如果您將代理設定留空,將使用系統代理(僅適用於桌面程式)",
|
||||
"password": "密碼",
|
||||
"password-placeholder": "密碼",
|
||||
"back": "返回",
|
||||
"finish-setup": "完成設定"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "同步中",
|
||||
"successful": "同步已正確設定。初始同步完成可能需要一些時間。完成後,您將被重定向到登入頁面。",
|
||||
"outstanding-items": "未完成的同步項目:",
|
||||
"outstanding-items-default": "無"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "未找到",
|
||||
"heading": "未找到"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "上級目錄:",
|
||||
"clipped-from": "此筆記最初剪下自 {{- url}}",
|
||||
"child-notes": "子筆記:",
|
||||
"no-content": "此筆記沒有內容。"
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "週一",
|
||||
"tuesday": "週二",
|
||||
"wednesday": "週三",
|
||||
"thursday": "週四",
|
||||
"friday": "週五",
|
||||
"saturday": "週六",
|
||||
"sunday": "週日"
|
||||
},
|
||||
"months": {
|
||||
"january": "一月",
|
||||
"february": "二月",
|
||||
"march": "三月",
|
||||
"april": "四月",
|
||||
"may": "五月",
|
||||
"june": "六月",
|
||||
"july": "七月",
|
||||
"august": "八月",
|
||||
"september": "九月",
|
||||
"october": "十月",
|
||||
"november": "十一月",
|
||||
"december": "十二月"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "搜尋:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "並未設定同步伺服器主機,請先設定同步",
|
||||
"successful": "成功與同步伺服器握手,現在開始同步"
|
||||
}
|
||||
"keyboard_actions": {
|
||||
"open-jump-to-note-dialog": "打開「跳轉到筆記」對話框",
|
||||
"search-in-subtree": "在當前筆記的子樹中搜索筆記",
|
||||
"expand-subtree": "展開當前筆記的子樹",
|
||||
"collapse-tree": "折疊完整的筆記樹",
|
||||
"collapse-subtree": "折疊當前筆記的子樹",
|
||||
"sort-child-notes": "排序子筆記",
|
||||
"creating-and-moving-notes": "新增和移動筆記",
|
||||
"create-note-into-inbox": "在收件匣(如果有定義的話)或日記中新增筆記",
|
||||
"delete-note": "刪除筆記",
|
||||
"move-note-up": "上移筆記",
|
||||
"move-note-down": "下移筆記",
|
||||
"move-note-up-in-hierarchy": "上移筆記層級",
|
||||
"move-note-down-in-hierarchy": "下移筆記層級",
|
||||
"edit-note-title": "從筆記樹跳轉到筆記詳情並編輯標題",
|
||||
"edit-branch-prefix": "顯示編輯分支前綴對話框",
|
||||
"note-clipboard": "筆記剪貼簿",
|
||||
"copy-notes-to-clipboard": "複製選定的筆記到剪貼簿",
|
||||
"paste-notes-from-clipboard": "從剪貼簿粘貼筆記到活動筆記中",
|
||||
"cut-notes-to-clipboard": "剪下選定的筆記到剪貼簿",
|
||||
"select-all-notes-in-parent": "選擇當前筆記級別的所有筆記",
|
||||
"add-note-above-to-the-selection": "將上方筆記添加到選擇中",
|
||||
"add-note-below-to-selection": "將下方筆記添加到選擇中",
|
||||
"duplicate-subtree": "複製子樹",
|
||||
"tabs-and-windows": "標籤和窗口",
|
||||
"open-new-tab": "打開新標籤",
|
||||
"close-active-tab": "關閉活動標籤",
|
||||
"reopen-last-tab": "重新打開最後關閉的標籤",
|
||||
"activate-next-tab": "激活右側標籤",
|
||||
"activate-previous-tab": "激活左側標籤",
|
||||
"open-new-window": "打開新空白窗口",
|
||||
"toggle-tray": "顯示/隱藏應用程式的系統托盤",
|
||||
"first-tab": "激活列表中的第一個標籤",
|
||||
"second-tab": "激活列表中的第二個標籤",
|
||||
"third-tab": "激活列表中的第三個標籤",
|
||||
"fourth-tab": "激活列表中的第四個標籤",
|
||||
"fifth-tab": "激活列表中的第五個標籤",
|
||||
"sixth-tab": "激活列表中的第六個標籤",
|
||||
"seventh-tab": "激活列表中的第七個標籤",
|
||||
"eight-tab": "激活列表中的第八個標籤",
|
||||
"ninth-tab": "激活列表中的第九個標籤",
|
||||
"last-tab": "激活列表中的最後一個標籤",
|
||||
"dialogs": "對話框",
|
||||
"show-note-source": "顯示筆記源對話框",
|
||||
"show-options": "顯示選項對話框",
|
||||
"show-revisions": "顯示筆記歷史對話框",
|
||||
"show-recent-changes": "顯示最近更改對話框",
|
||||
"show-sql-console": "顯示SQL控制台對話框",
|
||||
"show-backend-log": "顯示後端日誌對話框",
|
||||
"text-note-operations": "文本筆記操作",
|
||||
"add-link-to-text": "打開對話框以將鏈接添加到文本",
|
||||
"follow-link-under-cursor": "跟隨遊標下的鏈接",
|
||||
"insert-date-and-time-to-text": "將當前日期和時間插入文本",
|
||||
"paste-markdown-into-text": "將剪貼簿中的Markdown粘貼到文本筆記中",
|
||||
"cut-into-note": "從當前筆記中剪下選擇並新增包含選定文本的子筆記",
|
||||
"add-include-note-to-text": "打開對話框以包含筆記",
|
||||
"edit-readonly-note": "編輯唯讀筆記",
|
||||
"attributes-labels-and-relations": "屬性(標籤和關係)",
|
||||
"add-new-label": "新增新標籤",
|
||||
"create-new-relation": "新增新關係",
|
||||
"ribbon-tabs": "功能區標籤",
|
||||
"toggle-basic-properties": "切換基本屬性",
|
||||
"toggle-file-properties": "切換文件屬性",
|
||||
"toggle-image-properties": "切換圖像屬性",
|
||||
"toggle-owned-attributes": "切換擁有的屬性",
|
||||
"toggle-inherited-attributes": "切換繼承的屬性",
|
||||
"toggle-promoted-attributes": "切換提升的屬性",
|
||||
"toggle-link-map": "切換鏈接地圖",
|
||||
"toggle-note-info": "切換筆記資訊",
|
||||
"toggle-note-paths": "切換筆記路徑",
|
||||
"toggle-similar-notes": "切換相似筆記",
|
||||
"other": "其他",
|
||||
"toggle-right-pane": "切換右側面板的顯示,包括目錄和高亮",
|
||||
"print-active-note": "打印活動筆記",
|
||||
"open-note-externally": "以預設應用程式打開筆記文件",
|
||||
"render-active-note": "渲染(重新渲染)活動筆記",
|
||||
"run-active-note": "運行主動的JavaScript(前端/後端)代碼筆記",
|
||||
"toggle-note-hoisting": "切換活動筆記的提升",
|
||||
"unhoist": "從任何地方取消提升",
|
||||
"reload-frontend-app": "重新加載前端應用",
|
||||
"open-dev-tools": "打開開發工具",
|
||||
"toggle-left-note-tree-panel": "切換左側(筆記樹)面板",
|
||||
"toggle-full-screen": "切換全熒幕",
|
||||
"zoom-out": "縮小",
|
||||
"zoom-in": "放大",
|
||||
"note-navigation": "筆記導航",
|
||||
"reset-zoom-level": "重置縮放級別",
|
||||
"copy-without-formatting": "複製不帶格式的選定文本",
|
||||
"force-save-revision": "強制新增/保存當前筆記的歷史版本",
|
||||
"show-help": "顯示內置說明/備忘單",
|
||||
"toggle-book-properties": "切換書籍屬性"
|
||||
},
|
||||
"login": {
|
||||
"title": "登入",
|
||||
"heading": "Trilium登入",
|
||||
"incorrect-password": "密碼不正確。請再試一次。",
|
||||
"password": "密碼",
|
||||
"remember-me": "記住我",
|
||||
"button": "登入"
|
||||
},
|
||||
"set_password": {
|
||||
"title": "設定密碼",
|
||||
"heading": "設定密碼",
|
||||
"description": "在您可以從Web開始使用Trilium之前,您需要先設定一個密碼。然後您將使用此密碼登錄。",
|
||||
"password": "密碼",
|
||||
"password-confirmation": "密碼確認",
|
||||
"button": "設定密碼"
|
||||
},
|
||||
"javascript-required": "Trilium需要啓用JavaScript。",
|
||||
"setup": {
|
||||
"heading": "TriliumNext筆記設定",
|
||||
"new-document": "我是新用戶,我想為我的筆記新增一個新的Trilium檔案",
|
||||
"sync-from-desktop": "我已經有一個桌面實例,我想設定與它的同步",
|
||||
"sync-from-server": "我已經有一個伺服器實例,我想設定與它的同步",
|
||||
"next": "下一步",
|
||||
"init-in-progress": "檔案初始化進行中",
|
||||
"redirecting": "您將很快被重定向到應用程式。",
|
||||
"title": "設定"
|
||||
},
|
||||
"setup_sync-from-desktop": {
|
||||
"heading": "從桌面同步",
|
||||
"description": "此設定需要從桌面實例啓動:",
|
||||
"step1": "打開您的TriliumNext筆記桌面實例。",
|
||||
"step2": "從Trilium菜單中,點擊選項。",
|
||||
"step3": "點擊同步。",
|
||||
"step4": "將伺服器實例地址更改為:{{- host}}並點擊保存。",
|
||||
"step5": "點擊「測試同步」按鈕以驗證連接是否成功。",
|
||||
"step6": "完成這些步驟後,點擊{{- link}}。",
|
||||
"step6-here": "這裡"
|
||||
},
|
||||
"setup_sync-from-server": {
|
||||
"heading": "從伺服器同步",
|
||||
"instructions": "請在下面輸入Trilium伺服器地址和密碼。這將從伺服器下載整個Trilium數據庫檔案並設定同步。因應數據庫大小和您的連接速度,這可能需要一段時間。",
|
||||
"server-host": "Trilium伺服器地址",
|
||||
"server-host-placeholder": "https://<主機名稱>:<端口>",
|
||||
"proxy-server": "代理伺服器(可選)",
|
||||
"proxy-server-placeholder": "https://<主機名稱>:<端口>",
|
||||
"note": "注意:",
|
||||
"proxy-instruction": "如果您將代理設定留空,將使用系統代理(僅適用於桌面程式)",
|
||||
"password": "密碼",
|
||||
"password-placeholder": "密碼",
|
||||
"back": "返回",
|
||||
"finish-setup": "完成設定"
|
||||
},
|
||||
"setup_sync-in-progress": {
|
||||
"heading": "同步中",
|
||||
"successful": "同步已正確設定。初始同步完成可能需要一些時間。完成後,您將被重定向到登入頁面。",
|
||||
"outstanding-items": "未完成的同步項目:",
|
||||
"outstanding-items-default": "無"
|
||||
},
|
||||
"share_404": {
|
||||
"title": "未找到",
|
||||
"heading": "未找到"
|
||||
},
|
||||
"share_page": {
|
||||
"parent": "上級目錄:",
|
||||
"clipped-from": "此筆記最初剪下自 {{- url}}",
|
||||
"child-notes": "子筆記:",
|
||||
"no-content": "此筆記沒有內容。"
|
||||
},
|
||||
"weekdays": {
|
||||
"monday": "週一",
|
||||
"tuesday": "週二",
|
||||
"wednesday": "週三",
|
||||
"thursday": "週四",
|
||||
"friday": "週五",
|
||||
"saturday": "週六",
|
||||
"sunday": "週日"
|
||||
},
|
||||
"months": {
|
||||
"january": "一月",
|
||||
"february": "二月",
|
||||
"march": "三月",
|
||||
"april": "四月",
|
||||
"may": "五月",
|
||||
"june": "六月",
|
||||
"july": "七月",
|
||||
"august": "八月",
|
||||
"september": "九月",
|
||||
"october": "十月",
|
||||
"november": "十一月",
|
||||
"december": "十二月"
|
||||
},
|
||||
"special_notes": {
|
||||
"search_prefix": "搜尋:"
|
||||
},
|
||||
"test_sync": {
|
||||
"not-configured": "並未設定同步伺服器主機,請先設定同步",
|
||||
"successful": "成功與同步伺服器握手,現在開始同步"
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user