diff --git a/.github/workflows/main-docker-alpine.yml b/.github/workflows/main-docker-alpine.yml index 3f9e24eea..cbf195c86 100644 --- a/.github/workflows/main-docker-alpine.yml +++ b/.github/workflows/main-docker-alpine.yml @@ -1,14 +1,4 @@ on: - push: - branches: - - "develop" - - "feature/update**" - - "feature/server_esm**" - paths-ignore: - - "docs/**" - - "bin/**" - tags: - - "v*" workflow_dispatch: env: diff --git a/.github/workflows/main-docker.yml b/.github/workflows/main-docker.yml index 52421b969..70a3592b1 100644 --- a/.github/workflows/main-docker.yml +++ b/.github/workflows/main-docker.yml @@ -16,7 +16,7 @@ env: DOCKERHUB_REGISTRY: docker.io IMAGE_NAME: ${{ github.repository }} TEST_TAG: triliumnext/notes:test - PLATFORMS: linux/arm64,linux/arm/v7 + PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7 jobs: test_docker: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 85e8e7590..2628a4ebb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,14 +2,11 @@ name: Main on: push: branches: - - "develop" - "feature/update**" - "feature/server_esm**" paths-ignore: - "docs/**" - ".github/workflows/main-docker.yml" - tags: - - "v*" workflow_dispatch: concurrency: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..0e1239625 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,95 @@ +name: Release +on: + push: + tags: + - "v*" + workflow_dispatch: +permissions: + contents: write +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + make-electron: + name: Make Electron + strategy: + fail-fast: false + matrix: + arch: [x64, arm64] + os: + - name: macos + image: macos-latest + extension: dmg + - name: linux + image: ubuntu-latest + extension: deb + - name: windows + image: windows-latest + extension: exe + runs-on: ${{ matrix.os.image }} + steps: + - uses: actions/checkout@v4 + - name: Set up node & dependencies + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Set up Python for appdmg to be installed + if: ${{ matrix.os.name == 'macos' }} + run: brew install python-setuptools + - name: Install dependencies + run: npm ci + - name: Update build info + run: npm run update-build-info + - name: Run electron-forge + run: npm run make-electron -- --arch=${{ matrix.arch }} + - name: Prepare artifacts (Unix) + if: runner.os != 'windows' + run: | + mkdir -p upload + file=$(find out/make -name '*.zip' -print -quit) + cp "$file" "upload/TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}.zip" + file=$(find out/make -name '*.${{ matrix.os.extension }}' -print -quit) + cp "$file" "upload/TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}" + - name: Prepare artifacts (Windows) + if: runner.os == 'windows' + run: | + mkdir upload + $file = Get-ChildItem -Path out/make -Filter '*.zip' -Recurse | Select-Object -First 1 + Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}.zip" + $file = Get-ChildItem -Path out/make -Filter '*.${{ matrix.os.extension }}' -Recurse | Select-Object -First 1 + Copy-Item -Path $file.FullName -Destination "upload/TriliumNextNotes-${{ github.ref_name }}-${{ matrix.os.name }}-${{ matrix.arch }}.${{ matrix.os.extension }}" + - name: Publish release + uses: softprops/action-gh-release@v2 + with: + draft: true + fail_on_unmatched_files: true + files: upload/*.* + build_linux_server-x64: + name: Build Linux Server x86_64 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up node & dependencies + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "npm" + - name: Install dependencies + run: npm ci + - name: Run Linux server build (x86_64) + run: | + npm run update-build-info + ./bin/build-server.sh + - name: Prepare artifacts + if: runner.os != 'windows' + run: | + mkdir -p upload + file=$(find dist -name '*.tar.xz' -print -quit) + cp "$file" "upload/TriliumNextNotes-${{ github.ref_name }}-server-linux-x64.tar.xz" + - name: Publish release + uses: softprops/action-gh-release@v2 + with: + draft: true + fail_on_unmatched_files: true + files: upload/*.* \ No newline at end of file diff --git a/.vscode/i18n-ally-custom-framework.yml b/.vscode/i18n-ally-custom-framework.yml index 539a82b45..32ec786aa 100644 --- a/.vscode/i18n-ally-custom-framework.yml +++ b/.vscode/i18n-ally-custom-framework.yml @@ -3,6 +3,7 @@ languageIds: - javascript - typescript + - html # An array of RegExes to find the key usage. **The key should be captured in the first match group**. # You should unescape RegEx strings in order to fit in the YAML file @@ -25,6 +26,7 @@ scopeRangeRegex: "useTranslation\\(\\s*\\[?\\s*['\"`](.*?)['\"`]" refactorTemplates: - t("$1") - ${t("$1")} + - <%= t("$1") %> # If set to true, only enables this custom framework (will disable all built-in frameworks) diff --git a/.vscode/settings.json b/.vscode/settings.json index a2203a03b..805d4b906 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,7 +6,8 @@ "i18n-ally.sourceLanguage": "en", "i18n-ally.keystyle": "nested", "i18n-ally.localesPaths": [ - "./src/public/translations" + "./src/public/translations", + "./translations" ], "[jsonc]": { "editor.defaultFormatter": "vscode.json-language-features" diff --git a/README.es.md b/README.es.md index c470ca01d..127a7bfe0 100644 --- a/README.es.md +++ b/README.es.md @@ -18,7 +18,7 @@ No hay pasos de migración especiales para migrar de una instancia de zadam/Tril ## 💬 Discuta con nosotros -Siéntase libre de unirse a nuestras conversaciones oficiales. ¡Nos encantaría escuchar de las características, sugerencias o problemas pueda tener! +Siéntase libre de unirse a nuestras conversaciones oficiales. ¡Nos encantaría escuchar de las características, sugerencias o problemas que pueda tener! - [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (Para discusiones síncronas) - La sala `General` es replicada a [XMPP](xmpp:discuss@trilium.thisgreat.party?join) diff --git a/bin/build-server.sh b/bin/build-server.sh index a0357b046..be00cf592 100755 --- a/bin/build-server.sh +++ b/bin/build-server.sh @@ -22,7 +22,7 @@ rm -r $PKG_DIR/node/lib/node_modules/npm rm -r $PKG_DIR/node/include/node rm -r $PKG_DIR/node_modules/electron* -rm -r $PKG_DIR/electron.js +rm -r $PKG_DIR/electron*.js printf "#!/bin/sh\n./node/bin/node src/www" > $PKG_DIR/trilium.sh chmod 755 $PKG_DIR/trilium.sh diff --git a/bin/copy-trilium.sh b/bin/copy-trilium.sh index 25b881dd7..ca2acf674 100755 --- a/bin/copy-trilium.sh +++ b/bin/copy-trilium.sh @@ -37,11 +37,11 @@ for f in 'package.json' 'package-lock.json' 'README.md' 'LICENSE' 'config-sample done # Patch package.json main -sed -i 's/.\/dist\/electron.js/electron.js/g' "$DIR/package.json" +sed -i 's/.\/dist\/electron-main.js/electron-main.js/g' "$DIR/package.json" script_dir=$(realpath $(dirname $0)) cp -R "$script_dir/../build/src" "$DIR" -cp "$script_dir/../build/electron.js" "$DIR" +cp "$script_dir/../build/electron-main.js" "$DIR" # run in subshell (so we return to original dir) (cd $DIR && npm install --omit=dev) diff --git a/bin/release.sh b/bin/release.sh index 30dd0c462..fea1e030f 100755 --- a/bin/release.sh +++ b/bin/release.sh @@ -47,35 +47,3 @@ echo "Tagging commit with $TAG" git tag $TAG git push origin $TAG - -bin/build.sh - -LINUX_X64_BUILD=trilium-linux-x64-$VERSION.tar.xz -DEBIAN_X64_BUILD=trilium_${VERSION}_amd64.deb -WINDOWS_X64_BUILD=trilium-windows-x64-$VERSION.zip -MAC_X64_BUILD=trilium-mac-x64-$VERSION.zip -MAC_ARM64_BUILD=trilium-mac-arm64-$VERSION.zip -SERVER_BUILD=trilium-linux-x64-server-$VERSION.tar.xz - -echo "Creating release in GitHub" - -EXTRA= - -if [[ $TAG == *"beta"* ]]; then - EXTRA=--prerelease -fi - -if [ ! -z "$GITHUB_CLI_AUTH_TOKEN" ]; then - echo "$GITHUB_CLI_AUTH_TOKEN" | gh auth login --with-token -fi - -gh release create -d "$TAG" \ - --title "$TAG release" \ - --notes "" \ - $EXTRA \ - "dist/$DEBIAN_X64_BUILD" \ - "dist/$LINUX_X64_BUILD" \ - "dist/$WINDOWS_X64_BUILD" \ - "dist/$MAC_X64_BUILD" \ - "dist/$MAC_ARM64_BUILD" \ - "dist/$SERVER_BUILD" diff --git a/electron-main.ts b/electron-main.ts new file mode 100644 index 000000000..6fff6ae6b --- /dev/null +++ b/electron-main.ts @@ -0,0 +1,4 @@ +import { initializeTranslations } from "./src/services/i18n.js"; + +await initializeTranslations(); +await import("./electron.js") \ No newline at end of file diff --git a/electron.ts b/electron.ts index 0f0e03cd0..6ebf9a9cf 100644 --- a/electron.ts +++ b/electron.ts @@ -70,4 +70,4 @@ electron.app.on("will-quit", () => { // this is to disable electron warning spam in the dev console (local development only) process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true"; -await import('./src/www.js'); +await import('./src/main.js'); diff --git a/nodemon.json b/nodemon.json index 1266e1b4c..efaf5f759 100644 --- a/nodemon.json +++ b/nodemon.json @@ -3,7 +3,10 @@ "ignore": [".git", "node_modules/**/node_modules", "src/public/"], "verbose": false, "exec": "tsx", - "watch": ["src/"], + "watch": [ + "src/", + "translations/" + ], "signal": "SIGTERM", "env": { "NODE_ENV": "development" diff --git a/package-lock.json b/package-lock.json index f8b61d20a..99eae360a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "trilium", - "version": "0.90.4", + "version": "0.90.6-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "trilium", - "version": "0.90.4", + "version": "0.90.6-beta", "license": "AGPL-3.0-only", "dependencies": { "@braintree/sanitize-url": "^7.1.0", @@ -48,6 +48,7 @@ "http-proxy-agent": "7.0.2", "https-proxy-agent": "^7.0.5", "i18next": "^23.14.0", + "i18next-fs-backend": "^2.3.2", "i18next-http-backend": "^2.6.1", "image-type": "4.1.0", "ini": "^4.1.3", @@ -98,7 +99,7 @@ "yauzl": "^3.1.3" }, "bin": { - "trilium": "src/www.js" + "trilium": "src/main.js" }, "devDependencies": { "@electron-forge/cli": "^7.4.0", @@ -9660,6 +9661,11 @@ "@babel/runtime": "^7.23.2" } }, + "node_modules/i18next-fs-backend": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.3.2.tgz", + "integrity": "sha512-LIwUlkqDZnUI8lnUxBnEj8K/FrHQTT/Sc+1rvDm9E8YvvY5YxzoEAASNx+W5M9DfD5s77lI5vSAFWeTp26B/3Q==" + }, "node_modules/i18next-http-backend": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.6.1.tgz", diff --git a/package.json b/package.json index 520c89ca6..d75f90ae2 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,9 @@ "name": "trilium", "productName": "TriliumNext Notes", "description": "Build your personal knowledge base with TriliumNext Notes", - "version": "0.90.4", + "version": "0.90.6-beta", "license": "AGPL-3.0-only", - "main": "./dist/electron.js", + "main": "./dist/electron-main.js", "author": { "name": "TriliumNext Notes Team", "email": "contact@eliandoran.me", @@ -12,7 +12,7 @@ }, "copyright": "", "bin": { - "trilium": "src/www.js" + "trilium": "src/main.js" }, "repository": { "type": "git", @@ -20,12 +20,12 @@ }, "type": "module", "scripts": { - "start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts", - "start-server-safe": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts", - "start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts", - "start-test-server": "npm run switch-server; rimraf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 ts-node src/www.ts", + "start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts", + "start-server-safe": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts", + "start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/main.ts", + "start-test-server": "npm run switch-server; rimraf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 ts-node src/main.ts", "qstart-server": "npm run switch-server && npm run start-server", - "start-electron": "npm run prepare-dist && cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron.js --inspect=5858 .", + "start-electron": "npm run prepare-dist && cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron-main.js --inspect=5858 .", "start-electron-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .", "qstart-electron": "npm run switch-electron && npm run start-electron", "switch-server": "rimraf ./node_modules/better-sqlite3 && npm install", @@ -43,9 +43,9 @@ "prepare-dist": "rimraf ./dist && tsc && tsx ./bin/copy-dist.ts", "update-build-info": "tsx bin/update-build-info.ts", "errors": "tsc --watch --noEmit", - "integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts", - "integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts", - "integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/www.ts", + "integration-edit-db": "cross-env TRILIUM_INTEGRATION_TEST=edit TRILIUM_PORT=8081 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", + "integration-mem-db": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", + "integration-mem-db-dev": "cross-env TRILIUM_INTEGRATION_TEST=memory TRILIUM_PORT=8082 TRILIUM_ENV=dev TRILIUM_DATA_DIR=./integration-tests/db nodemon src/main.ts", "generate-document": "cross-env nodemon src/tools/generate_document.ts 1000" }, "dependencies": { @@ -88,6 +88,7 @@ "http-proxy-agent": "7.0.2", "https-proxy-agent": "^7.0.5", "i18next": "^23.14.0", + "i18next-fs-backend": "^2.3.2", "i18next-http-backend": "^2.6.1", "image-type": "4.1.0", "ini": "^4.1.3", diff --git a/src/app.ts b/src/app.ts index 351bd6f0a..4fabebcc5 100644 --- a/src/app.ts +++ b/src/app.ts @@ -14,6 +14,7 @@ import custom from "./routes/custom.js"; import error_handlers from "./routes/error_handlers.js"; import { startScheduledCleanup } from "./services/erase.js"; import sql_init from "./services/sql_init.js"; +import { t } from "i18next"; await import('./services/handlers.js'); await import('./becca/becca_loader.js'); @@ -29,6 +30,11 @@ sql_init.initializeDb(); app.set('views', path.join(scriptDir, 'views')); app.set('view engine', 'ejs'); +app.use((req, res, next) => { + res.locals.t = t; + return next(); +}); + if (!utils.isElectron()) { app.use(compression()); // HTTP compression } diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 000000000..c38b85933 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,13 @@ +/* + * Make sure not to import any modules that depend on localized messages via i18next here, as the initializations + * are loaded later and will result in an empty string. + */ + +import { initializeTranslations } from "./services/i18n.js"; + +async function startApplication() { + await import("./www.js"); +} + +await initializeTranslations(); +await startApplication(); \ No newline at end of file diff --git a/src/public/app/menus/tree_context_menu.js b/src/public/app/menus/tree_context_menu.js index e3e19dc93..684b31c99 100644 --- a/src/public/app/menus/tree_context_menu.js +++ b/src/public/app/menus/tree_context_menu.js @@ -8,6 +8,7 @@ import noteTypesService from "../services/note_types.js"; import server from "../services/server.js"; import toastService from "../services/toast.js"; import dialogService from "../services/dialog.js"; +import { t } from "../services/i18n.js"; export default class TreeContextMenu { /** @@ -48,55 +49,55 @@ export default class TreeContextMenu { const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch; return [ - { title: 'Open in a new tab Ctrl+Click', command: "openInTab", uiIcon: "bx bx-empty", enabled: noSelectedNotes }, - { title: 'Open in a new split', command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes }, - { title: 'Insert note after ', command: "insertNoteAfter", uiIcon: "bx bx-plus", + { title: `${t("tree-context-menu.open-in-a-new-tab")} Ctrl+Click`, command: "openInTab", uiIcon: "bx bx-empty", enabled: noSelectedNotes }, + { title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes }, + { title: `${t("tree-context-menu.insert-note-after")} `, command: "insertNoteAfter", uiIcon: "bx bx-plus", items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null, enabled: insertNoteAfterEnabled && noSelectedNotes && notOptions }, - { title: 'Insert child note ', command: "insertChildNote", uiIcon: "bx bx-plus", + { title: `${t("tree-context-menu.insert-child-note")} `, command: "insertChildNote", uiIcon: "bx bx-plus", items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null, enabled: notSearch && noSelectedNotes && notOptions }, - { title: 'Delete ', command: "deleteNotes", uiIcon: "bx bx-trash", + { title: `${t("tree-context-menu.delete")} `, command: "deleteNotes", uiIcon: "bx bx-trash", enabled: isNotRoot && !isHoisted && parentNotSearch && notOptions }, { title: "----" }, - { title: 'Search in subtree ', command: "searchInSubtree", uiIcon: "bx bx-search", + { title: `${t("tree-context-menu.search-in-subtree")} `, command: "searchInSubtree", uiIcon: "bx bx-search", enabled: notSearch && noSelectedNotes }, isHoisted ? null : { title: 'Hoist note ', command: "toggleNoteHoisting", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch }, !isHoisted || !isNotRoot ? null : { title: 'Unhoist note ', command: "toggleNoteHoisting", uiIcon: "bx bx-door-open" }, - { title: 'Edit branch prefix ', command: "editBranchPrefix", uiIcon: "bx bx-empty", + { title: `${t("tree-context-menu.edit-branch-prefix")} `, command: "editBranchPrefix", uiIcon: "bx bx-empty", enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptions }, - { title: "Advanced", uiIcon: "bx bx-empty", enabled: true, items: [ - { title: 'Expand subtree ', command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes }, - { title: 'Collapse subtree ', command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes }, - { title: 'Sort by ... ', command: "sortChildNotes", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch }, - { title: 'Recent changes in subtree', command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptions }, - { title: 'Convert to attachment', command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted && notOptions }, - { title: 'Copy note path to clipboard', command: "copyNotePathToClipboard", uiIcon: "bx bx-empty", enabled: true } + { title: t("tree-context-menu.advanced"), uiIcon: "bx bx-empty", enabled: true, items: [ + { title: `${t("tree-context-menu.expand-subtree")} `, command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes }, + { title: `${t("tree-context-menu.collapse-subtree")} `, command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes }, + { title: `${t("tree-context-menu.sort-by")} `, command: "sortChildNotes", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch }, + { title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptions }, + { title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted && notOptions }, + { title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-empty", enabled: true } ] }, { title: "----" }, - { title: "Protect subtree", command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes }, - { title: "Unprotect subtree", command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes }, + { title: t("tree-context-menu.protect-subtree"), command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes }, + { title: t("tree-context-menu.unprotect-subtree"), command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes }, { title: "----" }, - { title: 'Copy / clone ', command: "copyNotesToClipboard", uiIcon: "bx bx-copy", + { title: `${t("tree-context-menu.copy-clone")} `, command: "copyNotesToClipboard", uiIcon: "bx bx-copy", enabled: isNotRoot && !isHoisted }, - { title: 'Clone to ... ', command: "cloneNotesTo", uiIcon: "bx bx-empty", + { title: `${t("tree-context-menu.clone-to")} `, command: "cloneNotesTo", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted }, - { title: 'Cut ', command: "cutNotesToClipboard", uiIcon: "bx bx-cut", + { title: `${t("tree-context-menu.cut")} `, command: "cutNotesToClipboard", uiIcon: "bx bx-cut", enabled: isNotRoot && !isHoisted && parentNotSearch }, - { title: 'Move to ... ', command: "moveNotesTo", uiIcon: "bx bx-empty", + { title: `${t("tree-context-menu.move-to")} `, command: "moveNotesTo", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted && parentNotSearch }, - { title: 'Paste into ', command: "pasteNotesFromClipboard", uiIcon: "bx bx-paste", + { title: `${t("tree-context-menu.paste-into")} `, command: "pasteNotesFromClipboard", uiIcon: "bx bx-paste", enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes }, - { title: 'Paste after', command: "pasteNotesAfterFromClipboard", uiIcon: "bx bx-paste", + { title: t("tree-context-menu.paste-after"), command: "pasteNotesAfterFromClipboard", uiIcon: "bx bx-paste", enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes }, - { title: `Duplicate subtree `, command: "duplicateSubtree", uiIcon: "bx bx-empty", + { title: `${t("tree-context-menu.duplicate-subtree")} `, command: "duplicateSubtree", uiIcon: "bx bx-empty", enabled: parentNotSearch && isNotRoot && !isHoisted && notOptions }, { title: "----" }, - { title: "Export", command: "exportNote", uiIcon: "bx bx-empty", + { title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-empty", enabled: notSearch && noSelectedNotes && notOptions }, - { title: "Import into note", command: "importIntoNote", uiIcon: "bx bx-empty", + { title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-empty", enabled: notSearch && noSelectedNotes && notOptions }, - { title: "Apply bulk actions", command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus", + { title: t("tree-context-menu.apply-bulk-actions"), command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus", enabled: true } ].filter(row => row !== null); } diff --git a/src/public/app/services/note_types.js b/src/public/app/services/note_types.js index c04b0a9f1..840237c8a 100644 --- a/src/public/app/services/note_types.js +++ b/src/public/app/services/note_types.js @@ -1,19 +1,20 @@ import server from "./server.js"; import froca from "./froca.js"; +import { t } from "./i18n.js"; async function getNoteTypeItems(command) { const items = [ - { title: "Text", command: command, type: "text", uiIcon: "bx bx-note" }, - { title: "Code", command: command, type: "code", uiIcon: "bx bx-code" }, - { title: "Saved Search", command: command, type: "search", uiIcon: "bx bx-file-find" }, - { title: "Relation Map", command: command, type: "relationMap", uiIcon: "bx bxl-graphql" }, - { title: "Note Map", command: command, type: "noteMap", uiIcon: "bx bxl-graphql" }, - { title: "Render Note", command: command, type: "render", uiIcon: "bx bx-extension" }, - { title: "Book", command: command, type: "book", uiIcon: "bx bx-book" }, - { title: "Mermaid Diagram", command: command, type: "mermaid", uiIcon: "bx bx-selection" }, - { title: "Canvas", command: command, type: "canvas", uiIcon: "bx bx-pen" }, - { title: "Web View", command: command, type: "webView", uiIcon: "bx bx-globe-alt" }, - { title: "Mind Map", command, type: "mindMap", uiIcon: "bx bx-sitemap" } + { title: t("note_types.text"), command: command, type: "text", uiIcon: "bx bx-note" }, + { title: t("note_types.code"), command: command, type: "code", uiIcon: "bx bx-code" }, + { title: t("note_types.saved-search"), command: command, type: "search", uiIcon: "bx bx-file-find" }, + { title: t("note_types.relation-map"), command: command, type: "relationMap", uiIcon: "bx bx-map-alt" }, + { title: t("note_types.note-map"), command: command, type: "noteMap", uiIcon: "bx bxl-graphql" }, + { title: t("note_types.render-note"), command: command, type: "render", uiIcon: "bx bx-extension" }, + { title: t("note_types.book"), command: command, type: "book", uiIcon: "bx bx-book" }, + { title: t("note_types.mermaid-diagram"), command: command, type: "mermaid", uiIcon: "bx bx-selection" }, + { title: t("note_types.canvas"), command: command, type: "canvas", uiIcon: "bx bx-pen" }, + { title: t("note_types.web-view"), command: command, type: "webView", uiIcon: "bx bx-globe-alt" }, + { title: t("note_types.mind-map"), command, type: "mindMap", uiIcon: "bx bx-sitemap" } ]; const templateNoteIds = await server.get("search-templates"); diff --git a/src/public/app/widgets/buttons/note_actions.js b/src/public/app/widgets/buttons/note_actions.js index fa3a14de6..1c8e53f3f 100644 --- a/src/public/app/widgets/buttons/note_actions.js +++ b/src/public/app/widgets/buttons/note_actions.js @@ -17,7 +17,7 @@ const TPL = ` } .note-actions .dropdown-menu { - width: 15em; + min-width: 15em; } .note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover { diff --git a/src/public/app/widgets/note_map.js b/src/public/app/widgets/note_map.js index ab7d2c0e0..f4b00dc81 100644 --- a/src/public/app/widgets/note_map.js +++ b/src/public/app/widgets/note_map.js @@ -6,6 +6,7 @@ import appContext from "../components/app_context.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js"; import linkContextMenuService from "../menus/link_context_menu.js"; import utils from "../services/utils.js"; +import { t } from "../services/i18n.js"; const esc = utils.escapeHtml; @@ -30,8 +31,8 @@ const TPL = `
- - + +
diff --git a/src/public/app/widgets/note_type.js b/src/public/app/widgets/note_type.js index 66e699eff..1bf121888 100644 --- a/src/public/app/widgets/note_type.js +++ b/src/public/app/widgets/note_type.js @@ -2,25 +2,26 @@ import server from '../services/server.js'; import mimeTypesService from '../services/mime_types.js'; import NoteContextAwareWidget from "./note_context_aware_widget.js"; import dialogService from "../services/dialog.js"; +import { t } from '../services/i18n.js'; const NOTE_TYPES = [ - { type: "file", title: "File", selectable: false }, - { type: "image", title: "Image", selectable: false }, - { type: "search", title: "Saved Search", selectable: false }, - { type: "noteMap", mime: '', title: "Note Map", selectable: false }, - { type: "launcher", mime: '', title: "Launcher", selectable: false }, - { type: "doc", mime: '', title: "Doc", selectable: false }, - { type: "contentWidget", mime: '', title: "Widget", selectable: false }, + { type: "file", title: t("note_types.file"), selectable: false }, + { type: "image", title: t("note_types.image"), selectable: false }, + { type: "search", title: t("note_types.saved-search"), selectable: false }, + { type: "noteMap", mime: '', title: t("note_types.note-map"), selectable: false }, + { type: "launcher", mime: '', title: t("note_types.launcher"), selectable: false }, + { type: "doc", mime: '', title: t("note_types.doc"), selectable: false }, + { type: "contentWidget", mime: '', title: t("note_types.widget"), selectable: false }, - { type: "text", mime: "text/html", title: "Text", selectable: true }, - { type: "relationMap", mime: "application/json", title: "Relation Map", selectable: true }, - { type: "mindMap", mime: "application/json", "title": "Mind Map", selectable: true }, - { type: "render", mime: '', title: "Render Note", selectable: true }, - { type: "canvas", mime: 'application/json', title: "Canvas", selectable: true }, - { type: "mermaid", mime: 'text/mermaid', title: "Mermaid Diagram", selectable: true }, - { type: "book", mime: '', title: "Book", selectable: true }, - { type: "webView", mime: '', title: "Web View", selectable: true }, - { type: "code", mime: 'text/plain', title: "Code", selectable: true } + { type: "text", mime: "text/html", title: t("note_types.text"), selectable: true }, + { type: "relationMap", mime: "application/json", title: t("note_types.relation-map"), selectable: true }, + { type: "mindMap", mime: "application/json", "title": t("note_types.mind-map"), selectable: true }, + { type: "render", mime: '', title: t("note_types.render-note"), selectable: true }, + { type: "canvas", mime: 'application/json', title: t("note_types.canvas"), selectable: true }, + { type: "mermaid", mime: 'text/mermaid', title: t("note_types.mermaid-diagram"), selectable: true }, + { type: "book", mime: '', title: t("note_types.book"), selectable: true }, + { type: "webView", mime: '', title: t("note_types.web-view"), selectable: true }, + { type: "code", mime: 'text/plain', title: t("note_types.code"), selectable: true } ]; const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter(nt => !nt.selectable).map(nt => nt.type); diff --git a/src/public/app/widgets/protected_note_switch.js b/src/public/app/widgets/protected_note_switch.js index e06bf973d..c7cd5c41a 100644 --- a/src/public/app/widgets/protected_note_switch.js +++ b/src/public/app/widgets/protected_note_switch.js @@ -1,3 +1,4 @@ +import { t } from "../services/i18n.js"; import protectedSessionService from "../services/protected_session.js"; import SwitchWidget from "./switch.js"; @@ -5,11 +6,11 @@ export default class ProtectedNoteSwitchWidget extends SwitchWidget { doRender() { super.doRender(); - this.$switchOnName.text("Protect the note"); - this.$switchOnButton.attr("title", "Note is not protected, click to make it protected"); + this.$switchOnName.text(t("protect_note.toggle-on")); + this.$switchOnButton.attr("title", t("protect_note.toggle-on-hint")); - this.$switchOffName.text("Unprotect the note"); - this.$switchOffButton.attr("title", "Note is protected, click to make it unprotected"); + this.$switchOffName.text(t("protect_note.toggle-off")); + this.$switchOffButton.attr("title", t("protect_note.toggle-off-hint")); } switchOn() { diff --git a/src/public/app/widgets/shared_info.js b/src/public/app/widgets/shared_info.js index 9183a5c2c..e6a6ad33e 100644 --- a/src/public/app/widgets/shared_info.js +++ b/src/public/app/widgets/shared_info.js @@ -1,6 +1,7 @@ import NoteContextAwareWidget from "./note_context_aware_widget.js"; import options from "../services/options.js"; import attributeService from "../services/attributes.js"; +import { t } from "../services/i18n.js"; const TPL = `
@@ -13,7 +14,7 @@ const TPL = ` } - . For help visit wiki. + . ${t("shared_info.help_link")}
`; export default class SharedInfoWidget extends NoteContextAwareWidget { @@ -36,7 +37,7 @@ export default class SharedInfoWidget extends NoteContextAwareWidget { if (syncServerHost) { link = `${syncServerHost}/share/${shareId}`; - this.$sharedText.text("This note is shared publicly on"); + this.$sharedText.text(t("shared_info.shared_publicly")); } else { let host = location.host; @@ -47,7 +48,7 @@ export default class SharedInfoWidget extends NoteContextAwareWidget { } link = `${location.protocol}//${host}${location.pathname}share/${shareId}`; - this.$sharedText.text("This note is shared locally on"); + this.$sharedText.text(t("shared_info.shared_locally")); } this.$sharedLink.attr("href", link).text(link); diff --git a/src/public/app/widgets/shared_switch.js b/src/public/app/widgets/shared_switch.js index 65f654c14..2a51eddc8 100644 --- a/src/public/app/widgets/shared_switch.js +++ b/src/public/app/widgets/shared_switch.js @@ -4,6 +4,7 @@ import server from "../services/server.js"; import utils from "../services/utils.js"; import syncService from "../services/sync.js"; import dialogService from "../services/dialog.js"; +import { t } from "../services/i18n.js"; export default class SharedSwitchWidget extends SwitchWidget { isEnabled() { @@ -15,11 +16,11 @@ export default class SharedSwitchWidget extends SwitchWidget { doRender() { super.doRender(); - this.$switchOnName.text("Shared"); - this.$switchOnButton.attr("title", "Share the note"); + this.$switchOnName.text(t("shared_switch.shared")); + this.$switchOnButton.attr("title", t("shared_switch.toggle-on-title")); - this.$switchOffName.text("Shared"); - this.$switchOffButton.attr("title", "Unshare the note"); + this.$switchOffName.text(t("shared_switch.shared")); + this.$switchOffButton.attr("title", t("shared_switch.toggle-off-title")); this.$helpButton.attr("data-help-page", "sharing.html").show(); this.$helpButton.on('click', e => utils.openHelp($(e.target))); @@ -39,9 +40,7 @@ export default class SharedSwitchWidget extends SwitchWidget { } if (this.note.getParentBranches().length === 1) { - const text = "This note exists only as a shared note, unsharing would delete it. Do you want to continue and thus delete this note?"; - - if (!await dialogService.confirm(text)) { + if (!await dialogService.confirm(t("shared_switch.shared-branch"))) { return; } } @@ -60,7 +59,7 @@ export default class SharedSwitchWidget extends SwitchWidget { this.$switchOff.toggle(!!isShared); if (switchDisabled) { - this.$widget.attr("title", "Note cannot be unshared here because it is shared through inheritance from an ancestor."); + this.$widget.attr("title", t("shared_switch.inherited")); this.$switchOff.addClass("switch-disabled"); } else { diff --git a/src/public/app/widgets/switch.js b/src/public/app/widgets/switch.js index e70fb5612..c59126f81 100644 --- a/src/public/app/widgets/switch.js +++ b/src/public/app/widgets/switch.js @@ -1,3 +1,4 @@ +import { t } from "../services/i18n.js"; import NoteContextAwareWidget from "./note_context_aware_widget.js"; const TPL = ` @@ -90,7 +91,7 @@ const TPL = `
- + `; export default class SwitchWidget extends NoteContextAwareWidget { diff --git a/src/public/app/widgets/template_switch.js b/src/public/app/widgets/template_switch.js index f18440973..0c2c064ca 100644 --- a/src/public/app/widgets/template_switch.js +++ b/src/public/app/widgets/template_switch.js @@ -1,5 +1,6 @@ import SwitchWidget from "./switch.js"; import attributeService from "../services/attributes.js"; +import { t } from "../services/i18n.js"; /** * Switch for the basic properties widget which allows the user to select whether the note is a template or not, which toggles the `#template` attribute. @@ -14,11 +15,11 @@ export default class TemplateSwitchWidget extends SwitchWidget { doRender() { super.doRender(); - this.$switchOnName.text("Template"); - this.$switchOnButton.attr("title", "Make the note a template"); + this.$switchOnName.text(t("template_switch.template")); + this.$switchOnButton.attr("title", t("template_switch.toggle-on-hint")); this.$switchOffName.text("Template"); - this.$switchOffButton.attr("title", "Remove the note as a template"); + this.$switchOffButton.attr("title", t("template_switch.toggle-off-hint")); this.$helpButton.attr("data-help-page", "template.html").show(); this.$helpButton.on('click', e => utils.openHelp($(e.target))); diff --git a/src/public/app/widgets/type_widgets/options/appearance/max_content_width.js b/src/public/app/widgets/type_widgets/options/appearance/max_content_width.js index a832cb5db..a874dff3f 100644 --- a/src/public/app/widgets/type_widgets/options/appearance/max_content_width.js +++ b/src/public/app/widgets/type_widgets/options/appearance/max_content_width.js @@ -9,7 +9,7 @@ const TPL = `

${t("max_content_width.default_description")}

-
+
diff --git a/src/public/translations/cn/translation.json b/src/public/translations/cn/translation.json index 950a24805..8cdf4e411 100644 --- a/src/public/translations/cn/translation.json +++ b/src/public/translations/cn/translation.json @@ -1023,8 +1023,8 @@ "title": "下拉菜单可用的MIME文件类型" }, "vim_key_bindings": { - "use_vim_keybindings_in_code_notes": "在代码笔记中使用Vim键绑定(无ex模式)", - "enable_vim_keybindings": "启用Vim键绑定" + "use_vim_keybindings_in_code_notes": "", + "enable_vim_keybindings": "" }, "wrap_lines": { "wrap_lines_in_code_notes": "代码笔记自动换行", diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index d92f2fe60..d2579f789 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1026,8 +1026,8 @@ "title": "Available MIME types in the dropdown" }, "vim_key_bindings": { - "use_vim_keybindings_in_code_notes": "Use vim keybindings in code notes (no ex mode)", - "enable_vim_keybindings": "Enable Vim Keybindings" + "use_vim_keybindings_in_code_notes": "Vim keybindings", + "enable_vim_keybindings": "Enable Vim keybindings in code notes (no ex mode)" }, "wrap_lines": { "wrap_lines_in_code_notes": "Wrap lines in code notes", @@ -1244,5 +1244,79 @@ "note_is_editable": "Note is editable if it's not too long.", "note_is_read_only": "Note is read-only, but can be edited with a button click.", "note_is_always_editable": "Note is always editable, regardless of its length." - } + }, + "note-map": { + "button-link-map": "Link Map", + "button-tree-map": "Tree map" + }, + "tree-context-menu": { + "open-in-a-new-tab": "Open in a new tab", + "open-in-a-new-split": "Open in a new split", + "insert-note-after": "Insert note after", + "insert-child-note": "Insert child note", + "delete": "Delete", + "search-in-subtree": "Search in subtree", + "edit-branch-prefix": "Edit branch prefix", + "advanced": "Advanced", + "expand-subtree": "Expand subtree", + "collapse-subtree": "Collapse subtree", + "sort-by": "Sort by...", + "recent-changes-in-subtree": "Recent changes in subtree", + "convert-to-attachment": "Convert to attachment", + "copy-note-path-to-clipboard": "Copy note path to clipboard", + "protect-subtree": "Protect subtree", + "unprotect-subtree": "Unprotect subtree", + "copy-clone": "Copy / clone", + "clone-to": "Clone to...", + "cut": "Cut", + "move-to": "Move to...", + "paste-into": "Paste into", + "paste-after": "Paste after", + "duplicate-subtree": "Duplicate subtree", + "export": "Export", + "import-into-note": "Import into note", + "apply-bulk-actions": "Apply bulk actions" + }, + "shared_info": { + "shared_publicly": "This note is shared publicly on", + "shared_locally": "This note is shared locally on", + "help_link": "For help visit wiki." + }, + "note_types": { + "text": "Text", + "code": "Code", + "saved-search": "Saved Search", + "relation-map": "Relation Map", + "note-map": "Note Map", + "render-note": "Render Note", + "book": "Book", + "mermaid-diagram": "Mermaid Diagram", + "canvas": "Canvas", + "web-view": "Web View", + "mind-map": "Mind Map", + "file": "File", + "image": "Image", + "launcher": "Launcher", + "doc": "Doc", + "widget": "Widget" + }, + "protect_note": { + "toggle-on": "Protect the note", + "toggle-off": "Unprotect the note", + "toggle-on-hint": "Note is not protected, click to make it protected", + "toggle-off-hint": "Note is protected, click to make it unprotected" + }, + "shared_switch": { + "shared": "Shared", + "toggle-on-title": "Share the note", + "toggle-off-title": "Unshare the note", + "shared-branch": "This note exists only as a shared note, unsharing would delete it. Do you want to continue and thus delete this note?", + "inherited": "Note cannot be unshared here because it is shared through inheritance from an ancestor." + }, + "template_switch": { + "template": "Template", + "toggle-on-hint": "Make the note a template", + "toggle-off-hint": "Remove the note as a template" + }, + "open-help-page": "Open help page" } diff --git a/src/public/translations/es/translation.json b/src/public/translations/es/translation.json index 6f98ddd27..c1176ad22 100644 --- a/src/public/translations/es/translation.json +++ b/src/public/translations/es/translation.json @@ -645,6 +645,9 @@ "hide_floating_buttons_button": { "button_title": "Ocultar botones" }, + "svg_export_button": { + "button_title": "Exportar diagrama como SVG" + }, "relation_map_buttons": { "create_child_note_title": "Crear una nueva nota hijo y agregarla a este mapa de relaciones", "reset_pan_zoom_title": "Restablecer la panorámica y el zoom a las coordenadas y ampliación iniciales", @@ -1023,8 +1026,8 @@ "title": "Tipos MIME disponibles en el menú desplegable" }, "vim_key_bindings": { - "use_vim_keybindings_in_code_notes": "Utilizar combinaciones de teclas Vim en notas de código (no modo ex)", - "enable_vim_keybindings": "Habilitar combinaciones de teclas de Vim" + "use_vim_keybindings_in_code_notes": "", + "enable_vim_keybindings": "" }, "wrap_lines": { "wrap_lines_in_code_notes": "Ajustar líneas en notas de código", @@ -1043,7 +1046,7 @@ "attachment_auto_deletion_description": "Los archivos adjuntos se eliminan (y borran) automáticamente si ya no se hace referencia a ellos en su nota después de un tiempo de espera definido.", "erase_attachments_after_x_seconds": "Borrar archivos adjuntos después de X segundos de no usarse en su nota", "manual_erasing_description": "También puede activar el borrado manualmente (sin considerar el tiempo de espera definido anteriormente):", - "erase_unused_attachments_now": "Borrar ahora los archivos adjuntos no utilizados en la nota ", + "erase_unused_attachments_now": "Borrar ahora los archivos adjuntos no utilizados en la nota", "unused_attachments_erased": "Los archivos adjuntos no utilizados se han eliminado." }, "network_connections": { @@ -1078,7 +1081,7 @@ "save_button": "Guardar" }, "tray": { - "title": "Bandeja", + "title": "Bandeja de sistema", "enable_tray": "Habilitar bandeja (es necesario reiniciar Trilium para que este cambio surta efecto)" }, "heading_style": { diff --git a/src/public/translations/ro/translation.json b/src/public/translations/ro/translation.json index f3285c4e7..6bc64185d 100644 --- a/src/public/translations/ro/translation.json +++ b/src/public/translations/ro/translation.json @@ -77,7 +77,7 @@ "attachment_auto_deletion_description": "Atașamentele se șterg automat (permanent) dacă nu sunt referențiate de către notița lor părinte după un timp prestabilit de timp.", "attachment_erasure_timeout": "Perioadă de ștergere a atașamentelor", "erase_attachments_after_x_seconds": "Șterge atașamentele după X secunde după ce acestea n-au mai fost folosite într-o notiță", - "erase_unused_attachments_now": "Se poate lansa o ștergere manuală (fără a mai considera perioada de grație definită anterior):", + "erase_unused_attachments_now": "Elimină atașamentele șterse acum", "manual_erasing_description": "Șterge acum toate atașamentele nefolosite din notițe", "unused_attachments_erased": "Atașamentele nefolosite au fost șterse." }, @@ -142,8 +142,8 @@ "icon_class": "valoarea acestei etichete este adăugată ca o clasă CSS la iconița notiței din ierarhia notițelor, fapt ce poate ajuta la identificarea vizuală mai rapidă a notițelor. Un exemplu ar fi „bx bx-home” pentru iconițe preluate din boxicons. Poate fi folosită în notițe de tip șablon.", "inbox": "locația implicită în care vor apărea noile notițe atunci când se crează o noitiță utilizând butonul „Crează notiță” din bara laterală, notițele vor fi create în interiorul notiței cu această etichetă.", "inherit": "atributele acestei notițe vor fi moștenite chiar dacă nu există o relație părinte-copil între notițe. A se vedea relația de tip șablon pentru un concept similar. De asemenea, a se vedea moștenirea atributelor în documentație.", - "inheritable": "Moștenibil", - "inheritable_title": "Atributele moștenibile vor fi moștenite tuturor descendenților acestei notițe.", + "inheritable": "Moștenibilă", + "inheritable_title": "Atributele moștenibile vor fi moștenite de către toți descendenții acestei notițe.", "inverse_relation": "Relație inversă", "inverse_relation_title": "Setare opțională pentru a defini relația inversă. Exemplu: Tată - Fiu sunt două relații inverse.", "is_owned_by_note": "este deținut(ă) de notița", @@ -1219,8 +1219,8 @@ "vacuuming_database": "Baza de date este în curs de compactare..." }, "vim_key_bindings": { - "enable_vim_keybindings": "Activează combinațiile de taste Vim", - "use_vim_keybindings_in_code_notes": "Permite utilizarea combinațiilor de taste în stil Vim pentru notițeled e tip cod (fără modul ex)" + "enable_vim_keybindings": "Permite utilizarea combinațiilor de taste în stil Vim pentru notițele de tip cod (fără modul ex)", + "use_vim_keybindings_in_code_notes": "Combinații de taste Vim" }, "web_view": { "create_label": "Pentru a începe, creați o etichetă cu adresa URL de încorporat, e.g. #webViewSrc=\"https://www.google.com\"", @@ -1244,5 +1244,79 @@ }, "svg_export_button": { "button_title": "Exportă diagrama ca SVG" - } + }, + "note-map": { + "button-link-map": "Harta legăturilor", + "button-tree-map": "Harta ierarhiei" + }, + "tree-context-menu": { + "advanced": "Opțiuni avansate", + "apply-bulk-actions": "Aplică acțiuni în masă", + "clone-to": "Clonare în...", + "collapse-subtree": "Minimizează subnotițele", + "convert-to-attachment": "Convertește în atașament", + "copy-clone": "Copiază/clonează", + "copy-note-path-to-clipboard": "Copiază calea notiței în clipboard", + "cut": "Decupează", + "delete": "Șterge", + "duplicate-subtree": "Dublifică ierarhia", + "edit-branch-prefix": "Editează prefixul ramurii", + "expand-subtree": "Expandează subnotițele", + "export": "Exportă", + "import-into-note": "Importă în notiță", + "insert-child-note": "Inserează subnotiță", + "insert-note-after": "Inserează după notiță", + "move-to": "Mutare la...", + "open-in-a-new-split": "Deschide în lateral", + "open-in-a-new-tab": "Deschide în tab nou", + "paste-after": "Lipește după notiță", + "paste-into": "Lipește în notiță", + "protect-subtree": "Protejează ierarhia", + "recent-changes-in-subtree": "Schimbări recente în ierarhie", + "search-in-subtree": "Caută în ierarhie", + "sort-by": "Ordonare după...", + "unprotect-subtree": "Deprotejează ierarhia" + }, + "shared_info": { + "help_link": "Pentru informații vizitați wiki-ul.", + "shared_locally": "Această notiță este partajată local la", + "shared_publicly": "Această notiță este partajată public la" + }, + "note_types": { + "book": "Carte", + "canvas": "Schiță", + "code": "Cod sursă", + "mermaid-diagram": "Diagramă Mermaid", + "mind-map": "Hartă mentală", + "note-map": "Hartă notițe", + "relation-map": "Hartă relații", + "render-note": "Randare notiță", + "saved-search": "Căutare salvată", + "text": "Text", + "web-view": "Vizualizare web", + "doc": "Document", + "file": "Fișier", + "image": "Imagine", + "launcher": "Scurtătură", + "widget": "Widget" + }, + "protect_note": { + "toggle-off": "Deprotejează notița", + "toggle-off-hint": "Notița este protejată, click pentru a o deproteja", + "toggle-on": "Protejează notița", + "toggle-on-hint": "Notița nu este protejată, clic pentru a o proteja" + }, + "shared_switch": { + "inherited": "Nu se poate înlătura partajarea deoarece notița este partajată prin moștenirea de la o notiță părinte.", + "shared": "Partajată", + "shared-branch": "Această notiță există doar ca o notiță partajată, anularea partajării ar cauza ștergerea ei. Sigur doriți ștergerea notiței?", + "toggle-off-title": "Anulează partajarea notițeii", + "toggle-on-title": "Partajează notița" + }, + "template_switch": { + "template": "Șablon", + "toggle-off-hint": "Înlătură notița ca șablon", + "toggle-on-hint": "Marchează notița drept șablon" + }, + "open-help-page": "Deschide pagina de informații" } diff --git a/src/services/build.ts b/src/services/build.ts index e56f37eea..ea9553c7f 100644 --- a/src/services/build.ts +++ b/src/services/build.ts @@ -1,4 +1,4 @@ export default { - buildDate: "2024-08-09T22:05:59Z", - buildRevision: "2a5c444eff3eb99389339716ea8bfc989be90ecd" + buildDate: "2024-09-07T18:36:34Z", + buildRevision: "7c0d6930fa8f20d269dcfbcbc8f636a25f6bb9a7" }; diff --git a/src/services/i18n.ts b/src/services/i18n.ts new file mode 100644 index 000000000..ea109499c --- /dev/null +++ b/src/services/i18n.ts @@ -0,0 +1,31 @@ +import i18next from "i18next"; +import Backend from "i18next-fs-backend"; +import options from "./options.js"; +import sql_init from "./sql_init.js"; + +export async function initializeTranslations() { + // Initialize translations + await i18next.use(Backend).init({ + lng: await getCurrentLanguage(), + fallbackLng: "en", + ns: "server", + backend: { + loadPath: "translations/{{lng}}/{{ns}}.json" + }, + debug: true + }); +} + +function getCurrentLanguage() { + let language; + if (sql_init.isDbInitialized()) { + language = options.getOption("locale"); + } + + if (!language) { + console.info("Language option not found, falling back to en."); + language = "en"; + } + + return language; +} \ No newline at end of file diff --git a/src/services/keyboard_actions.ts b/src/services/keyboard_actions.ts index 28e3aeeb4..c00186ef6 100644 --- a/src/services/keyboard_actions.ts +++ b/src/services/keyboard_actions.ts @@ -4,6 +4,7 @@ import optionService from "./options.js"; import log from "./log.js"; import utils from "./utils.js"; import { KeyboardShortcut } from './keyboard_actions_interface.js'; +import { t } from "i18next"; const isMac = process.platform === "darwin"; const isElectron = utils.isElectron(); @@ -19,7 +20,7 @@ const isElectron = utils.isElectron(); const DEFAULT_KEYBOARD_ACTIONS: KeyboardShortcut[] = [ { - separator: "Note navigation" + separator: t("keyboard_actions.note-navigation") }, { actionName: "backInNoteHistory", @@ -36,7 +37,7 @@ const DEFAULT_KEYBOARD_ACTIONS: KeyboardShortcut[] = [ { actionName: "jumpToNote", defaultShortcuts: ["CommandOrControl+J"], - description: 'Open "Jump to note" dialog', + description: t("keyboard_actions.open-jump-to-note-dialog"), scope: "window" }, { @@ -52,37 +53,37 @@ const DEFAULT_KEYBOARD_ACTIONS: KeyboardShortcut[] = [ { actionName: "searchInSubtree", defaultShortcuts: ["CommandOrControl+Shift+S"], - description: "Search for notes in the active note's subtree", + description: t("keyboard_actions.search-in-subtree"), scope: "note-tree" }, { actionName: "expandSubtree", defaultShortcuts: [], - description: "Expand subtree of current note", + description: t("keyboard_actions.expand-subtree"), scope: "note-tree" }, { actionName: "collapseTree", defaultShortcuts: ["Alt+C"], - description: "Collapses the complete note tree", + description: t("keyboard_actions.collapse-tree"), scope: "window" }, { actionName: "collapseSubtree", defaultShortcuts: ["Alt+-"], - description: "Collapses subtree of current note", + description: t("keyboard_actions.collapse-subtree"), scope: "note-tree" }, { actionName: "sortChildNotes", defaultShortcuts: ["Alt+S"], - description: "Sort child notes", + description: t("keyboard_actions.sort-child-notes"), scope: "note-tree" }, { - separator: "Creating and moving notes" + separator: t("keyboard_actions.creating-and-moving-notes") }, { actionName: "createNoteAfter", @@ -97,49 +98,49 @@ const DEFAULT_KEYBOARD_ACTIONS: KeyboardShortcut[] = [ { actionName: "createNoteIntoInbox", defaultShortcuts: ["global:CommandOrControl+Alt+P"], - description: "Create and open in the inbox (if defined) or day note", + description: t("keyboard_actions.create-note-into-inbox"), scope: "window" }, { actionName: "deleteNotes", defaultShortcuts: ["Delete"], - description: "Delete note", + description: t("keyboard_actions.delete-note"), scope: "note-tree" }, { actionName: "moveNoteUp", defaultShortcuts: isMac ? ["Alt+Up"] : ["CommandOrControl+Up"], - description: "Move note up", + description: t("keyboard_actions.move-note-up"), scope: "note-tree" }, { actionName: "moveNoteDown", defaultShortcuts: isMac ? ["Alt+Down"] : ["CommandOrControl+Down"], - description: "Move note down", + description: t("keyboard_actions.move-note-down"), scope: "note-tree" }, { actionName: "moveNoteUpInHierarchy", defaultShortcuts: isMac ? ["Alt+Left"] : ["CommandOrControl+Left"], - description: "Move note up in hierarchy", + description: t("keyboard_actions.move-note-up-in-hierarchy"), scope: "note-tree" }, { actionName: "moveNoteDownInHierarchy", defaultShortcuts: isMac ? ["Alt+Right"] : ["CommandOrControl+Right"], - description: "Move note down in hierarchy", + description: t("keyboard_actions.move-note-down-in-hierarchy"), scope: "note-tree" }, { actionName: "editNoteTitle", defaultShortcuts: ["Enter"], - description: "Jump from tree to the note detail and edit title", + description: t("keyboard_actions.edit-note-title"), scope: "note-tree" }, { actionName: "editBranchPrefix", defaultShortcuts: ["F2"], - description: "Show Edit branch prefix dialog", + description: t("keyboard_actions.edit-branch-prefix"), scope: "note-tree" }, { @@ -154,399 +155,398 @@ const DEFAULT_KEYBOARD_ACTIONS: KeyboardShortcut[] = [ }, { - separator: "Note clipboard" + separator: t("keyboard_actions.note-clipboard") }, - { actionName: "copyNotesToClipboard", defaultShortcuts: ["CommandOrControl+C"], - description: "Copy selected notes to the clipboard", + description: t("keyboard_actions.copy-notes-to-clipboard"), scope: "note-tree" }, { actionName: "pasteNotesFromClipboard", defaultShortcuts: ["CommandOrControl+V"], - description: "Paste notes from the clipboard into active note", + description: t("keyboard_actions.paste-notes-from-clipboard"), scope: "note-tree" }, { actionName: "cutNotesToClipboard", defaultShortcuts: ["CommandOrControl+X"], - description: "Cut selected notes to the clipboard", + description: t("keyboard_actions.cut-notes-to-clipboard"), scope: "note-tree" }, { actionName: "selectAllNotesInParent", defaultShortcuts: ["CommandOrControl+A"], - description: "Select all notes from the current note level", + description: t("keyboard_actions.select-all-notes-in-parent"), scope: "note-tree" }, { actionName: "addNoteAboveToSelection", defaultShortcuts: ["Shift+Up"], - description: "Add note above to the selection", + description: t("keyboard_actions.add-note-above-to-the-selection"), scope: "note-tree" }, { actionName: "addNoteBelowToSelection", defaultShortcuts: ["Shift+Down"], - description: "Add note above to the selection", + description: t("keyboard_actions.add-note-below-to-selection"), scope: "note-tree" }, { actionName: "duplicateSubtree", defaultShortcuts: [], - description: "Duplicate subtree", + description: t("keyboard_actions.duplicate-subtree"), scope: "note-tree" }, { - separator: "Tabs & Windows" + separator: t("keyboard_actions.tabs-and-windows") }, { actionName: "openNewTab", defaultShortcuts: isElectron ? ["CommandOrControl+T"] : [], - description: "Opens new tab", + description: t("keyboard_actions.open-new-tab"), scope: "window" }, { actionName: "closeActiveTab", defaultShortcuts: isElectron ? ["CommandOrControl+W"] : [], - description: "Closes active tab", + description: t("keyboard_actions.close-active-tab"), scope: "window" }, { actionName: "reopenLastTab", defaultShortcuts: isElectron ? ["CommandOrControl+Shift+T"] : [], - description: "Reopens the last closed tab", + description: t("keyboard_actions.reopen-last-tab"), scope: "window" }, { actionName: "activateNextTab", defaultShortcuts: isElectron ? ["CommandOrControl+Tab", "CommandOrControl+PageDown"] : [], - description: "Activates tab on the right", + description: t("keyboard_actions.activate-next-tab"), scope: "window" }, { actionName: "activatePreviousTab", defaultShortcuts: isElectron ? ["CommandOrControl+Shift+Tab", "CommandOrControl+PageUp"] : [], - description: "Activates tab on the left", + description: t("keyboard_actions.activate-previous-tab"), scope: "window" }, { actionName: "openNewWindow", defaultShortcuts: [], - description: "Open new empty window", + description: t("keyboard_actions.open-new-window"), scope: "window" }, { actionName: "toggleTray", defaultShortcuts: [], - description: "Shows/hides the application from the system tray", + description: t("keyboard_actions.toggle-tray"), scope: "window" }, { actionName: "firstTab", defaultShortcuts: ["CommandOrControl+1"], - description: "Activates the first tab in the list", + description: t("keyboard_actions.first-tab"), scope: "window" }, { actionName: "secondTab", defaultShortcuts: ["CommandOrControl+2"], - description: "Activates the second tab in the list", + description: t("keyboard_actions.second-tab"), scope: "window" }, { actionName: "thirdTab", defaultShortcuts: ["CommandOrControl+3"], - description: "Activates the third tab in the list", + description: t("keyboard_actions.third-tab"), scope: "window" }, { actionName: "fourthTab", defaultShortcuts: ["CommandOrControl+4"], - description: "Activates the fourth tab in the list", + description: t("keyboard_actions.fourth-tab"), scope: "window" }, { actionName: "fifthTab", defaultShortcuts: ["CommandOrControl+5"], - description: "Activates the fifth tab in the list", + description: t("keyboard_actions.fifth-tab"), scope: "window" }, { actionName: "sixthTab", defaultShortcuts: ["CommandOrControl+6"], - description: "Activates the sixth tab in the list", + description: t("keyboard_actions.sixth-tab"), scope: "window" }, { actionName: "seventhTab", defaultShortcuts: ["CommandOrControl+7"], - description: "Activates the seventh tab in the list", + description: t("keyboard_actions.seventh-tab"), scope: "window" }, { actionName: "eigthTab", defaultShortcuts: ["CommandOrControl+8"], - description: "Activates the eighth tab in the list", + description: t("keyboard_actions.eight-tab"), scope: "window" }, { actionName: "ninthTab", defaultShortcuts: ["CommandOrControl+9"], - description: "Activates the ninth tab in the list", + description: t("keyboard_actions.ninth-tab"), scope: "window" }, { actionName: "lastTab", defaultShortcuts: [], - description: "Activates the last tab in the list", + description: t("keyboard_actions.last-tab"), scope: "window" }, { - separator: "Dialogs" + separator: t("keyboard_actions.dialogs") }, { actionName: "showNoteSource", defaultShortcuts: [], - description: "Shows Note Source dialog", + description: t("keyboard_actions.show-note-source"), scope: "window" }, { actionName: "showOptions", defaultShortcuts: [], - description: "Shows Options dialog", + description: t("keyboard_actions.show-options"), scope: "window" }, { actionName: "showRevisions", defaultShortcuts: [], - description: "Shows Note Revisions dialog", + description: t("keyboard_actions.show-revisions"), scope: "window" }, { actionName: "showRecentChanges", defaultShortcuts: [], - description: "Shows Recent Changes dialog", + description: t("keyboard_actions.show-recent-changes"), scope: "window" }, { actionName: "showSQLConsole", defaultShortcuts: ["Alt+O"], - description: "Shows SQL Console dialog", + description: t("keyboard_actions.show-sql-console"), scope: "window" }, { actionName: "showBackendLog", defaultShortcuts: [], - description: "Shows Backend Log dialog", + description: t("keyboard_actions.show-backend-log"), scope: "window" }, { actionName: "showHelp", defaultShortcuts: ["F1"], - description: "Shows built-in Help / cheatsheet", + description: t("keyboard_actions.show-help"), scope: "window" }, { - separator: "Text note operations" + separator: t("keyboard_actions.text-note-operations") }, { actionName: "addLinkToText", defaultShortcuts: ["CommandOrControl+L"], - description: "Open dialog to add link to the text", + description: t("keyboard_actions.add-link-to-text"), scope: "text-detail" }, { actionName: "followLinkUnderCursor", defaultShortcuts: ["CommandOrControl+Enter"], - description: "Follow link within which the caret is placed", + description: t("keyboard_actions.follow-link-under-cursor"), scope: "text-detail" }, { actionName: "insertDateTimeToText", defaultShortcuts: ["Alt+T"], - description: "Insert current date & time into text", + description: t("keyboard_actions.insert-date-and-time-to-text"), scope: "text-detail" }, { actionName: "pasteMarkdownIntoText", defaultShortcuts: [], - description: "Pastes Markdown from clipboard into text note", + description: t("keyboard_actions.paste-markdown-into-text"), scope: "text-detail" }, { actionName: "cutIntoNote", defaultShortcuts: [], - description: "Cuts the selection from the current note and creates subnote with the selected text", + description: t("keyboard_actions.cut-into-note"), scope: "text-detail" }, { actionName: "addIncludeNoteToText", defaultShortcuts: [], - description: "Opens the dialog to include a note", + description: t("keyboard_actions.add-include-note-to-text"), scope: "text-detail" }, { actionName: "editReadOnlyNote", defaultShortcuts: [], - description: "Edit a read-only note", + description: t("keyboard_actions.edit-readonly-note"), scope: "window" }, { - separator: "Attributes (labels & relations)" + separator: t("keyboard_actions.attributes-labels-and-relations") }, { actionName: "addNewLabel", defaultShortcuts: ["Alt+L"], - description: "Create new label", + description: t("keyboard_actions.add-new-label"), scope: "window" }, { actionName: "addNewRelation", defaultShortcuts: ["Alt+R"], - description: "Create new relation", + description: t("keyboard_actions.create-new-relation"), scope: "window" }, { - separator: "Ribbon tabs" + separator: t("keyboard_actions.ribbon-tabs") }, { actionName: "toggleRibbonTabBasicProperties", defaultShortcuts: [], - description: "Toggle Basic Properties", + description: t("keyboard_actions.toggle-basic-properties"), scope: "window" }, { actionName: "toggleRibbonTabBookProperties", defaultShortcuts: [], - description: "Toggle Book Properties", + description: t("keyboard_actions.toggle-book-properties"), scope: "window" }, { actionName: "toggleRibbonTabFileProperties", defaultShortcuts: [], - description: "Toggle File Properties", + description: t("keyboard_actions.toggle-file-properties"), scope: "window" }, { actionName: "toggleRibbonTabImageProperties", defaultShortcuts: [], - description: "Toggle Image Properties", + description: t("keyboard_actions.toggle-image-properties"), scope: "window" }, { actionName: "toggleRibbonTabOwnedAttributes", defaultShortcuts: ["Alt+A"], - description: "Toggle Owned Attributes", + description: t("keyboard_actions.toggle-owned-attributes"), scope: "window" }, { actionName: "toggleRibbonTabInheritedAttributes", defaultShortcuts: [], - description: "Toggle Inherited Attributes", + description: t("keyboard_actions.toggle-inherited-attributes"), scope: "window" }, { actionName: "toggleRibbonTabPromotedAttributes", defaultShortcuts: [], - description: "Toggle Promoted Attributes", + description: t("keyboard_actions.toggle-promoted-attributes"), scope: "window" }, { actionName: "toggleRibbonTabNoteMap", defaultShortcuts: [], - description: "Toggle Link Map", + description: t("keyboard_actions.toggle-link-map"), scope: "window" }, { actionName: "toggleRibbonTabNoteInfo", defaultShortcuts: [], - description: "Toggle Note Info", + description: t("keyboard_actions.toggle-note-info"), scope: "window" }, { actionName: "toggleRibbonTabNotePaths", defaultShortcuts: [], - description: "Toggle Note Paths", + description: t("keyboard_actions.toggle-note-paths"), scope: "window" }, { actionName: "toggleRibbonTabSimilarNotes", defaultShortcuts: [], - description: "Toggle Similar Notes", + description: t("keyboard_actions.toggle-similar-notes"), scope: "window" }, { - separator: "Other" + separator: t("keyboard_actions.other") }, { actionName: "toggleRightPane", defaultShortcuts: [], - description: "Toggle the display of the right pane, which includes Table of Contents and Highlights", + description: t("keyboard_actions.toggle-right-pane"), scope: "window" }, { actionName: "printActiveNote", defaultShortcuts: [], - description: "Print active note", + description: t("keyboard_actions.print-active-note"), scope: "window" }, { actionName: "openNoteExternally", defaultShortcuts: [], - description: "Open note as a file with default application", + description: t("keyboard_actions.open-note-externally"), scope: "window" }, { actionName: "renderActiveNote", defaultShortcuts: [], - description: "Render (re-render) active note", + description: t("keyboard_actions.render-active-note"), scope: "window" }, { actionName: "runActiveNote", defaultShortcuts: ["CommandOrControl+Enter"], - description: "Run active JavaScript (frontend/backend) code note", + description: t("keyboard_actions.run-active-note"), scope: "code-detail" }, { actionName: "toggleNoteHoisting", defaultShortcuts: ["Alt+H"], - description: "Toggles note hoisting of active note", + description: t("keyboard_actions.toggle-note-hoisting"), scope: "window" }, { actionName: "unhoist", defaultShortcuts: ["Alt+U"], - description: "Unhoist from anywhere", + description: t("keyboard_actions.unhoist"), scope: "window" }, { actionName: "reloadFrontendApp", defaultShortcuts: ["F5", "CommandOrControl+R"], - description: "Reload frontend App", + description: t("keyboard_actions.reload-frontend-app"), scope: "window" }, { actionName: "openDevTools", defaultShortcuts: isElectron ? ["CommandOrControl+Shift+I"] : [], - description: "Open dev tools", + description: t("keyboard_actions.open-dev-tools"), scope: "window" }, { @@ -557,43 +557,43 @@ const DEFAULT_KEYBOARD_ACTIONS: KeyboardShortcut[] = [ { actionName: "toggleLeftPane", defaultShortcuts: [], - description: "Toggle left (note tree) panel", + description: t("keyboard_actions.toggle-left-note-tree-panel"), scope: "window" }, { actionName: "toggleFullscreen", defaultShortcuts: ["F11"], - description: "Toggle full screen", + description: t("keyboard_actions.toggle-full-screen"), scope: "window" }, { actionName: "zoomOut", defaultShortcuts: isElectron ? ["CommandOrControl+-"] : [], - description: "Zoom Out", + description: t("keyboard_actions.zoom-out"), scope: "window" }, { actionName: "zoomIn", - description: "Zoom In", + description: t("keyboard_actions.zoom-in"), defaultShortcuts: isElectron ? ["CommandOrControl+="] : [], scope: "window" }, { actionName: "zoomReset", - description: "Reset zoom level", + description: t("keyboard_actions.reset-zoom-level"), defaultShortcuts: isElectron ? ["CommandOrControl+0"] : [], scope: "window" }, { actionName: "copyWithoutFormatting", defaultShortcuts: ["CommandOrControl+Alt+C"], - description: "Copy selected text without formatting", + description: t("keyboard_actions.copy-without-formatting"), scope: "text-detail" }, { actionName: "forceSaveRevision", defaultShortcuts: [], - description: "Force creating / saving new note revision of the active note", + description: t("keyboard_actions.force-save-revision"), scope: "window" } ]; diff --git a/src/views/desktop.ejs b/src/views/desktop.ejs index 64135659a..59a4b2b4f 100644 --- a/src/views/desktop.ejs +++ b/src/views/desktop.ejs @@ -7,7 +7,7 @@ TriliumNext Notes - +