Merge branch 'main' into main

This commit is contained in:
github-actions[bot]
2026-04-29 11:29:05 +00:00
committed by GitHub
37 changed files with 1511 additions and 171 deletions

View File

@@ -157,9 +157,9 @@ interface VaultEntry {
|---|---|---|
| `markdown` or absent | `.md`, `.markdown` | Full Tolaria note model: frontmatter, BlockNote, raw editor, relationships, title sync |
| `text` | UTF-8 editable formats such as `.yml`, `.json`, `.ts`, `.py`, `.sh` | Opens through the raw editor without Markdown note semantics |
| `binary` | Images, PDFs, archives, other non-text files | Stays a normal vault file; previewable images open in `FilePreview`, unsupported or broken binaries show an explicit fallback |
| `binary` | Images, PDFs, archives, other non-text files | Stays a normal vault file; previewable images and PDFs open in `FilePreview`, unsupported or broken binaries show an explicit fallback |
Image previewability is inferred in the renderer from the filename extension (`src/utils/filePreview.ts`) rather than stored as a new persisted kind. This keeps the filesystem as source of truth and avoids converting assets into proprietary objects.
Asset previewability is inferred in the renderer from the filename extension (`src/utils/filePreview.ts`) rather than stored as a new persisted kind. Supported images render through `<img>` and supported PDFs render through the webview's PDF object renderer, both backed by Tauri asset URLs. This keeps the filesystem as source of truth and avoids converting assets into proprietary objects.
### Note Content Freshness

View File

@@ -193,8 +193,8 @@ flowchart TD
```
- **Sidebar** (150-400px, resizable): Top-level filters (All Notes, Changes, Pulse), saved Views, collapsible type-based section groups, and a dedicated folder tree. The folder tree shows user-created folders plus default vault folders such as `attachments/` and `views/`; only the dedicated `type/` directory stays hidden because note types already have their own sidebar section. Saved Views persist a top-level YAML `order` field in each view file and use the same ordered-list mental model as Types: pointer users can drag the existing view row, while keyboard users can use command-palette move actions. The folder tree supports inline folder creation and rename, exposes a right-click menu for rename/delete plus filesystem reveal/copy-path actions, and auto-expands ancestor folders when the current selection or rename target is nested. Type sections and folder rows also act as note drop targets: dropping a note on a type updates its `type:` frontmatter, while dropping it on a folder runs the same crash-safe move path as the command palette flow. Each type can have a custom icon, color, sort, and visibility set via its type document in `type/`.
- **Note List / Pulse View** (200-500px, resizable): When a section group, filter, or saved view is selected, shows filtered notes with snippets, modified dates, status indicators, and per-context note-list controls. When `selection.kind === 'entity'`, the same pane enters **Neighborhood** mode: the source note is pinned at the top as a normal active row, outgoing relationship groups render first, inverse/backlink groups follow, empty groups stay visible with `0`, and duplicates across groups are allowed when multiple relationships are true. Plain click / `Enter` open the focused note without replacing the current Neighborhood, while Cmd/Ctrl-click and Cmd/Ctrl-`Enter` pivot the pane into the clicked note's Neighborhood. Folder-backed lists also show non-Markdown files: previewable image binaries get an image indicator and open in the editor pane, while unsupported binaries remain muted instead of auto-launching an external app. Saved views reuse the same sort and visible-column controls as the built-in lists, and those changes persist back into the view `.yml` definition (`sort`, `listPropertiesDisplay`). When Pulse filter is active, shows `PulseView` — a chronological git activity feed grouped by day.
- **Editor** (flex, fills remaining space): Single note open at a time (no tabs — see ADR-0003). Breadcrumb bar with word count and note-layout toggle, BlockNote rich text editor with wikilink support, Markdown-compatible inline/display math rendering, first-class Mermaid diagram blocks, markdown-safe formatting controls, and schema-backed fenced code block highlighting via `@blocknote/code-block`. Can toggle to diff view (modified files), raw CodeMirror view, or a wide-screen left-aligned note column while preserving the same readable max width. Binary image files render through `FilePreview` as ordinary vault files using Tauri asset URLs, with explicit unsupported/broken fallback states and keyboard focus returning to the note list on `Escape`. Decomposed into `Editor` (orchestrator), `EditorContent`, `FilePreview`, `EditorRightPanel`, `SingleEditorView`, with hooks `useDiffMode`, `useEditorFocus`, and `useEditorSave`, plus the `useRawMode`/`RawEditorView` pair for markdown source editing. Rich BlockNote input and raw CodeMirror input both route typed `->`, `<-`, and `<->` through the shared `src/utils/arrowLigatures.ts` resolver so arrow ligatures stay consistent across mode switches while escaped ASCII sequences remain literal. Navigation history (Cmd+[/]) replaces tabs.
- **Note List / Pulse View** (200-500px, resizable): When a section group, filter, or saved view is selected, shows filtered notes with snippets, modified dates, status indicators, and per-context note-list controls. When `selection.kind === 'entity'`, the same pane enters **Neighborhood** mode: the source note is pinned at the top as a normal active row, outgoing relationship groups render first, inverse/backlink groups follow, empty groups stay visible with `0`, and duplicates across groups are allowed when multiple relationships are true. Plain click / `Enter` open the focused note without replacing the current Neighborhood, while Cmd/Ctrl-click and Cmd/Ctrl-`Enter` pivot the pane into the clicked note's Neighborhood. Folder-backed lists also show non-Markdown files: previewable image and PDF binaries get file indicators and open in the editor pane, while unsupported binaries remain muted instead of auto-launching an external app. Saved views reuse the same sort and visible-column controls as the built-in lists, and those changes persist back into the view `.yml` definition (`sort`, `listPropertiesDisplay`). When Pulse filter is active, shows `PulseView` — a chronological git activity feed grouped by day.
- **Editor** (flex, fills remaining space): Single note open at a time (no tabs — see ADR-0003). Breadcrumb bar with word count and note-layout toggle, BlockNote rich text editor with wikilink support, Markdown-compatible inline/display math rendering, first-class Mermaid diagram blocks, markdown-safe formatting controls, and schema-backed fenced code block highlighting via `@blocknote/code-block`. Can toggle to diff view (modified files), raw CodeMirror view, or a wide-screen left-aligned note column while preserving the same readable max width. Binary image and PDF files render through `FilePreview` as ordinary vault files using Tauri asset URLs, with explicit unsupported/broken fallback states and keyboard focus returning to the note list on `Escape`. Decomposed into `Editor` (orchestrator), `EditorContent`, `FilePreview`, `EditorRightPanel`, `SingleEditorView`, with hooks `useDiffMode`, `useEditorFocus`, and `useEditorSave`, plus the `useRawMode`/`RawEditorView` pair for markdown source editing. Rich BlockNote input and raw CodeMirror input both route typed `->`, `<-`, and `<->` through the shared `src/utils/arrowLigatures.ts` resolver so arrow ligatures stay consistent across mode switches while escaped ASCII sequences remain literal. Navigation history (Cmd+[/]) replaces tabs.
- **Inspector / AI Agent** (200-500px or 40px collapsed): Toggles between Inspector (frontmatter, relationships, instances, backlinks, git history) and AI Agent panel (the selected CLI agent with tool execution). The Sparkle icon in the breadcrumb bar toggles between them. Per-note `icon` is a suggested Inspector property and the command palette's "Set Note Icon" action opens that field directly. When viewing a Type note, the Inspector shows an **Instances** section listing all notes of that type (sorted by modified_at desc, capped at 50).
Panels are separated by `ResizeHandle` components that support drag-to-resize.

View File

@@ -0,0 +1,28 @@
---
type: ADR
id: "0098"
title: "In-app image and PDF previews for binary vault files"
status: active
date: 2026-04-29
supersedes: "0086"
---
## Context
ADR-0086 introduced the `FilePreview` path for image binaries while keeping binary files as ordinary `VaultEntry` records. The same file-first model should now cover PDFs, because asset-heavy vaults often mix screenshots, diagrams, and document exports that users need to inspect without leaving Tolaria.
## Decision
**Tolaria previews supported image and PDF files in the editor pane while keeping them as ordinary binary vault files.**
- The scanner keeps the coarse `fileKind: "binary"` representation. Previewability stays a renderer concern inferred from the file extension in `src/utils/filePreview.ts`.
- Supported images render with `<img>` and supported PDFs render with the webview PDF object renderer, both using Tauri asset URLs from `convertFileSrc`.
- PDF preview fallback content lives inside the PDF object so unsupported or failed renderers still expose an explicit "Open in default app" escape hatch.
- Note-list rows for previewable images and PDFs remain clickable and carry file-specific indicators; unsupported binary rows stay muted and non-clickable.
- `Escape` on the preview surface returns keyboard focus to the note list, matching the existing image-preview keyboard behavior.
## Consequences
- PDFs do not become notes and do not get Markdown editor semantics.
- The asset preview surface can keep growing to additional safe binary formats without changing the vault scanner or persisted cache shape.
- Broken PDFs may rely on the webview's own renderer failure state, but the surrounding Tolaria preview chrome still provides reveal, copy path, and default-app actions.

View File

@@ -149,3 +149,4 @@ proposed → active → superseded
| [0094](0094-gitignored-content-visibility-boundary-filter.md) | Gitignored content visibility as a command-boundary filter | active |
| [0095](0095-saved-view-order-field.md) | Saved views use an explicit YAML order field | active |
| [0097](0097-gemini-cli-agent-adapter.md) | Gemini CLI agent adapter | active |
| [0098](0098-in-app-image-and-pdf-file-previews.md) | In-app image and PDF previews for binary vault files | active |

View File

@@ -14,6 +14,7 @@ files:
command.openLanguageSettings.keywords: 68efcb7e847b1a9d472baa609824be80
command.useSystemLanguage: b7f0315c47d4a59faa2a49571fcf1fca
command.openH1Setting: 05b4f6edc0e98b29670e8ee902cdb9bf
command.toggleGitignoredFilesVisibility: 0a2d3ea4e8ff8b00a801183caeb97a03
command.contribute: 9887a4451812854f0f1b6f669a874307
command.checkUpdates: f77f38f684c8b974dd4a56bb321cfd5f
command.group.navigation: 846495f9ceed11accf8879f555936a7d
@@ -70,6 +71,10 @@ files:
command.view.toggleAiPanel: 4279cbf31767465f25feff6e7bdd336e
command.view.newAiChat: 1b0a886d6e77f628ec96c2d71cdb0a9b
command.view.toggleBacklinks: b3c4be2d2c07bb8df181355a2c3658a7
command.view.moveViewUp: 76f92d3250e028609349d825c672f13e
command.view.moveViewDown: 9deea30162b8d3234f8d52795ad7393f
command.view.moveNamedViewUp: b4ac634d13a09eacb680ef12c69dd1f7
command.view.moveNamedViewDown: 15302c02df26fe52def77636e9141711
command.view.zoomIn: ae4a8e406b707636392b6673fca0e6a6
command.view.zoomOut: 97326f8cd9df886a6b19af180fb7dcc9
command.view.resetZoom: 193ee8c32391fc5cc303a51617cfd046
@@ -81,6 +86,9 @@ files:
command.settings.setupExternalAi: 24eea679eb699763daa2e3f2a67fcf9a
command.settings.reloadVault: f6e506dad6b6cf2be24d9438d458ae54
command.settings.repairVault: cee560b1fd5ad9d1ff1c19a0a2afe77a
command.settings.useLightMode: a76d5b1c076983bc113d247f0520c166
command.settings.useDarkMode: ba7873c42ae00542ba71db9be3b6761f
command.settings.toggleGitignoredFilesVisibility: 0a2d3ea4e8ff8b00a801183caeb97a03
command.ai.openAgents: 612abe804edd0f5ae228a534f77f3d23
command.ai.restoreGuidance: 23331fecc692d11d85cf24838ed273f2
command.ai.switchToAgent: 5bb02187e653b360ab7ed661403271f2
@@ -115,6 +123,10 @@ files:
settings.titles.description: b94aeeca78dd376cc19b9aa019e53f6c
settings.titles.autoRename: 5383db8e790ebf5f956d9c59cb4c4f67
settings.titles.autoRenameDescription: cc28c28d8817736e658082a2261d9a6e
settings.vaultContent.title: 78b883ce9acb9c25e611158a518d9185
settings.vaultContent.description: a53487bf1c7898a1459dc1510e216f7d
settings.vaultContent.hideGitignored: 2c07ee6c8bfe746d356f44275d42f90e
settings.vaultContent.hideGitignoredDescription: e8b1ddfa416610f9d6eb299fa123a1d6
settings.aiAgents.title: 27f08387b26e1ceeb1b60bd9c97e7a47
settings.aiAgents.description: a13222e67eb121676ff6dfc7b085f02d
settings.aiAgents.default: 6af573559fd05d8c35bc57b41cc78e24
@@ -138,6 +150,68 @@ files:
settings.cancel: ea4788705e6873b424c65e91c2846b19
settings.save: c9cc8cce247e49bae79f15173ce97354
common.cancel: ea4788705e6873b424c65e91c2846b19
ai.permission.safe.short: c6eea0560cd6f377e78dff2c85cc9122
ai.permission.safe.control: 386cc66605d50e5bf1fb726f97e55b55
ai.permission.safe.tooltip: 3bd635e2b2223da302994d77bfd7edfd
ai.permission.powerUser.short: d4a47db271fc1ef3cd17f7396326b057
ai.permission.powerUser.control: d4a47db271fc1ef3cd17f7396326b057
ai.permission.powerUser.tooltip: 2585ecdb79ca02a3ee779bb61552de03
ai.permission.modeAria: 3ff6346566a4c44fd5c7395907125d3e
ai.permission.changed: b5d06ad47e6053fe5a73f4571a40e00c
ai.panel.title: 4412225fe5b326643b8c2e1d9b7b10ae
ai.panel.status.checking: 118740af1c4521b917e29791d661db82
ai.panel.status.missing: fe07f8eac233c229d81cbe9aa25668a0
ai.panel.status.ready: 296a0ac791eab2df130789f55fdc109b
ai.panel.copyMcpConfig: 6a8c9fe401557c870e3a90bbfab01b25
ai.panel.mcpConfig: 53bac93d0314941c8d2c1163026f3ad9
ai.panel.newChat: 1b0a886d6e77f628ec96c2d71cdb0a9b
ai.panel.close: d00ba22b83762f5a6e66db0be9e916d7
ai.panel.linkedCount: 5292fda189a357c9597b1cb0e1b0900b
ai.panel.empty.checkingTitle: 0dde2076cb53fe497d05d231296d50bb
ai.panel.empty.checkingDescription: 31e41510d851c366860377ba0c956f0b
ai.panel.empty.missingTitle: a2b764da52cb43b191bb4ecfd8ea4d26
ai.panel.empty.missingDescription: 91825c7d86b1d588d44a33e1d94af950
ai.panel.empty.withContextTitle: 66f02985937083e4ef62b98cc9f25aee
ai.panel.empty.noContextTitle: e919aeb0a49b58e494e43e5f6f6597c9
ai.panel.empty.withContextDescription: dd8faefcc8160a2db800a41d6628facb
ai.panel.empty.noContextDescription: 6a6e610835808016d1f2790c3f991a54
ai.panel.placeholder.checking: 08fec77719c37d3d6279b2641b37b4d3
ai.panel.placeholder.missing: 547c492026d115608067988b7cfbb9a9
ai.panel.placeholder.ready: 31f5265c2e0432a7ca10d08e5c6f3114
ai.panel.send: 747c6a3564774149c989884e0c581d53
mcp.setup.manageTitle: d786c7b9ab9b2406258648931bca0e01
mcp.setup.setupTitle: b4b373f690c8a0008885b31e78e93a5c
mcp.setup.connectedDescription: a2a3be70c0ac9fa7ef112df5252249fa
mcp.setup.setupDescription: 8bf199ffbcf303a99abac078c0c5e094
mcp.setup.reconnect: 813ba447b5e64c17ff9b68c693358ca3
mcp.setup.connect: fb95777dbc9f7b22014dc360a2957652
mcp.setup.disconnect: 42ae25231906c83927831e0ef7c317ac
mcp.setup.connecting: 182e7eda4e721ad00737460b88701dee
mcp.setup.disconnecting: 11de134aefe51cb4a5c545b5a057a871
mcp.setup.nodeRequirement: ac58a35cf21d2204fed548c227f77594
mcp.setup.writeEntryDescription: 370aa4403d2ffc37f574503bc3cd00e6
mcp.setup.clientPathsDescription: 94565852412051c799e78d2f4525ff53
mcp.setup.geminiGuidanceDescription: 182cf5b84b2f56cb1cf41dcdb5e093d8
mcp.setup.manual.title: 2188ca52346158cbd103ca7ce7c12dd9
mcp.setup.manual.copy: 6a8c9fe401557c870e3a90bbfab01b25
mcp.setup.manual.loading: 11ee201c121c8e2015c065952474a3ea
mcp.setup.manual.unavailable: 8b4330b839c41bad378c86c91ee0b5cd
mcp.toast.connected: 7716c0f9dad41a7fdbef592074459239
mcp.toast.refreshed: 227d8721ebec3077792b1223c1f30e54
mcp.toast.disconnected: 9b5254079561087d679e35aa10c80638
mcp.toast.alreadyDisconnected: 645bec2e33bd9de2b334b704d9fa84cf
mcp.toast.configCopied: 458b50444b9a1cb069b00475c0951b8a
mcp.toast.configCopyFailed: b451cfb95f87458d142d026a8aca74cf
mcp.toast.setupFailed: 51f290962c08d8ba76bfc6c19552eaed
mcp.toast.disconnectFailed: ddc3387ab6e996ce15bc4950a3548e1e
mcp.error.clipboardUnavailable: 3b9ed3fe7dba627edb74e1cdaa02b2ee
save.toast.saved: 248336101b461380a4b2391a7625493d
save.toast.nothingToSave: 86ae8cef7371e639f3c007449a2f7d1f
save.toast.missingActiveVault: 49cf5064a7dce757f895950d8beeed68
save.error.failed: b55b9fefe93fabae3c277e4e7956a534
save.error.autoFailed: 09b2b131f07baddb5ba8f5a3ee6234c5
save.error.invalidPath: c96fb669715e86508f4c5e73da9f6995
savedViews.reordered: 531081a566b826eed2e714ce70a0de08
locale.en: 78463a384a5aa4fad5fa73e2f506ecfc
sidebar.nav.inbox: 3882d32c66e7e768145ecd8f104b0c08
sidebar.nav.allNotes: cd76cf851b08a9d2feafaf255bc1f4d5
@@ -149,6 +223,9 @@ files:
sidebar.action.createView: ba019414ea8c7fcdb4a83a70ec1bb639
sidebar.action.editView: acdf34bd08b0692b906d446e590b1eb9
sidebar.action.deleteView: c8f4f9cd8ff820f955474e5c9f70ff39
sidebar.action.reorderView: ca2f42548fd57160d22fd0809bf8ed6e
sidebar.action.moveViewUp: e601b7f0901f12efb71f7821c0fdb79e
sidebar.action.moveViewDown: 6f86951085367864e3e4eb13f5c8688e
sidebar.action.customizeSections: 050a134cad1e9c6f04abe5d635592703
sidebar.action.renameSection: 8936914051df04e7550d0eeb4a31e0e0
sidebar.action.customizeIconColor: 9a2f685c74d261ab483ef00dee5314fe
@@ -318,6 +395,8 @@ files:
status.vault.cloneGit: a7d0aef7c6237a36e54fd5a26e9c23fe
status.vault.cloneGettingStarted: 9953a11a477b441976a626a9acf5d7df
status.vault.notFound: 3119b7fa94c96209bca571b7c7ed0b7e
status.vault.reloading: 893da50ef3a9e01d8a99ff5d3ceb55e9
status.vault.reloadingTooltip: 4a884bd7d113797ad16f905f70742da8
status.vault.remove: 7f3b4f76df23626170940dbc55c70728
status.remote.noneConfigured: 18a4edd4e8d862ccb343937f45585fb1
status.remote.inSync: a56f571b4df55d88fb06e61e343b3c91

View File

@@ -469,7 +469,7 @@ function App() {
mcpConfigError,
loadMcpConfigSnippet,
copyMcpConfig,
} = useMcpStatus(resolvedPath, setToastMessage)
} = useMcpStatus(resolvedPath, setToastMessage, appLocale)
const gitRemoteStatus = useGitRemoteStatus(resolvedPath)
const loadVaultModifiedFiles = vault.loadModifiedFiles
const refreshGitRemoteStatus = gitRemoteStatus.refreshRemoteStatus
@@ -772,6 +772,7 @@ function App() {
replaceEntry: vault.replaceEntry, resolvedPath,
initialH1AutoRenameEnabled: settings.initial_h1_auto_rename_enabled !== false,
onInternalVaultWrite: recentVaultWrites.markInternalWrite,
locale: appLocale,
})
const aiActivity = useAiActivity({
@@ -1335,6 +1336,7 @@ function App() {
reloadViews: vault.reloadViews,
loadModifiedFiles: vault.loadModifiedFiles,
onToast: setToastMessage,
locale: appLocale,
})
const activeNoteModified = useMemo(
() => vault.modifiedFiles.some((file) => file.path === notes.activeTabPath),
@@ -1726,7 +1728,7 @@ function App() {
/>
<SettingsPanel open={dialogs.showSettings} settings={settings} aiAgentsStatus={aiAgentsStatus} locale={appLocale} systemLocale={systemLocale} isGitVault={isGitVault} onSave={saveSettings} explicitOrganizationEnabled={explicitOrganizationEnabled} onSaveExplicitOrganization={handleSaveExplicitOrganization} onClose={dialogs.closeSettings} />
<FeedbackDialog open={showFeedback} onClose={closeFeedback} />
<McpSetupDialog open={showMcpSetupDialog} status={mcpStatus} busyAction={mcpDialogAction} manualConfigSnippet={mcpConfigSnippet} manualConfigLoading={mcpConfigLoading} manualConfigError={mcpConfigError} onClose={closeMcpSetupDialog} onConnect={handleConnectMcp} onCopyManualConfig={handleCopyMcpConfig} onDisconnect={handleDisconnectMcp} onLoadManualConfig={handleLoadMcpConfigSnippet} />
<McpSetupDialog open={showMcpSetupDialog} status={mcpStatus} busyAction={mcpDialogAction} manualConfigSnippet={mcpConfigSnippet} manualConfigLoading={mcpConfigLoading} manualConfigError={mcpConfigError} locale={appLocale} onClose={closeMcpSetupDialog} onConnect={handleConnectMcp} onCopyManualConfig={handleCopyMcpConfig} onDisconnect={handleDisconnectMcp} onLoadManualConfig={handleLoadMcpConfigSnippet} />
<CloneVaultModal key={dialogs.showCloneVault ? 'clone-open' : 'clone-closed'} open={dialogs.showCloneVault} onClose={dialogs.closeCloneVault} onVaultCloned={vaultSwitcher.handleVaultCloned} />
{deleteActions.confirmDelete && (
<ConfirmDeleteDialog

View File

@@ -11,6 +11,7 @@ import {
type AiAgentId,
type AiAgentReadiness,
} from '../lib/aiAgents'
import type { AppLocale } from '../lib/i18n'
import { type NoteListItem } from '../utils/ai-context'
import type { VaultEntry } from '../types'
import { useAiPanelController, type AiPanelController } from './useAiPanelController'
@@ -27,6 +28,7 @@ interface AiPanelProps {
defaultAiAgent?: AiAgentId
defaultAiAgentReadiness?: AiAgentReadiness
defaultAiAgentReady?: boolean
locale?: AppLocale
onFileCreated?: (relativePath: string) => void
onFileModified?: (relativePath: string) => void
onVaultChanged?: () => void
@@ -49,6 +51,7 @@ interface AiPanelViewProps {
defaultAiAgent?: AiAgentId
defaultAiAgentReadiness?: AiAgentReadiness
defaultAiAgentReady?: boolean
locale?: AppLocale
activeEntry?: VaultEntry | null
entries?: VaultEntry[]
}
@@ -66,6 +69,7 @@ export function AiPanelView({
defaultAiAgent: providedDefaultAiAgent,
defaultAiAgentReadiness: providedDefaultAiAgentReadiness,
defaultAiAgentReady: providedDefaultAiAgentReady,
locale = 'en',
activeEntry,
entries,
}: AiPanelViewProps) {
@@ -117,6 +121,7 @@ export function AiPanelView({
<AiPanelHeader
agentLabel={agentLabel}
agentReadiness={defaultAiAgentReadiness}
locale={locale}
permissionMode={permissionMode}
permissionModeDisabled={isActive}
onPermissionModeChange={handlePermissionModeChange}
@@ -125,11 +130,12 @@ export function AiPanelView({
onNewChat={handleNewChat}
/>
{activeEntry && (
<AiPanelContextBar activeEntry={activeEntry} linkedCount={linkedEntries.length} />
<AiPanelContextBar activeEntry={activeEntry} linkedCount={linkedEntries.length} locale={locale} />
)}
<AiPanelMessageHistory
agentLabel={agentLabel}
agentReadiness={defaultAiAgentReadiness}
locale={locale}
messages={agent.messages}
isActive={isActive}
onOpenNote={onOpenNote}
@@ -140,6 +146,7 @@ export function AiPanelView({
entries={entries ?? []}
agentLabel={agentLabel}
agentReadiness={defaultAiAgentReadiness}
locale={locale}
input={input}
inputRef={inputRef}
isActive={isActive}
@@ -159,6 +166,7 @@ export function AiPanel({
defaultAiAgent: providedDefaultAiAgent,
defaultAiAgentReadiness: providedDefaultAiAgentReadiness,
defaultAiAgentReady: providedDefaultAiAgentReady,
locale = 'en',
onFileCreated,
onFileModified,
onVaultChanged,
@@ -183,6 +191,7 @@ export function AiPanel({
openTabs,
noteList,
noteListFilter,
locale,
onOpenNote,
onFileCreated,
onFileModified,
@@ -199,6 +208,7 @@ export function AiPanel({
defaultAiAgent={providedDefaultAiAgent}
defaultAiAgentReadiness={defaultAiAgentReadiness}
defaultAiAgentReady={providedDefaultAiAgentReady}
locale={locale}
activeEntry={activeEntry}
entries={entries}
/>

View File

@@ -8,26 +8,19 @@ import { TooltipProvider } from '@/components/ui/tooltip'
import { WikilinkChatInput } from './WikilinkChatInput'
import { extractInlineWikilinkReferences } from './inlineWikilinkText'
import {
AI_AGENT_PERMISSION_MODE_LABELS,
aiAgentPermissionModeLabels,
type AiAgentPermissionMode,
} from '../lib/aiAgentPermissionMode'
import { createTranslator, type AppLocale } from '../lib/i18n'
import type { AiAgentMessage } from '../hooks/useCliAiAgent'
import type { AiAgentReadiness } from '../lib/aiAgents'
import type { NoteReference } from '../utils/ai-context'
import type { VaultEntry } from '../types'
const AI_PERMISSION_MODE_TOOLTIPS: Record<AiAgentPermissionMode, { label: string }> = {
safe: {
label: 'Vault Safe keeps agents limited to file, search, and edit tools.',
},
power_user: {
label: 'Power User also allows local shell commands for this vault.',
},
}
interface AiPanelHeaderProps {
agentLabel: string
agentReadiness: AiAgentReadiness
locale?: AppLocale
permissionMode: AiAgentPermissionMode
permissionModeDisabled: boolean
onPermissionModeChange: (mode: AiAgentPermissionMode) => void
@@ -38,12 +31,14 @@ interface AiPanelHeaderProps {
interface AiPanelContextBarProps {
activeEntry: VaultEntry
locale?: AppLocale
linkedCount: number
}
interface AiPanelMessageHistoryProps {
agentLabel: string
agentReadiness: AiAgentReadiness
locale?: AppLocale
messages: AiAgentMessage[]
isActive: boolean
onOpenNote?: (path: string) => void
@@ -55,6 +50,7 @@ interface AiPanelComposerProps {
entries: VaultEntry[]
agentLabel: string
agentReadiness: AiAgentReadiness
locale?: AppLocale
input: string
inputRef: React.RefObject<HTMLDivElement | null>
isActive: boolean
@@ -66,23 +62,54 @@ interface AiPanelComposerProps {
function getComposerPlaceholder(
agentLabel: string,
agentReadiness: AiAgentReadiness,
t: ReturnType<typeof createTranslator>,
): string {
if (agentReadiness === 'checking') {
return 'Checking AI agent availability...'
return t('ai.panel.placeholder.checking')
}
if (agentReadiness === 'missing') {
return `${agentLabel} is not installed. Open AI Agents in Settings.`
return t('ai.panel.placeholder.missing', { agent: agentLabel })
}
return `Ask ${agentLabel}`
return t('ai.panel.placeholder.ready', { agent: agentLabel })
}
function permissionModeTooltip(
mode: AiAgentPermissionMode,
t: ReturnType<typeof createTranslator>,
): { label: string } {
return {
label: t(mode === 'power_user'
? 'ai.permission.powerUser.tooltip'
: 'ai.permission.safe.tooltip'),
}
}
function headerStatusText({
agentLabel,
agentReadiness,
modeLabel,
t,
}: {
agentLabel: string
agentReadiness: AiAgentReadiness
modeLabel: string
t: ReturnType<typeof createTranslator>
}): string {
if (agentReadiness === 'checking') return t('ai.panel.status.checking')
if (agentReadiness === 'missing') return t('ai.panel.status.missing', { agent: agentLabel })
return t('ai.panel.status.ready', { agent: agentLabel, mode: modeLabel })
}
function AiPanelEmptyState({
agentLabel,
agentReadiness,
hasContext,
}: Pick<AiPanelMessageHistoryProps, 'agentLabel' | 'agentReadiness' | 'hasContext'>) {
locale = 'en',
}: Pick<AiPanelMessageHistoryProps, 'agentLabel' | 'agentReadiness' | 'hasContext' | 'locale'>) {
const t = createTranslator(locale)
if (agentReadiness === 'checking') {
return (
<div
@@ -91,10 +118,10 @@ function AiPanelEmptyState({
>
<Robot size={24} style={{ marginBottom: 8, opacity: 0.5 }} />
<p style={{ fontSize: 13, margin: '0 0 4px' }}>
Checking AI agent availability
{t('ai.panel.empty.checkingTitle')}
</p>
<p style={{ fontSize: 11, margin: 0, opacity: 0.6 }}>
Messages can be sent when the selected agent is ready
{t('ai.panel.empty.checkingDescription')}
</p>
</div>
)
@@ -108,10 +135,10 @@ function AiPanelEmptyState({
>
<Robot size={24} style={{ marginBottom: 8, opacity: 0.5 }} />
<p style={{ fontSize: 13, margin: '0 0 4px' }}>
{agentLabel} is not available on this machine
{t('ai.panel.empty.missingTitle', { agent: agentLabel })}
</p>
<p style={{ fontSize: 11, margin: 0, opacity: 0.6 }}>
Install it or switch the default AI agent in Settings
{t('ai.panel.empty.missingDescription')}
</p>
</div>
)
@@ -125,14 +152,14 @@ function AiPanelEmptyState({
<Robot size={24} style={{ marginBottom: 8, opacity: 0.5 }} />
<p style={{ fontSize: 13, margin: '0 0 4px' }}>
{hasContext
? `Ask ${agentLabel} about this note and its linked context`
: `Open a note, then ask ${agentLabel} about it`
? t('ai.panel.empty.withContextTitle', { agent: agentLabel })
: t('ai.panel.empty.noContextTitle', { agent: agentLabel })
}
</p>
<p style={{ fontSize: 11, margin: 0, opacity: 0.6 }}>
{hasContext
? 'Summarize, find connections, expand ideas'
: 'The AI will use the active note as context'
? t('ai.panel.empty.withContextDescription')
: t('ai.panel.empty.noContextDescription')
}
</p>
</div>
@@ -142,6 +169,7 @@ function AiPanelEmptyState({
export function AiPanelHeader({
agentLabel,
agentReadiness,
locale = 'en',
permissionMode,
permissionModeDisabled,
onPermissionModeChange,
@@ -149,7 +177,8 @@ export function AiPanelHeader({
onCopyMcpConfig,
onNewChat,
}: AiPanelHeaderProps) {
const modeLabel = AI_AGENT_PERMISSION_MODE_LABELS[permissionMode].short
const t = createTranslator(locale)
const modeLabel = aiAgentPermissionModeLabels(permissionMode, locale).short
return (
<div
@@ -160,12 +189,10 @@ export function AiPanelHeader({
<Robot size={16} className="shrink-0 text-muted-foreground" />
<div className="flex flex-1 flex-col overflow-hidden">
<span className="text-muted-foreground" style={{ fontSize: 13, fontWeight: 600 }}>
AI Agent
{t('ai.panel.title')}
</span>
<span className="truncate text-[11px] text-muted-foreground">
{agentReadiness === 'checking'
? 'Checking availability'
: `${agentLabel}${agentReadiness === 'missing' ? ' · not installed' : ` · ${modeLabel}`}`}
{headerStatusText({ agentLabel, agentReadiness, modeLabel, t })}
</span>
</div>
{onCopyMcpConfig ? (
@@ -175,12 +202,12 @@ export function AiPanelHeader({
size="xs"
onClick={onCopyMcpConfig}
className="h-7 gap-1.5 px-2 text-[11px]"
aria-label="Copy MCP config"
title="Copy MCP config"
aria-label={t('ai.panel.copyMcpConfig')}
title={t('ai.panel.copyMcpConfig')}
data-testid="ai-copy-mcp-config"
>
<Copy size={15} />
<span>MCP config</span>
<span>{t('ai.panel.mcpConfig')}</span>
</Button>
) : null}
<Button
@@ -188,8 +215,8 @@ export function AiPanelHeader({
variant="ghost"
size="icon-xs"
onClick={onNewChat}
aria-label="New AI chat"
title="New AI chat"
aria-label={t('ai.panel.newChat')}
title={t('ai.panel.newChat')}
>
<Plus size={16} />
</Button>
@@ -198,14 +225,15 @@ export function AiPanelHeader({
variant="ghost"
size="icon-xs"
onClick={onClose}
aria-label="Close AI panel"
title="Close AI panel"
aria-label={t('ai.panel.close')}
title={t('ai.panel.close')}
>
<X size={16} />
</Button>
</div>
<AiPermissionModeToggle
value={permissionMode}
locale={locale}
disabled={permissionModeDisabled}
onChange={onPermissionModeChange}
/>
@@ -215,19 +243,23 @@ export function AiPanelHeader({
function AiPermissionModeToggle({
value,
locale = 'en',
disabled,
onChange,
}: {
value: AiAgentPermissionMode
locale?: AppLocale
disabled: boolean
onChange: (mode: AiAgentPermissionMode) => void
}) {
const t = createTranslator(locale)
return (
<TooltipProvider>
<div
className="inline-flex w-full rounded-md border border-border bg-muted p-1"
role="radiogroup"
aria-label="AI agent permission mode"
aria-label={t('ai.permission.modeAria')}
data-testid="ai-permission-mode-toggle"
>
{(['safe', 'power_user'] as const).map((mode) => {
@@ -235,7 +267,7 @@ function AiPermissionModeToggle({
return (
<ActionTooltip
key={mode}
copy={AI_PERMISSION_MODE_TOOLTIPS[mode]}
copy={permissionModeTooltip(mode, t)}
side="bottom"
contentTestId="ai-permission-mode-tooltip"
>
@@ -253,7 +285,7 @@ function AiPermissionModeToggle({
}
onClick={() => onChange(mode)}
>
{AI_AGENT_PERMISSION_MODE_LABELS[mode].control}
{aiAgentPermissionModeLabels(mode, locale).control}
</Button>
</ActionTooltip>
)
@@ -263,7 +295,9 @@ function AiPermissionModeToggle({
)
}
export function AiPanelContextBar({ activeEntry, linkedCount }: AiPanelContextBarProps) {
export function AiPanelContextBar({ activeEntry, linkedCount, locale = 'en' }: AiPanelContextBarProps) {
const t = createTranslator(locale)
return (
<div
className="flex shrink-0 items-center border-b border-border text-muted-foreground"
@@ -273,7 +307,7 @@ export function AiPanelContextBar({ activeEntry, linkedCount }: AiPanelContextBa
<Link size={12} className="shrink-0" />
<span className="truncate" style={{ fontWeight: 500 }}>{activeEntry.title}</span>
{linkedCount > 0 && (
<span style={{ opacity: 0.6 }}>+ {linkedCount} linked</span>
<span style={{ opacity: 0.6 }}>{t('ai.panel.linkedCount', { count: linkedCount })}</span>
)}
</div>
)
@@ -282,6 +316,7 @@ export function AiPanelContextBar({ activeEntry, linkedCount }: AiPanelContextBa
export function AiPanelMessageHistory({
agentLabel,
agentReadiness,
locale = 'en',
messages,
isActive,
onOpenNote,
@@ -300,6 +335,7 @@ export function AiPanelMessageHistory({
<AiPanelEmptyState
agentLabel={agentLabel}
agentReadiness={agentReadiness}
locale={locale}
hasContext={hasContext}
/>
)}
@@ -320,6 +356,7 @@ export function AiPanelComposer({
entries,
agentLabel,
agentReadiness,
locale = 'en',
input,
inputRef,
isActive,
@@ -327,9 +364,10 @@ export function AiPanelComposer({
onSend,
onUnsupportedAiPaste,
}: AiPanelComposerProps) {
const t = createTranslator(locale)
const composerDisabled = isActive || agentReadiness !== 'ready'
const canSend = !composerDisabled && input.trim().length > 0
const placeholder = getComposerPlaceholder(agentLabel, agentReadiness)
const placeholder = getComposerPlaceholder(agentLabel, agentReadiness, t)
const sendButtonStyle = {
background: canSend ? 'var(--primary)' : 'var(--muted)',
color: canSend ? 'var(--primary-foreground)' : 'var(--muted-foreground)',
@@ -365,8 +403,8 @@ export function AiPanelComposer({
style={sendButtonStyle}
onClick={() => onSend(input, extractInlineWikilinkReferences(input, entries))}
disabled={!canSend}
aria-label="Send message"
title="Send message"
aria-label={t('ai.panel.send')}
title={t('ai.panel.send')}
data-testid="agent-send"
>
<PaperPlaneRight size={16} />

View File

@@ -284,6 +284,28 @@ describe('Editor', () => {
expect(screen.queryByTestId('blocknote-view')).not.toBeInTheDocument()
})
it('renders an in-app PDF preview for binary PDF tabs', () => {
const pdfEntry: VaultEntry = {
...mockEntry,
path: '/vault/assets/report.pdf',
filename: 'report.pdf',
title: 'report.pdf',
fileKind: 'binary',
}
renderEditor({
tabs: [{ entry: pdfEntry, content: '' }],
activeTabPath: pdfEntry.path,
entries: [pdfEntry],
})
expect(screen.getByTestId('pdf-file-preview')).toHaveAttribute(
'data',
'asset://localhost/%2Fvault%2Fassets%2Freport.pdf',
)
expect(screen.queryByTestId('blocknote-view')).not.toBeInTheDocument()
})
it('shows a graceful fallback when an image preview fails to render', () => {
const imageEntry: VaultEntry = {
...mockEntry,

View File

@@ -64,6 +64,7 @@ export function EditorRightPanel({
entries,
noteList,
noteListFilter,
locale,
onOpenNote,
onFileCreated,
onFileModified,
@@ -95,6 +96,7 @@ export function EditorRightPanel({
defaultAiAgent={defaultAiAgent}
defaultAiAgentReadiness={defaultAiAgentReadiness}
defaultAiAgentReady={defaultAiAgentReady}
locale={locale}
activeEntry={inspectorEntry}
entries={entries}
/>

View File

@@ -40,6 +40,12 @@ const imageEntry: VaultEntry = {
hasH1: false,
fileKind: 'binary',
}
const pdfEntry: VaultEntry = {
...imageEntry,
path: '/vault/Attachments/report.pdf',
filename: 'report.pdf',
title: 'report.pdf',
}
describe('FilePreview', () => {
it('routes header file actions to the active file path', () => {
@@ -64,4 +70,24 @@ describe('FilePreview', () => {
expect(onCopyFilePath).toHaveBeenCalledWith('/vault/Attachments/photo.png')
expect(onOpenExternalFile).toHaveBeenCalledWith('/vault/Attachments/photo.png')
})
it('renders supported PDF files through the asset preview path', () => {
render(<FilePreview entry={pdfEntry} />)
expect(screen.getByTestId('pdf-file-preview')).toHaveAttribute('data', 'asset:///vault/Attachments/report.pdf')
expect(screen.getByText('PDF file')).toBeInTheDocument()
})
it('renders supported PDFs when binary metadata is unavailable', () => {
render(<FilePreview entry={{ ...pdfEntry, fileKind: undefined }} />)
expect(screen.getByTestId('pdf-file-preview')).toHaveAttribute('data', 'asset:///vault/Attachments/report.pdf')
})
it('provides a graceful fallback when a PDF preview cannot render', () => {
render(<FilePreview entry={pdfEntry} />)
expect(screen.getByTestId('file-preview-fallback')).toHaveTextContent('PDF preview failed')
expect(screen.getByRole('button', { name: 'Open in default app' })).toBeInTheDocument()
})
})

View File

@@ -1,8 +1,8 @@
import { useCallback, useMemo, useState, type KeyboardEvent } from 'react'
import { convertFileSrc } from '@tauri-apps/api/core'
import { ArrowSquareOut, ClipboardText, FileDashed, FolderOpen, ImageSquare, WarningCircle } from '@phosphor-icons/react'
import { ArrowSquareOut, ClipboardText, FileDashed, FilePdf, FolderOpen, ImageSquare, WarningCircle } from '@phosphor-icons/react'
import type { VaultEntry } from '../types'
import { isImagePreviewEntry, previewFileTypeLabel } from '../utils/filePreview'
import { filePreviewKind, previewFileTypeLabel, type FilePreviewKind } from '../utils/filePreview'
import { focusNoteListContainer } from '../utils/neighborhoodHistory'
import { openLocalFile } from '../utils/url'
import { Button } from './ui/button'
@@ -21,6 +21,42 @@ interface FilePreviewFallbackProps {
onOpenExternal: () => void
}
function fallbackContentForPreviewKind(previewKind: FilePreviewKind | null): Omit<FilePreviewFallbackProps, 'onOpenExternal'> {
if (previewKind === 'image') {
return {
icon: 'warning',
title: 'Image preview failed',
description: 'Tolaria could not render this image file in the preview.',
}
}
if (previewKind === 'pdf') {
return {
icon: 'warning',
title: 'PDF preview failed',
description: 'Tolaria could not render this PDF file in the preview.',
}
}
return {
icon: 'file',
title: 'Preview unavailable',
description: 'Tolaria does not have an in-app preview for this file type.',
}
}
function FilePreviewHeaderIcon({ previewKind }: { previewKind: FilePreviewKind | null }) {
if (previewKind === 'image') {
return <ImageSquare size={17} className="shrink-0 text-muted-foreground" aria-hidden="true" />
}
if (previewKind === 'pdf') {
return <FilePdf size={17} className="shrink-0 text-muted-foreground" aria-hidden="true" />
}
return <FileDashed size={17} className="shrink-0 text-muted-foreground" aria-hidden="true" />
}
function FilePreviewFallback({ icon, title, description, onOpenExternal }: FilePreviewFallbackProps) {
const Icon = icon === 'warning' ? WarningCircle : FileDashed
@@ -44,28 +80,26 @@ function FilePreviewFallback({ icon, title, description, onOpenExternal }: FileP
function FilePreviewHeader({
entry,
isImage,
previewKind,
fileTypeLabel,
onOpenExternal,
onRevealFile,
onCopyFilePath,
}: {
entry: VaultEntry
isImage: boolean
previewKind: FilePreviewKind | null
fileTypeLabel: string
onOpenExternal: () => void
onRevealFile?: () => void
onCopyFilePath?: () => void
}) {
const HeaderIcon = isImage ? ImageSquare : FileDashed
return (
<div
className="flex h-[52px] shrink-0 items-center justify-between border-b border-border px-4"
data-tauri-drag-region
>
<div className="flex min-w-0 items-center gap-2">
<HeaderIcon size={17} className="shrink-0 text-muted-foreground" aria-hidden="true" />
<FilePreviewHeaderIcon previewKind={previewKind} />
<div className="min-w-0">
<h1 className="m-0 truncate text-[14px] font-semibold text-foreground">{entry.title}</h1>
<p className="m-0 text-[11px] text-muted-foreground">{fileTypeLabel}</p>
@@ -93,6 +127,35 @@ function FilePreviewHeader({
)
}
function FilePreviewPdf({
entry,
pdfSrc,
onOpenExternal,
}: {
entry: VaultEntry
pdfSrc: string
onOpenExternal: () => void
}) {
const fallback = fallbackContentForPreviewKind('pdf')
return (
<object
data={pdfSrc}
type="application/pdf"
title={entry.title}
className="h-full min-h-[320px] w-full bg-background"
data-testid="pdf-file-preview"
>
<FilePreviewFallback
icon={fallback.icon}
title={fallback.title}
description={fallback.description}
onOpenExternal={onOpenExternal}
/>
</object>
)
}
function FilePreviewImage({
entry,
imageSrc,
@@ -121,32 +184,34 @@ function shouldRenderImagePreview(isImage: boolean, imageSrc: string | null, ima
function FilePreviewBody({
entry,
isImage,
imageSrc,
previewKind,
assetSrc,
imageFailed,
onImageError,
onOpenExternal,
}: {
entry: VaultEntry
isImage: boolean
imageSrc: string | null
previewKind: FilePreviewKind | null
assetSrc: string | null
imageFailed: boolean
onImageError: () => void
onOpenExternal: () => void
}) {
if (shouldRenderImagePreview(isImage, imageSrc, imageFailed)) {
return <FilePreviewImage entry={entry} imageSrc={imageSrc} onImageError={onImageError} />
if (shouldRenderImagePreview(previewKind === 'image', assetSrc, imageFailed)) {
return <FilePreviewImage entry={entry} imageSrc={assetSrc} onImageError={onImageError} />
}
if (previewKind === 'pdf' && assetSrc !== null) {
return <FilePreviewPdf entry={entry} pdfSrc={assetSrc} onOpenExternal={onOpenExternal} />
}
const fallback = fallbackContentForPreviewKind(previewKind)
return (
<FilePreviewFallback
icon={isImage ? 'warning' : 'file'}
title={isImage ? 'Image preview failed' : 'Preview unavailable'}
description={
isImage
? 'Tolaria could not render this image file in the preview.'
: 'Tolaria does not have an in-app preview for this file type.'
}
icon={fallback.icon}
title={fallback.title}
description={fallback.description}
onOpenExternal={onOpenExternal}
/>
)
@@ -158,11 +223,12 @@ export function FilePreview({
onOpenExternalFile,
onRevealFile,
}: FilePreviewProps) {
const [imageFailed, setImageFailed] = useState(false)
const isImage = isImagePreviewEntry(entry)
const imageSrc = useMemo(() => (isImage ? convertFileSrc(entry.path) : null), [entry.path, isImage])
const [failedImagePath, setFailedImagePath] = useState<string | null>(null)
const previewKind = filePreviewKind(entry)
const assetSrc = useMemo(() => (previewKind ? convertFileSrc(entry.path) : null), [entry.path, previewKind])
const fileTypeLabel = previewFileTypeLabel(entry)
const handleImageError = useCallback(() => setImageFailed(true), [])
const imageFailed = failedImagePath === entry.path
const handleImageError = useCallback(() => setFailedImagePath(entry.path), [entry.path])
const handleOpenExternal = useCallback(() => {
if (onOpenExternalFile) {
@@ -200,7 +266,7 @@ export function FilePreview({
>
<FilePreviewHeader
entry={entry}
isImage={isImage}
previewKind={previewKind}
fileTypeLabel={fileTypeLabel}
onOpenExternal={handleOpenExternal}
onRevealFile={onRevealFile ? handleRevealFile : undefined}
@@ -209,8 +275,8 @@ export function FilePreview({
<div className="min-h-0 flex-1 overflow-auto bg-background">
<FilePreviewBody
entry={entry}
isImage={isImage}
imageSrc={imageSrc}
previewKind={previewKind}
assetSrc={assetSrc}
imageFailed={imageFailed}
onImageError={handleImageError}
onOpenExternal={handleOpenExternal}

View File

@@ -36,13 +36,14 @@ describe('McpSetupDialog', () => {
expect(screen.getByTestId('mcp-config-snippet')).toHaveTextContent('"type": "stdio"')
expect(screen.getByTestId('mcp-config-snippet')).toHaveTextContent('"VAULT_PATH": "/Users/luca/Laputa"')
expect(screen.getByTestId('mcp-config-snippet')).toHaveTextContent('"WS_UI_PORT": "9711"')
expect(screen.getAllByText('~/.claude.json')).toHaveLength(2)
expect(screen.getByText('~/.claude.json')).toBeInTheDocument()
expect(screen.getByText('~/.claude/mcp.json')).toBeInTheDocument()
expect(screen.getAllByText('~/.gemini/settings.json')).toHaveLength(2)
expect(screen.getAllByText('~/.config/mcp/mcp.json')).toHaveLength(2)
expect(screen.getByText('~/.gemini/settings.json')).toBeInTheDocument()
expect(screen.getByText('~/.config/mcp/mcp.json')).toBeInTheDocument()
expect(screen.getByText(/Claude Code CLI reads ~\/\.claude\.json/i)).toBeInTheDocument()
expect(screen.getByText(/picked up by other MCP-compatible tools/i)).toBeInTheDocument()
expect(screen.getByText(/Gemini CLI needs its own install and sign-in/i)).toBeInTheDocument()
expect(screen.getByText('GEMINI.md')).toBeInTheDocument()
expect(screen.getByText(/GEMINI\.md/)).toBeInTheDocument()
expect(screen.getByTestId('mcp-setup-connect')).toHaveTextContent('Connect External AI Tools')
expect(screen.queryByTestId('mcp-setup-disconnect')).not.toBeInTheDocument()
})

View File

@@ -10,6 +10,7 @@ import {
DialogTitle,
} from '@/components/ui/dialog'
import type { McpStatus } from '../hooks/useMcpStatus'
import { createTranslator, type AppLocale } from '../lib/i18n'
interface McpSetupDialogProps {
open: boolean
@@ -18,6 +19,7 @@ interface McpSetupDialogProps {
manualConfigError?: string | null
manualConfigLoading?: boolean
manualConfigSnippet?: string | null
locale?: AppLocale
onClose: () => void
onConnect: () => void
onCopyManualConfig?: () => void
@@ -28,6 +30,7 @@ interface McpSetupDialogProps {
interface ManualMcpConfigSectionProps {
error?: string | null
loading: boolean
locale?: AppLocale
onCopy?: () => void
snippet?: string | null
}
@@ -36,6 +39,7 @@ interface McpSetupActionsProps {
buttonsDisabled: boolean
connectBusy: boolean
disconnectBusy: boolean
locale?: AppLocale
primaryLabel: string
secondaryLabel: string | null
onClose: () => void
@@ -47,34 +51,39 @@ function isConnected(status: McpStatus): boolean {
return status === 'installed'
}
function actionCopy(status: McpStatus) {
function actionCopy(status: McpStatus, t: ReturnType<typeof createTranslator>) {
if (isConnected(status)) {
return {
description: 'Tolaria is already connected to external AI tools for this vault. Reconnect to refresh the configuration, or disconnect to remove Tolaria from those third-party config files.',
primaryLabel: 'Reconnect External AI Tools',
secondaryLabel: 'Disconnect',
title: 'Manage External AI Tools',
description: t('mcp.setup.connectedDescription'),
primaryLabel: t('mcp.setup.reconnect'),
secondaryLabel: t('mcp.setup.disconnect'),
title: t('mcp.setup.manageTitle'),
}
}
return {
description: 'Tolaria can add its MCP server to external AI tools for this vault, but it will not touch third-party config files until you confirm here.',
primaryLabel: 'Connect External AI Tools',
description: t('mcp.setup.setupDescription'),
primaryLabel: t('mcp.setup.connect'),
secondaryLabel: null,
title: 'Set Up External AI Tools',
title: t('mcp.setup.setupTitle'),
}
}
function manualConfigText({ error, loading, snippet }: ManualMcpConfigSectionProps): string {
if (loading) return 'Loading exact MCP config...'
return error ?? snippet ?? 'Exact config is available after a vault is open.'
function manualConfigText(
{ error, loading, snippet }: ManualMcpConfigSectionProps,
t: ReturnType<typeof createTranslator>,
): string {
if (loading) return t('mcp.setup.manual.loading')
return error ?? snippet ?? t('mcp.setup.manual.unavailable')
}
function ManualMcpConfigSection(props: ManualMcpConfigSectionProps) {
const t = createTranslator(props.locale)
return (
<div className="space-y-2">
<div className="flex items-center justify-between gap-2">
<p className="m-0 text-sm font-medium text-foreground">Manual MCP config</p>
<p className="m-0 text-sm font-medium text-foreground">{t('mcp.setup.manual.title')}</p>
<Button
type="button"
variant="outline"
@@ -84,7 +93,7 @@ function ManualMcpConfigSection(props: ManualMcpConfigSectionProps) {
data-testid="mcp-copy-config"
>
<Copy size={14} />
Copy MCP config
{t('mcp.setup.manual.copy')}
</Button>
</div>
<pre
@@ -92,7 +101,7 @@ function ManualMcpConfigSection(props: ManualMcpConfigSectionProps) {
className="max-h-48 overflow-auto rounded-md border border-border bg-background px-3 py-3 font-mono text-xs leading-5 text-foreground"
data-testid="mcp-config-snippet"
>
{manualConfigText(props)}
{manualConfigText(props, t)}
</pre>
</div>
)
@@ -102,16 +111,19 @@ function McpSetupActions({
buttonsDisabled,
connectBusy,
disconnectBusy,
locale = 'en',
primaryLabel,
secondaryLabel,
onClose,
onConnect,
onDisconnect,
}: McpSetupActionsProps) {
const t = createTranslator(locale)
return (
<DialogFooter className="flex-row items-center justify-end gap-2 sm:justify-end">
<Button type="button" variant="outline" onClick={onClose} disabled={buttonsDisabled}>
Cancel
{t('common.cancel')}
</Button>
{secondaryLabel ? (
<Button
@@ -121,7 +133,7 @@ function McpSetupActions({
disabled={buttonsDisabled}
data-testid="mcp-setup-disconnect"
>
{disconnectBusy ? 'Disconnecting' : secondaryLabel}
{disconnectBusy ? t('mcp.setup.disconnecting') : secondaryLabel}
</Button>
) : null}
<Button
@@ -131,7 +143,7 @@ function McpSetupActions({
disabled={buttonsDisabled}
data-testid="mcp-setup-connect"
>
{connectBusy ? 'Connecting' : primaryLabel}
{connectBusy ? t('mcp.setup.connecting') : primaryLabel}
</Button>
</DialogFooter>
)
@@ -144,13 +156,15 @@ export function McpSetupDialog({
manualConfigError,
manualConfigLoading = false,
manualConfigSnippet,
locale = 'en',
onClose,
onConnect,
onCopyManualConfig,
onDisconnect,
onLoadManualConfig,
}: McpSetupDialogProps) {
const copy = actionCopy(status)
const t = createTranslator(locale)
const copy = actionCopy(status, t)
const connectBusy = busyAction === 'connect'
const disconnectBusy = busyAction === 'disconnect'
const buttonsDisabled = busyAction !== null || status === 'checking'
@@ -172,10 +186,10 @@ export function McpSetupDialog({
<div className="space-y-3 text-sm leading-6 text-muted-foreground">
<p>
This setup requires Node.js 18+ on PATH and an MCP-compatible desktop tool. Tolaria checks Node.js before writing config so the tool is not left pointing at a broken command.
{t('mcp.setup.nodeRequirement')}
</p>
<p>
Confirming this action will write or update Tolaria&apos;s single <code className="rounded bg-muted px-1 py-0.5 text-xs">tolaria</code> MCP entry in:
{t('mcp.setup.writeEntryDescription', { entry: 'tolaria' })}
</p>
<div className="rounded-md border border-border bg-muted/30 px-3 py-3 font-mono text-xs text-foreground">
<div>~/.claude.json</div>
@@ -187,14 +201,15 @@ export function McpSetupDialog({
<ManualMcpConfigSection
error={manualConfigError}
loading={manualConfigLoading}
locale={locale}
onCopy={onCopyManualConfig}
snippet={manualConfigSnippet}
/>
<p>
Claude Code CLI reads <code className="rounded bg-muted px-1 py-0.5 text-xs">~/.claude.json</code>, Gemini CLI reads <code className="rounded bg-muted px-1 py-0.5 text-xs">~/.gemini/settings.json</code>, Cursor reads <code className="rounded bg-muted px-1 py-0.5 text-xs">~/.cursor/mcp.json</code>, and the generic <code className="rounded bg-muted px-1 py-0.5 text-xs">~/.config/mcp/mcp.json</code> path is picked up by other MCP-compatible tools. Cancel leaves all files untouched, reconnect is idempotent, and disconnect removes Tolaria&apos;s entry again.
{t('mcp.setup.clientPathsDescription')}
</p>
<p>
Gemini CLI needs its own install and sign-in. Use Restore Tolaria AI Guidance when you want a vault-root <code className="rounded bg-muted px-1 py-0.5 text-xs">GEMINI.md</code> compatibility shim that points Gemini back to the shared <code className="rounded bg-muted px-1 py-0.5 text-xs">AGENTS.md</code> instructions without overwriting custom guidance.
{t('mcp.setup.geminiGuidanceDescription')}
</p>
</div>
@@ -202,6 +217,7 @@ export function McpSetupDialog({
buttonsDisabled={buttonsDisabled}
connectBusy={connectBusy}
disconnectBusy={disconnectBusy}
locale={locale}
primaryLabel={copy.primaryLabel}
secondaryLabel={copy.secondaryLabel}
onClose={onClose}

View File

@@ -58,6 +58,26 @@ describe('NoteItem', () => {
expect(screen.getByTestId('type-icon')).toHaveAttribute('data-file-preview-kind', 'image')
})
it('renders PDF files as clickable rows with a PDF file indicator', () => {
const pdfEntry = makeEntry({
path: '/vault/reports/brief.pdf',
filename: 'brief.pdf',
title: 'brief.pdf',
fileKind: 'binary',
})
const onClickNote = vi.fn()
render(<NoteItem entry={pdfEntry} isSelected={false} typeEntryMap={{}} onClickNote={onClickNote} />)
const item = screen.getByTestId('pdf-file-item')
expect(item.className).not.toContain('opacity-50')
expect(item).toHaveAttribute('title', 'Open PDF preview')
fireEvent.click(item)
expect(onClickNote).toHaveBeenCalledWith(pdfEntry, expect.any(Object))
expect(screen.getByTestId('type-icon')).toHaveAttribute('data-file-preview-kind', 'pdf')
})
it('renders text files as clickable rows', () => {
const textEntry = makeEntry({
path: '/vault/config.yml',

View File

@@ -4,12 +4,12 @@ import { cn } from '@/lib/utils'
import {
Wrench, Flask, Target, ArrowsClockwise,
Users, CalendarBlank, Tag, FileText, StackSimple,
File, FileDashed, ImageSquare,
File, FileDashed, FilePdf, ImageSquare,
} from '@phosphor-icons/react'
import { getTypeColor, getTypeLightColor } from '../utils/typeColors'
import { resolveIcon } from '../utils/iconRegistry'
import { relativeDate, getDisplayDate } from '../utils/noteListHelpers'
import { isImagePreviewEntry } from '../utils/filePreview'
import { filePreviewKind, type FilePreviewKind } from '../utils/filePreview'
import { NoteTitleIcon } from './NoteTitleIcon'
import { PropertyChips } from './note-item/PropertyChips'
import { ChangeNoteContent } from './note-item/ChangeNoteContent'
@@ -109,7 +109,7 @@ function NoteTypeIndicator({
}: {
TypeIcon: ComponentType<SVGAttributes<SVGSVGElement>>
typeColor: string
filePreviewKind?: 'image'
filePreviewKind?: FilePreviewKind
}) {
return (
<TypeIcon
@@ -202,7 +202,9 @@ function InteractiveNoteDetails({
}
function resolveNoteTypeIcon(entry: VaultEntry, customIcon?: string | null): ComponentType<SVGAttributes<SVGSVGElement>> {
if (isImagePreviewEntry(entry)) return ImageSquare
const previewKind = filePreviewKind(entry)
if (previewKind === 'image') return ImageSquare
if (previewKind === 'pdf') return FilePdf
if (entry.fileKind && entry.fileKind !== 'markdown') return getFileKindIcon(entry.fileKind)
return getTypeIcon(entry.isA, customIcon)
}
@@ -232,11 +234,11 @@ function StandardNoteContent({
}) {
const te = typeEntryMap[entry.isA ?? '']
const TypeIcon = resolveNoteTypeIcon(entry, te?.icon)
const filePreviewKind = isImagePreviewEntry(entry) ? 'image' : undefined
const previewKind = filePreviewKind(entry) ?? undefined
return (
<>
<NoteTypeIndicator TypeIcon={TypeIcon} typeColor={typeColor} filePreviewKind={filePreviewKind} />
<NoteTypeIndicator TypeIcon={TypeIcon} typeColor={typeColor} filePreviewKind={previewKind} />
<div className="space-y-2" data-testid="note-content-stack">
{isBinary ? (
<NoteTitleRow
@@ -360,30 +362,31 @@ function resolveNoteItemSurfaceStyle({
function resolveNoteItemTestId({
isMultiSelected,
isImagePreview,
previewKind,
isUnavailableBinary,
}: Pick<NoteItemVisualState, 'isMultiSelected' | 'isUnavailableBinary'> & {
isImagePreview: boolean
previewKind: FilePreviewKind | null
}) {
if (isMultiSelected) return 'multi-selected-item'
if (isImagePreview) return 'image-file-item'
if (previewKind) return `${previewKind}-file-item`
return isUnavailableBinary ? 'binary-file-item' : undefined
}
function resolveNoteItemTitle({
isImagePreview,
previewKind,
isUnavailableBinary,
}: Pick<NoteItemVisualState, 'isUnavailableBinary'> & {
isImagePreview: boolean
previewKind: FilePreviewKind | null
}) {
if (isImagePreview) return 'Open image preview'
if (previewKind === 'image') return 'Open image preview'
if (previewKind === 'pdf') return 'Open PDF preview'
return isUnavailableBinary ? 'Cannot open this file type' : undefined
}
function resolveNoteItemSurfaceProps({
entry,
isUnavailableBinary,
isImagePreview,
previewKind,
isSelected,
isMultiSelected,
isHighlighted,
@@ -394,7 +397,7 @@ function resolveNoteItemSurfaceProps({
typeLightColor,
}: NoteItemVisualState & {
entry: VaultEntry
isImagePreview: boolean
previewKind: FilePreviewKind | null
onClickNote: NoteItemProps['onClickNote']
onPrefetch?: NoteItemProps['onPrefetch']
onContextMenu?: NoteItemProps['onContextMenu']
@@ -407,8 +410,8 @@ function resolveNoteItemSurfaceProps({
onClick: createNoteItemClickHandler(entry, isUnavailableBinary, onClickNote),
onContextMenu: onContextMenu ? (event) => onContextMenu(entry, event) : undefined,
onMouseEnter: entry.fileKind !== 'binary' && onPrefetch ? () => onPrefetch(entry.path) : undefined,
testId: resolveNoteItemTestId({ isMultiSelected, isImagePreview, isUnavailableBinary }),
title: resolveNoteItemTitle({ isImagePreview, isUnavailableBinary }),
testId: resolveNoteItemTestId({ isMultiSelected, previewKind, isUnavailableBinary }),
title: resolveNoteItemTitle({ previewKind, isUnavailableBinary }),
}
}
@@ -497,16 +500,17 @@ function NoteItemContent({
export function NoteItem({ entry, isSelected, isMultiSelected = false, isHighlighted = false, noteStatus = 'clean', changeStatus, typeEntryMap, allEntries, displayPropsOverride, onClickNote, onPrefetch, onContextMenu }: NoteItemProps) {
const isBinary = entry.fileKind === 'binary'
const isImagePreview = isImagePreviewEntry(entry)
const isUnavailableBinary = isBinary && !isImagePreview
const previewKind = filePreviewKind(entry)
const isPreviewableFile = previewKind !== null
const isUnavailableBinary = isBinary && !isPreviewableFile
const te = typeEntryMap[entry.isA ?? '']
const displayProps = resolveDisplayProps(entry, typeEntryMap, displayPropsOverride)
const typeColor = isImagePreview ? 'var(--accent-blue)' : isBinary ? 'var(--muted-foreground)' : getTypeColor(entry.isA ?? 'Note', te?.color)
const typeColor = isPreviewableFile ? 'var(--accent-blue)' : isBinary ? 'var(--muted-foreground)' : getTypeColor(entry.isA ?? 'Note', te?.color)
const typeLightColor = getTypeLightColor(entry.isA ?? 'Note', te?.color)
const surfaceProps = resolveNoteItemSurfaceProps({
entry,
isUnavailableBinary,
isImagePreview,
previewKind,
isSelected,
isMultiSelected,
isHighlighted,

View File

@@ -1,5 +1,6 @@
import { useCallback, useMemo, useState, useSyncExternalStore } from 'react'
import type { AiAgentId, AiAgentReadiness } from '../lib/aiAgents'
import type { AppLocale } from '../lib/i18n'
import {
aiAgentPermissionModeMarker,
normalizeAiAgentPermissionMode,
@@ -29,6 +30,7 @@ interface UseAiPanelControllerArgs {
openTabs?: VaultEntry[]
noteList?: NoteListItem[]
noteListFilter?: { type: string | null; query: string }
locale?: AppLocale
onOpenNote?: (path: string) => void
onFileCreated?: (relativePath: string) => void
onFileModified?: (relativePath: string) => void
@@ -87,6 +89,7 @@ export function useAiPanelController({
openTabs,
noteList,
noteListFilter,
locale = 'en',
onOpenNote,
onFileCreated,
onFileModified,
@@ -129,8 +132,8 @@ export function useAiPanelController({
if (isActive || nextMode === permissionMode) return
updateVaultConfigField('ai_agent_permission_mode', nextMode)
agent.addLocalMarker(aiAgentPermissionModeMarker(nextMode))
}, [agent, isActive, permissionMode])
agent.addLocalMarker(aiAgentPermissionModeMarker(nextMode, locale))
}, [agent, isActive, locale, permissionMode])
const handleNewChat = useCallback(() => {
agent.clearConversation()

View File

@@ -93,14 +93,35 @@ function localizeNoteStateCommand(command: CommandAction, t: Translate): string
function localizeColumnsCommand(command: CommandAction, t: Translate): string {
if (command.label === 'Customize All Notes columns') return t('noteList.properties.customizeAllColumns')
if (command.label === 'Customize Inbox columns') return t('noteList.properties.customizeInboxColumns')
const viewName = wrappedLabelValue(command.label, 'Customize ', ' columns')
if (viewName) return t('noteList.properties.customizeViewColumns', { name: viewName })
return t('noteList.properties.customizeColumns')
}
function wrappedLabelValue(label: string, prefix: string, suffix: string): string | null {
return label.startsWith(prefix) && label.endsWith(suffix)
? label.slice(prefix.length, -suffix.length)
: null
}
function localizeMoveSavedViewCommand(command: CommandAction, t: Translate, direction: 'Up' | 'Down'): string {
const viewName = wrappedLabelValue(command.label, 'Move ', ` ${direction}`)
if (!viewName || viewName === 'View') {
return t(direction === 'Up' ? 'command.view.moveViewUp' : 'command.view.moveViewDown')
}
return t(direction === 'Up' ? 'command.view.moveNamedViewUp' : 'command.view.moveNamedViewDown', {
name: viewName,
})
}
const VIEW_STATE_LABELERS: Partial<Record<string, CommandLabeler>> = {
'toggle-note-layout': (command, t) => t(command.label === 'Use Left-Aligned Note Layout' ? 'command.view.leftLayout' : 'command.view.centerLayout'),
'zoom-in': (command, t) => t('command.view.zoomIn', { zoom: parenthesizedSuffix(command.label)?.replace('%', '') ?? '' }),
'zoom-out': (command, t) => t('command.view.zoomOut', { zoom: parenthesizedSuffix(command.label)?.replace('%', '') ?? '' }),
'customize-note-list-columns': localizeColumnsCommand,
'move-view-up': (command, t) => localizeMoveSavedViewCommand(command, t, 'Up'),
'move-view-down': (command, t) => localizeMoveSavedViewCommand(command, t, 'Down'),
}
function localizeViewStateCommand(command: CommandAction, t: Translate): string | null {

View File

@@ -1,11 +1,11 @@
import { startTransition, useCallback, useEffect, useRef, type MutableRefObject } from 'react'
import { startTransition, useCallback, useEffect, useMemo, useRef, type MutableRefObject } from 'react'
import { invoke } from '@tauri-apps/api/core'
import { useEditorSaveWithLinks } from './useEditorSaveWithLinks'
import { MISSING_ACTIVE_VAULT_SAVE_MESSAGE } from './useEditorSave'
import { flushEditorContent } from '../utils/autoSave'
import { extractH1TitleFromContent } from '../utils/noteTitle'
import { isTauri } from '../mock-tauri'
import type { VaultEntry } from '../types'
import { createTranslator, type AppLocale } from '../lib/i18n'
interface TabState {
entry: VaultEntry
@@ -426,6 +426,7 @@ interface AppSaveDeps {
resolvedPath: string
initialH1AutoRenameEnabled: boolean
onInternalVaultWrite?: (path: string) => void
locale?: AppLocale
}
function useAppSaveStateRefs({
@@ -473,6 +474,7 @@ function useFlushBeforeAction({
clearUnsaved,
setToastMessage,
flushPendingUntitledRename,
locale,
}: {
canPersist: boolean
resolveCurrentPath: (path: string) => string
@@ -482,11 +484,14 @@ function useFlushBeforeAction({
clearUnsaved: AppSaveDeps['clearUnsaved']
setToastMessage: AppSaveDeps['setToastMessage']
flushPendingUntitledRename: (path?: string) => Promise<boolean>
locale: AppLocale
}) {
const t = useMemo(() => createTranslator(locale), [locale])
return useCallback(async (path: string) => {
const currentPath = resolveCurrentPath(path)
if (!canPersist) {
if (unsavedPathsRef.current.has(currentPath)) setToastMessage(MISSING_ACTIVE_VAULT_SAVE_MESSAGE)
if (unsavedPathsRef.current.has(currentPath)) setToastMessage(t('save.toast.missingActiveVault'))
return
}
try {
@@ -498,10 +503,10 @@ function useFlushBeforeAction({
})
await flushPendingUntitledRename(currentPath)
} catch (err) {
setToastMessage(`Auto-save failed: ${err}`)
setToastMessage(t('save.error.autoFailed', { error: String(err) }))
throw err
}
}, [canPersist, resolveCurrentPath, savePendingForPath, tabsRef, unsavedPathsRef, clearUnsaved, setToastMessage, flushPendingUntitledRename])
}, [canPersist, resolveCurrentPath, savePendingForPath, tabsRef, unsavedPathsRef, clearUnsaved, setToastMessage, flushPendingUntitledRename, t])
}
async function preparePathForManualRename({
@@ -606,6 +611,7 @@ function useEditorPersistence({
resolveCurrentPath,
resolvePathBeforeSave,
canPersist,
locale,
}: {
updateEntry: AppSaveDeps['updateEntry']
setTabs: AppSaveDeps['setTabs']
@@ -619,6 +625,7 @@ function useEditorPersistence({
resolveCurrentPath: (path: string) => string
resolvePathBeforeSave: (path: string) => Promise<string>
canPersist: boolean
locale: AppLocale
}) {
const onAfterSave = useCallback(() => {
loadModifiedFiles()
@@ -645,7 +652,7 @@ function useEditorPersistence({
resolvePath: resolveCurrentPath,
resolvePathBeforeSave,
canPersist,
disabledSaveMessage: MISSING_ACTIVE_VAULT_SAVE_MESSAGE,
locale,
})
const handleContentChange = useCallback((path: string, content: string) => {
@@ -688,6 +695,7 @@ function useAppSaveHandlers({
clearUnsaved,
setToastMessage,
flushPendingUntitledRename,
locale,
handleRenameNote,
handleRenameFilename,
resolvedPath,
@@ -710,6 +718,7 @@ function useAppSaveHandlers({
clearUnsaved: AppSaveDeps['clearUnsaved']
setToastMessage: AppSaveDeps['setToastMessage']
flushPendingUntitledRename: (path?: string) => Promise<boolean>
locale: AppLocale
handleRenameNote: AppSaveDeps['handleRenameNote']
handleRenameFilename: AppSaveDeps['handleRenameFilename']
resolvedPath: string
@@ -736,6 +745,7 @@ function useAppSaveHandlers({
clearUnsaved,
setToastMessage,
flushPendingUntitledRename,
locale,
})
const { handleFilenameRename, handleTitleSync } = useRenameHandlers({
resolveCurrentPath,
@@ -765,6 +775,7 @@ export function useAppSave({
handleRenameFilename: handleRenameFilenameRaw, replaceEntry, resolvedPath,
initialH1AutoRenameEnabled,
onInternalVaultWrite,
locale = 'en',
}: AppSaveDeps) {
const contentChangeRef = useRef<(path: string, content: string) => void>(() => {})
const canPersist = resolvedPath.trim().length > 0
@@ -795,6 +806,7 @@ export function useAppSave({
resolveCurrentPath,
resolvePathBeforeSave,
canPersist,
locale,
})
const replaceRenamedEntry = useReplaceRenamedEntry({ registerRenamedPath, replaceEntry })
const { handleFilenameRename, handleSave, handleTitleSync, flushBeforeAction } = useAppSaveHandlers({
@@ -811,6 +823,7 @@ export function useAppSave({
clearUnsaved,
setToastMessage,
flushPendingUntitledRename,
locale,
handleRenameNote,
handleRenameFilename: handleRenameFilenameRaw,
resolvedPath,

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useLayoutEffect, useRef, type MutableRefObject } from 'react'
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, type MutableRefObject } from 'react'
import type { SetStateAction } from 'react'
import { useSaveNote } from './useSaveNote'
import { createTranslator, type AppLocale } from '../lib/i18n'
interface Tab {
entry: { path: string }
@@ -22,6 +23,7 @@ interface EditorSaveConfig {
/** False when editor state is present but no vault is available to receive writes. */
canPersist?: boolean
disabledSaveMessage?: string
locale?: AppLocale
}
/**
@@ -31,8 +33,8 @@ interface EditorSaveConfig {
const noop = () => {}
const AUTO_SAVE_DEBOUNCE_MS = 500
const INVALID_PATH_SAVE_MESSAGE = 'Save failed: The note path is invalid on this platform. Rename the note or move it to a valid folder, then try again.'
export const MISSING_ACTIVE_VAULT_SAVE_MESSAGE = 'Select or restore a vault before saving.'
type Translator = ReturnType<typeof createTranslator>
interface PendingContent {
path: string
@@ -51,10 +53,10 @@ function isInvalidPathSaveError(message: string): boolean {
|| normalized.includes('path is invalid on this platform')
}
function formatSaveFailureMessage(error: unknown): string {
function formatSaveFailureMessage(error: unknown, t: Translator): string {
const message = errorMessage(error)
if (isInvalidPathSaveError(message)) return INVALID_PATH_SAVE_MESSAGE
return `Save failed: ${message}`
if (isInvalidPathSaveError(message)) return t('save.error.invalidPath')
return t('save.error.failed', { error: message })
}
function useLatestValueRef<T>(value: T): MutableRefObject<T> {
@@ -123,11 +125,13 @@ function scheduleAutoSave({
flushPending,
onAfterSaveRef,
setToastMessage,
t,
}: {
autoSaveTimerRef: MutableRefObject<ReturnType<typeof setTimeout> | null>
flushPending: () => Promise<boolean>
onAfterSaveRef: MutableRefObject<() => void>
setToastMessage: EditorSaveConfig['setToastMessage']
t: Translator
}): void {
autoSaveTimerRef.current = setTimeout(async () => {
autoSaveTimerRef.current = null
@@ -136,7 +140,7 @@ function scheduleAutoSave({
if (saved) onAfterSaveRef.current()
} catch (err) {
console.error('Auto-save failed:', err)
setToastMessage(formatSaveFailureMessage(err))
setToastMessage(formatSaveFailureMessage(err, t))
}
}, AUTO_SAVE_DEBOUNCE_MS)
}
@@ -222,16 +226,18 @@ function pausedSaveResult({
unsavedFallback,
setToastMessage,
disabledSaveMessage,
t,
}: {
canPersistRef: MutableRefObject<boolean>
pendingContentRef: MutableRefObject<PendingContent | null>
unsavedFallback?: { path: string; content: string }
setToastMessage: EditorSaveConfig['setToastMessage']
disabledSaveMessage: string
t: Translator
}): boolean | null {
if (canPersistRef.current) return null
const hasUnsavedContent = pendingContentRef.current !== null || unsavedFallback !== undefined
setToastMessage(hasUnsavedContent ? disabledSaveMessage : 'Nothing to save')
setToastMessage(hasUnsavedContent ? disabledSaveMessage : t('save.toast.nothingToSave'))
return !hasUnsavedContent
}
@@ -244,6 +250,7 @@ async function persistImmediateSave({
resolvePathBeforeSave,
setToastMessage,
onAfterSave,
t,
}: {
unsavedFallback?: { path: string; content: string }
flushPending: (pathFilter?: string) => Promise<boolean>
@@ -253,6 +260,7 @@ async function persistImmediateSave({
resolvePathBeforeSave?: EditorSaveConfig['resolvePathBeforeSave']
setToastMessage: EditorSaveConfig['setToastMessage']
onAfterSave: () => void
t: Translator
}): Promise<boolean> {
try {
const saved = await flushPending()
@@ -263,12 +271,12 @@ async function persistImmediateSave({
resolvePath,
resolvePathBeforeSave,
})
setToastMessage(saved || savedFallback ? 'Saved' : 'Nothing to save')
setToastMessage(saved || savedFallback ? t('save.toast.saved') : t('save.toast.nothingToSave'))
onAfterSave()
return true
} catch (err) {
console.error('Save failed:', err)
setToastMessage(formatSaveFailureMessage(err))
setToastMessage(formatSaveFailureMessage(err, t))
return false
}
}
@@ -285,6 +293,7 @@ function useImmediateSaveCommands({
resolvePathBeforeSave,
canPersistRef,
disabledSaveMessage,
t,
}: {
pendingContentRef: MutableRefObject<PendingContent | null>
cancelAutoSave: () => void
@@ -297,6 +306,7 @@ function useImmediateSaveCommands({
resolvePathBeforeSave?: EditorSaveConfig['resolvePathBeforeSave']
canPersistRef: MutableRefObject<boolean>
disabledSaveMessage: string
t: Translator
}) {
const handleSave = useCallback(async (unsavedFallback?: { path: string; content: string }): Promise<boolean> => {
cancelAutoSave()
@@ -306,6 +316,7 @@ function useImmediateSaveCommands({
unsavedFallback,
setToastMessage,
disabledSaveMessage,
t,
})
if (pausedResult !== null) return pausedResult
return persistImmediateSave({
@@ -317,8 +328,9 @@ function useImmediateSaveCommands({
resolvePathBeforeSave,
setToastMessage,
onAfterSave,
t,
})
}, [canPersistRef, cancelAutoSave, disabledSaveMessage, flushPending, onAfterSave, onNotePersisted, pendingContentRef, resolvePath, resolvePathBeforeSave, saveNote, setToastMessage])
}, [canPersistRef, cancelAutoSave, disabledSaveMessage, flushPending, onAfterSave, onNotePersisted, pendingContentRef, resolvePath, resolvePathBeforeSave, saveNote, setToastMessage, t])
const savePendingForPath = useCallback(
(path: string): Promise<boolean> => {
@@ -345,6 +357,7 @@ function useContentChangeCommand({
flushPending,
onAfterSaveRef,
canPersistRef,
t,
}: {
pendingContentRef: MutableRefObject<PendingContent | null>
autoSaveTimerRef: MutableRefObject<ReturnType<typeof setTimeout> | null>
@@ -354,14 +367,15 @@ function useContentChangeCommand({
flushPending: () => Promise<boolean>
onAfterSaveRef: MutableRefObject<() => void>
canPersistRef: MutableRefObject<boolean>
t: Translator
}) {
return useCallback((path: string, content: string) => {
pendingContentRef.current = { path, content }
applyTabContent(setTabs, path, content)
cancelAutoSave()
if (!canPersistRef.current) return
scheduleAutoSave({ autoSaveTimerRef, flushPending, onAfterSaveRef, setToastMessage })
}, [autoSaveTimerRef, canPersistRef, cancelAutoSave, flushPending, onAfterSaveRef, pendingContentRef, setTabs, setToastMessage])
scheduleAutoSave({ autoSaveTimerRef, flushPending, onAfterSaveRef, setToastMessage, t })
}, [autoSaveTimerRef, canPersistRef, cancelAutoSave, flushPending, onAfterSaveRef, pendingContentRef, setTabs, setToastMessage, t])
}
function useEditorSaveCommands({
@@ -377,6 +391,7 @@ function useEditorSaveCommands({
resolvePathBeforeSave,
canPersistRef,
disabledSaveMessage,
t,
}: {
pendingContentRef: MutableRefObject<PendingContent | null>
autoSaveTimerRef: MutableRefObject<ReturnType<typeof setTimeout> | null>
@@ -390,6 +405,7 @@ function useEditorSaveCommands({
resolvePathBeforeSave?: EditorSaveConfig['resolvePathBeforeSave']
canPersistRef: MutableRefObject<boolean>
disabledSaveMessage: string
t: Translator
}) {
const flushPending = usePendingContentFlush({
pendingContentRef,
@@ -412,6 +428,7 @@ function useEditorSaveCommands({
resolvePathBeforeSave,
canPersistRef,
disabledSaveMessage,
t,
})
const handleContentChange = useContentChangeCommand({
pendingContentRef,
@@ -422,6 +439,7 @@ function useEditorSaveCommands({
flushPending: () => flushPending(),
onAfterSaveRef,
canPersistRef,
t,
})
return { handleSave, handleContentChange, savePendingForPath, savePending }
@@ -436,11 +454,14 @@ export function useEditorSave({
resolvePath,
resolvePathBeforeSave,
canPersist = true,
disabledSaveMessage = MISSING_ACTIVE_VAULT_SAVE_MESSAGE,
disabledSaveMessage,
locale = 'en',
}: EditorSaveConfig) {
const pendingContentRef = useRef<{ path: string; content: string } | null>(null)
const autoSaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const canPersistRef = useLatestValueRef(canPersist)
const t = useMemo(() => createTranslator(locale), [locale])
const disabledSaveText = disabledSaveMessage ?? t('save.toast.missingActiveVault')
const updateTabAndContent = useCallback((path: string, content: string) => {
updateVaultContent(path, content)
@@ -462,6 +483,7 @@ export function useEditorSave({
resolvePath,
resolvePathBeforeSave,
canPersistRef,
disabledSaveMessage,
disabledSaveMessage: disabledSaveText,
t,
})
}

View File

@@ -5,6 +5,7 @@ import { deriveRawEditorEntryState } from './rawEditorEntryState'
import { deriveDisplayTitleState } from '../utils/noteTitle'
import { detectFrontmatterState } from '../utils/frontmatter'
import type { VaultEntry } from '../types'
import type { AppLocale } from '../lib/i18n'
const EMPTY_DERIVED_ENTRY_STATE_KEY = JSON.stringify(deriveRawEditorEntryState(''))
@@ -24,6 +25,7 @@ export function useEditorSaveWithLinks(config: {
resolvePathBeforeSave?: (path: string) => Promise<string>
canPersist?: boolean
disabledSaveMessage?: string
locale?: AppLocale
}) {
const { updateEntry } = config
const saveContent = useCallback((path: string, content: string) => {

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useRef, useState, type MutableRefObject } from 'react'
import { useCallback, useEffect, useMemo, useRef, useState, type MutableRefObject } from 'react'
import { invoke } from '@tauri-apps/api/core'
import { isTauri, mockInvoke } from '../mock-tauri'
import { createTranslator, type AppLocale } from '../lib/i18n'
export type McpStatus = 'checking' | 'installed' | 'not_installed'
type ManualConfigSnippet = string
@@ -14,6 +15,7 @@ type McpStatusResponse = string
type ToastHandler = (msg: ToastMessage) => void
type ToastMessage = string
type VaultPath = string
type Translator = ReturnType<typeof createTranslator>
interface ManualMcpConfigState {
error: ToastMessage | null
@@ -46,16 +48,16 @@ async function fetchMcpStatus(vaultPath: VaultPath): Promise<McpStatus> {
}
}
function connectSuccessToast(result: McpCommandResult): ToastMessage {
function connectSuccessToast(result: McpCommandResult, t: Translator): ToastMessage {
return result === 'registered'
? 'Tolaria external AI tools connected successfully'
: 'Tolaria external AI tools setup refreshed successfully'
? t('mcp.toast.connected')
: t('mcp.toast.refreshed')
}
function disconnectSuccessToast(result: McpCommandResult): ToastMessage {
function disconnectSuccessToast(result: McpCommandResult, t: Translator): ToastMessage {
return result === 'removed'
? 'Tolaria external AI tools disconnected successfully'
: 'Tolaria external AI tools were already disconnected'
? t('mcp.toast.disconnected')
: t('mcp.toast.alreadyDisconnected')
}
function errorMessage(error: unknown): ToastMessage {
@@ -69,9 +71,9 @@ function visibleManualConfig(
return state.vaultPath === vaultPath ? state : EMPTY_MANUAL_CONFIG
}
async function writeClipboardText(value: ManualConfigSnippet): Promise<void> {
async function writeClipboardText(value: ManualConfigSnippet, t: Translator): Promise<void> {
if (!navigator.clipboard?.writeText) {
throw new Error('Clipboard API is unavailable')
throw new Error(t('mcp.error.clipboardUnavailable'))
}
await navigator.clipboard.writeText(value)
@@ -80,6 +82,7 @@ async function writeClipboardText(value: ManualConfigSnippet): Promise<void> {
function useManualMcpConfig(
vaultPath: VaultPath,
onToastRef: MutableRefObject<ToastHandler>,
t: Translator,
) {
const [manualConfig, setManualConfig] = useState<ManualMcpConfigState>(EMPTY_MANUAL_CONFIG)
@@ -99,14 +102,14 @@ function useManualMcpConfig(
const copyMcpConfig = useCallback(async () => {
try {
const snippet = await loadMcpConfigSnippet()
await writeClipboardText(snippet)
onToastRef.current('Tolaria MCP config copied to clipboard')
await writeClipboardText(snippet, t)
onToastRef.current(t('mcp.toast.configCopied'))
return true
} catch (error) {
onToastRef.current(`Copy MCP config failed: ${errorMessage(error)}`)
onToastRef.current(t('mcp.toast.configCopyFailed', { error: errorMessage(error) }))
return false
}
}, [loadMcpConfigSnippet, onToastRef])
}, [loadMcpConfigSnippet, onToastRef, t])
const currentManualConfig = visibleManualConfig(manualConfig, vaultPath)
@@ -126,11 +129,13 @@ function useManualMcpConfig(
export function useMcpStatus(
vaultPath: VaultPath,
onToast: ToastHandler,
locale: AppLocale = 'en',
) {
const [status, setStatus] = useState<McpStatus>('checking')
const t = useMemo(() => createTranslator(locale), [locale])
const onToastRef = useRef(onToast)
useEffect(() => { onToastRef.current = onToast })
const manualConfigActions = useManualMcpConfig(vaultPath, onToastRef)
const manualConfigActions = useManualMcpConfig(vaultPath, onToastRef, t)
const refreshMcpStatus = useCallback(async () => {
const nextStatus = await fetchMcpStatus(vaultPath)
@@ -154,29 +159,29 @@ export function useMcpStatus(
try {
const result = await tauriCall<string>('register_mcp_tools', { vaultPath })
setStatus('installed')
onToastRef.current(connectSuccessToast(result))
onToastRef.current(connectSuccessToast(result, t))
return true
} catch (e) {
setStatus('not_installed')
onToastRef.current(`External AI tool setup failed: ${e}`)
onToastRef.current(t('mcp.toast.setupFailed', { error: errorMessage(e) }))
return false
}
}, [vaultPath])
}, [t, vaultPath])
const disconnectMcp = useCallback(async () => {
setStatus('checking')
try {
const result = await tauriCall<string>('remove_mcp_tools')
setStatus('not_installed')
onToastRef.current(disconnectSuccessToast(result))
onToastRef.current(disconnectSuccessToast(result, t))
return true
} catch (e) {
const nextStatus = await refreshMcpStatus()
setStatus(nextStatus)
onToastRef.current(`External AI tool disconnect failed: ${e}`)
onToastRef.current(t('mcp.toast.disconnectFailed', { error: errorMessage(e) }))
return false
}
}, [refreshMcpStatus])
}, [refreshMcpStatus, t])
return {
mcpStatus: status,

View File

@@ -2,6 +2,7 @@ import { useCallback, useMemo } from 'react'
import { invoke } from '@tauri-apps/api/core'
import { isTauri, mockInvoke } from '../mock-tauri'
import type { SidebarSelection, ViewFile } from '../types'
import { createTranslator, type AppLocale } from '../lib/i18n'
import {
buildViewOrderUpdates,
canMoveView,
@@ -19,6 +20,7 @@ interface SavedViewOrderingConfig {
reloadViews: () => Promise<unknown>
loadModifiedFiles: () => Promise<void>
onToast: (message: string) => void
locale?: AppLocale
}
interface SavedViewOrdering {
@@ -54,7 +56,10 @@ export function useSavedViewOrdering({
reloadViews,
loadModifiedFiles,
onToast,
locale = 'en',
}: SavedViewOrderingConfig): SavedViewOrdering {
const t = useMemo(() => createTranslator(locale), [locale])
const persistViewOrder = useCallback(async (orderedViews: ViewFile[]) => {
const target = isTauri() ? invoke : mockInvoke
await Promise.all(buildViewOrderUpdates(orderedViews).map(({ filename, definition }) => (
@@ -62,8 +67,8 @@ export function useSavedViewOrdering({
)))
await reloadViews()
await loadModifiedFiles()
onToast('Views reordered')
}, [loadModifiedFiles, onToast, reloadViews, vaultPath])
onToast(t('savedViews.reordered'))
}, [loadModifiedFiles, onToast, reloadViews, t, vaultPath])
const onReorderViews = useCallback<ViewReorderHandler>(async (orderedFilenames) => {
const orderedViews = orderViewsByFilename(views, orderedFilenames)

View File

@@ -1,3 +1,5 @@
import { createTranslator, type AppLocale } from './i18n'
export type AiAgentPermissionMode = 'safe' | 'power_user'
export const DEFAULT_AI_AGENT_PERMISSION_MODE: AiAgentPermissionMode = 'safe'
@@ -20,7 +22,27 @@ export function normalizeAiAgentPermissionMode(value: unknown): AiAgentPermissio
return value === 'power_user' ? 'power_user' : DEFAULT_AI_AGENT_PERMISSION_MODE
}
export function aiAgentPermissionModeMarker(mode: AiAgentPermissionMode): string {
const label = AI_AGENT_PERMISSION_MODE_LABELS[mode].short
return `AI permission mode changed to ${label}. It will apply to the next message.`
export function aiAgentPermissionModeLabels(
mode: AiAgentPermissionMode,
locale: AppLocale = 'en',
): { short: string; control: string } {
const t = createTranslator(locale)
return mode === 'power_user'
? {
short: t('ai.permission.powerUser.short'),
control: t('ai.permission.powerUser.control'),
}
: {
short: t('ai.permission.safe.short'),
control: t('ai.permission.safe.control'),
}
}
export function aiAgentPermissionModeMarker(
mode: AiAgentPermissionMode,
locale: AppLocale = 'en',
): string {
const t = createTranslator(locale)
const label = aiAgentPermissionModeLabels(mode, locale).short
return t('ai.permission.changed', { label })
}

View File

@@ -12,6 +12,7 @@
"command.openLanguageSettings.keywords": "Sprache Gebietsschema i18n Internationalisierung Lokalisierung Englisch Italienisch Französisch Deutsch Russisch Spanisch Portugiesisch Chinesisch Japanisch Koreanisch 中文",
"command.useSystemLanguage": "Systemsprache verwenden",
"command.openH1Setting": "Einstellung für automatische H1-Umbenennung öffnen",
"command.toggleGitignoredFilesVisibility": "Sichtbarkeit von Gitignored-Dateien umschalten",
"command.contribute": "Mitwirken",
"command.checkUpdates": "Nach Updates suchen",
"command.group.navigation": "Navigation",
@@ -68,6 +69,10 @@
"command.view.toggleAiPanel": "KI-Panel umschalten",
"command.view.newAiChat": "Neuer KI-Chat",
"command.view.toggleBacklinks": "Backlinks ein-/ausblenden",
"command.view.moveViewUp": "Ansicht nach oben verschieben",
"command.view.moveViewDown": "Ansicht nach unten verschieben",
"command.view.moveNamedViewUp": "{name} nach oben verschieben",
"command.view.moveNamedViewDown": "{name} nach unten verschieben",
"command.view.zoomIn": "Vergrößern ({zoom} %)",
"command.view.zoomOut": "Verkleinern ({zoom} %)",
"command.view.resetZoom": "Zoom zurücksetzen",
@@ -81,6 +86,7 @@
"command.settings.repairVault": "Vault reparieren",
"command.settings.useLightMode": "Hellen Modus verwenden",
"command.settings.useDarkMode": "Dunklen Modus verwenden",
"command.settings.toggleGitignoredFilesVisibility": "Sichtbarkeit von Gitignored-Dateien umschalten",
"command.ai.openAgents": "KI-Agents öffnen",
"command.ai.restoreGuidance": "Tolaria-KI-Anleitung wiederherstellen",
"command.ai.switchToAgent": "KI-Agent auf {agent} umstellen",
@@ -115,6 +121,10 @@
"settings.titles.description": "Legen Sie fest, ob Tolaria Dateinamen von unbenannten Notizen automatisch anhand der ersten H1-Überschrift synchronisiert.",
"settings.titles.autoRename": "Unbenannte Notizen automatisch anhand der ersten H1-Uberschrift umbenennen",
"settings.titles.autoRenameDescription": "Wenn diese Option aktiviert ist, benennt Tolaria Dateien mit unbenannten Notizen um, sobald die erste H1-Überschrift zu einem echten Titel wird. Deaktivieren Sie diese Option, um den Dateinamen unverändert zu lassen, bis Sie ihn manuell über die Breadcrumb-Leiste umbenennen.",
"settings.vaultContent.title": "Vault-Inhalt",
"settings.vaultContent.description": "Legen Sie fest, ob Dateien, die der .gitignore-Datei des Vaults entsprechen, in Tolaria angezeigt werden.",
"settings.vaultContent.hideGitignored": "Von Git ignorierte Dateien und Ordner ausblenden",
"settings.vaultContent.hideGitignoredDescription": "Hält generierte und rein lokale Vault-Dateien von Notizen, der Suche, dem schnellen Öffnen und Ordnern fern.",
"settings.aiAgents.title": "KI-Agents",
"settings.aiAgents.description": "Wählen Sie aus, welchen CLI-KI-Agenten Tolaria im KI-Panel und in der Befehlspalette verwendet.",
"settings.aiAgents.default": "Standard-KI-Agent",
@@ -138,6 +148,68 @@
"settings.cancel": "Abbrechen",
"settings.save": "Speichern",
"common.cancel": "Abbrechen",
"ai.permission.safe.short": "Safe",
"ai.permission.safe.control": "Vault Safe",
"ai.permission.safe.tooltip": "Mit Vault Safe können Agenten nur Datei-, Such- und Bearbeitungstools nutzen.",
"ai.permission.powerUser.short": "Power User",
"ai.permission.powerUser.control": "Power User",
"ai.permission.powerUser.tooltip": "Power User ermöglicht für diesen Vault auch lokale Shell-Befehle.",
"ai.permission.modeAria": "KI-Agent-Berechtigungsmodus",
"ai.permission.changed": "AI-Berechtigungsmodus auf {label} geändert. Dies gilt ab der nächsten Nachricht.",
"ai.panel.title": "KI-Agent",
"ai.panel.status.checking": "Verfügbarkeit wird geprüft",
"ai.panel.status.missing": "{agent} · nicht installiert",
"ai.panel.status.ready": "{agent} · {mode}",
"ai.panel.copyMcpConfig": "MCP-Konfiguration kopieren",
"ai.panel.mcpConfig": "MCP-Konfiguration",
"ai.panel.newChat": "Neuer KI-Chat",
"ai.panel.close": "KI-Panel schließen",
"ai.panel.linkedCount": "+ {count} verknüpft",
"ai.panel.empty.checkingTitle": "Verfügbarkeit des KI-Agents wird geprüft",
"ai.panel.empty.checkingDescription": "Nachrichten können gesendet werden, sobald der ausgewählte Agent bereit ist",
"ai.panel.empty.missingTitle": "{agent} ist auf diesem Computer nicht verfügbar",
"ai.panel.empty.missingDescription": "Installieren Sie ihn oder ändern Sie den standardmäßigen KI-Agenten in den Einstellungen.",
"ai.panel.empty.withContextTitle": "{agent} nach dieser Notiz und ihrem verknüpften Kontext fragen",
"ai.panel.empty.noContextTitle": "Öffnen Sie eine Notiz und fragen Sie dann {agent} danach",
"ai.panel.empty.withContextDescription": "Zusammenfassen, Zusammenhänge finden, Ideen ausbauen",
"ai.panel.empty.noContextDescription": "Die KI verwendet die aktive Notiz als Kontext",
"ai.panel.placeholder.checking": "Verfügbarkeit des KI-Agents wird geprüft …",
"ai.panel.placeholder.missing": "{agent} ist nicht installiert. Öffnen Sie „AI Agents“ in den Einstellungen.",
"ai.panel.placeholder.ready": "{agent} fragen",
"ai.panel.send": "Nachricht senden",
"mcp.setup.manageTitle": "Externe KI-Tools verwalten",
"mcp.setup.setupTitle": "Externe KI-Tools einrichten",
"mcp.setup.connectedDescription": "Tolaria ist für diesen Vault bereits mit externen KI-Tools verbunden. Stellen Sie die Verbindung erneut her, um die Konfiguration zu aktualisieren, oder trennen Sie die Verbindung, um Tolaria aus diesen Konfigurationsdateien von Drittanbietern zu entfernen.",
"mcp.setup.setupDescription": "Tolaria kann seinen MCP-Server zu externen KI-Tools für diesen Vault hinzufügen, wird aber erst dann auf Konfigurationsdateien von Drittanbietern zugreifen, wenn Sie dies hier bestätigen.",
"mcp.setup.reconnect": "Externe KI-Tools erneut verbinden",
"mcp.setup.connect": "Externe KI-Tools verbinden",
"mcp.setup.disconnect": "Verbindung trennen",
"mcp.setup.connecting": "Verbindung wird hergestellt …",
"mcp.setup.disconnecting": "Verbindung wird getrennt …",
"mcp.setup.nodeRequirement": "Für diese Einrichtung sind Node.js 18+ im PATH und ein MCP-kompatibles Desktop-Tool erforderlich. Tolaria prüft Node.js, bevor es die Konfiguration schreibt, damit das Tool nicht auf einen fehlerhaften Befehl verweist.",
"mcp.setup.writeEntryDescription": "Wenn Sie diese Aktion bestätigen, wird der einzige {entry}-MCP-Eintrag von Tolaria geschrieben oder aktualisiert in:",
"mcp.setup.clientPathsDescription": "Die Claude Code CLI liest ~/.claude.json, die Gemini CLI liest ~/.gemini/settings.json, Cursor liest ~/.cursor/mcp.json, und der allgemeine Pfad ~/.config/mcp/mcp.json wird von anderen MCP-kompatiblen Tools erkannt. Beim Schließen bleiben alle Dateien unverändert, es sei denn, Sie stellen eine Verbindung her oder trennen sie. Das Wiederherstellen der Verbindung ist idempotent, und das Trennen der Verbindung entfernt den Tolaria-Eintrag erneut.",
"mcp.setup.geminiGuidanceDescription": "Für die Gemini-CLI sind eine eigene Installation und eine eigene Anmeldung erforderlich. Verwenden Sie „Restore Tolaria AI Guidance“, wenn Sie einen GEMINI.md-Kompatibilitäts-Shim für den Vault-Root benötigen, der Gemini zurück zu den gemeinsamen AGENTS.md-Anweisungen führt, ohne die benutzerdefinierte Anleitung zu überschreiben.",
"mcp.setup.manual.title": "Manuelle MCP-Konfiguration",
"mcp.setup.manual.copy": "MCP-Konfiguration kopieren",
"mcp.setup.manual.loading": "Exakte MCP-Konfiguration wird geladen …",
"mcp.setup.manual.unavailable": "Die genaue MCP-Konfiguration ist verfügbar, nachdem ein Vault geöffnet wurde.",
"mcp.toast.connected": "Externe Tolaria-KI-Tools erfolgreich verbunden",
"mcp.toast.refreshed": "Einrichtung der externen Tolaria-KI-Tools erfolgreich aktualisiert",
"mcp.toast.disconnected": "Externe Tolaria-KI-Tools erfolgreich getrennt",
"mcp.toast.alreadyDisconnected": "Die externen KI-Tools von Tolaria waren bereits getrennt",
"mcp.toast.configCopied": "Tolaria-MCP-Konfiguration in die Zwischenablage kopiert",
"mcp.toast.configCopyFailed": "Fehler beim Kopieren der MCP-Konfiguration: {error}",
"mcp.toast.setupFailed": "Einrichtung des externen KI-Tools fehlgeschlagen: {error}",
"mcp.toast.disconnectFailed": "Trennung der Verbindung zur externen KI-Tool fehlgeschlagen: {error}",
"mcp.error.clipboardUnavailable": "Clipboard-API nicht verfügbar",
"save.toast.saved": "Gespeichert",
"save.toast.nothingToSave": "Nichts zum Speichern vorhanden",
"save.toast.missingActiveVault": "Wählen Sie vor dem Speichern einen Vault aus oder stellen Sie einen Vault wieder her.",
"save.error.failed": "Speichern fehlgeschlagen: {error}",
"save.error.autoFailed": "Automatisches Speichern fehlgeschlagen: {error}",
"save.error.invalidPath": "Speichern fehlgeschlagen: Der Notizpfad ist auf dieser Plattform ungültig. Benennen Sie die Notiz um oder verschieben Sie sie in einen gültigen Ordner und versuchen Sie es dann erneut.",
"savedViews.reordered": "Ansichten neu geordnet",
"locale.en": "Englisch",
"sidebar.nav.inbox": "Posteingang",
"sidebar.nav.allNotes": "Alle Notizen",
@@ -149,6 +221,9 @@
"sidebar.action.createView": "Ansicht erstellen",
"sidebar.action.editView": "Ansicht bearbeiten",
"sidebar.action.deleteView": "Ansicht löschen",
"sidebar.action.reorderView": "Ansicht neu anordnen",
"sidebar.action.moveViewUp": "Ansicht nach oben verschieben",
"sidebar.action.moveViewDown": "Ansicht nach unten verschieben",
"sidebar.action.customizeSections": "Abschnitte anpassen",
"sidebar.action.renameSection": "Abschnitt umbenennen …",
"sidebar.action.customizeIconColor": "Symbol und Farbe anpassen …",
@@ -318,6 +393,8 @@
"status.vault.cloneGit": "Git-Repo klonen",
"status.vault.cloneGettingStarted": "Getting-Started-Vault klonen",
"status.vault.notFound": "Vault nicht gefunden: {path}",
"status.vault.reloading": "Vault wird neu geladen …",
"status.vault.reloadingTooltip": "Vault von der Festplatte neu laden",
"status.vault.remove": "{label} aus der Liste entfernen",
"status.remote.noneConfigured": "Kein Remote konfiguriert",
"status.remote.inSync": "Mit Remote synchronisiert",

View File

@@ -69,6 +69,10 @@
"command.view.toggleAiPanel": "Toggle AI Panel",
"command.view.newAiChat": "New AI chat",
"command.view.toggleBacklinks": "Toggle Backlinks",
"command.view.moveViewUp": "Move View Up",
"command.view.moveViewDown": "Move View Down",
"command.view.moveNamedViewUp": "Move {name} Up",
"command.view.moveNamedViewDown": "Move {name} Down",
"command.view.zoomIn": "Zoom In ({zoom}%)",
"command.view.zoomOut": "Zoom Out ({zoom}%)",
"command.view.resetZoom": "Reset Zoom",
@@ -144,6 +148,68 @@
"settings.cancel": "Cancel",
"settings.save": "Save",
"common.cancel": "Cancel",
"ai.permission.safe.short": "Safe",
"ai.permission.safe.control": "Vault Safe",
"ai.permission.safe.tooltip": "Vault Safe keeps agents limited to file, search, and edit tools.",
"ai.permission.powerUser.short": "Power User",
"ai.permission.powerUser.control": "Power User",
"ai.permission.powerUser.tooltip": "Power User also allows local shell commands for this vault.",
"ai.permission.modeAria": "AI agent permission mode",
"ai.permission.changed": "AI permission mode changed to {label}. It will apply to the next message.",
"ai.panel.title": "AI Agent",
"ai.panel.status.checking": "Checking availability",
"ai.panel.status.missing": "{agent} · not installed",
"ai.panel.status.ready": "{agent} · {mode}",
"ai.panel.copyMcpConfig": "Copy MCP config",
"ai.panel.mcpConfig": "MCP config",
"ai.panel.newChat": "New AI chat",
"ai.panel.close": "Close AI panel",
"ai.panel.linkedCount": "+ {count} linked",
"ai.panel.empty.checkingTitle": "Checking AI agent availability",
"ai.panel.empty.checkingDescription": "Messages can be sent when the selected agent is ready",
"ai.panel.empty.missingTitle": "{agent} is not available on this machine",
"ai.panel.empty.missingDescription": "Install it or switch the default AI agent in Settings",
"ai.panel.empty.withContextTitle": "Ask {agent} about this note and its linked context",
"ai.panel.empty.noContextTitle": "Open a note, then ask {agent} about it",
"ai.panel.empty.withContextDescription": "Summarize, find connections, expand ideas",
"ai.panel.empty.noContextDescription": "The AI will use the active note as context",
"ai.panel.placeholder.checking": "Checking AI agent availability...",
"ai.panel.placeholder.missing": "{agent} is not installed. Open AI Agents in Settings.",
"ai.panel.placeholder.ready": "Ask {agent}",
"ai.panel.send": "Send message",
"mcp.setup.manageTitle": "Manage External AI Tools",
"mcp.setup.setupTitle": "Set Up External AI Tools",
"mcp.setup.connectedDescription": "Tolaria is already connected to external AI tools for this vault. Reconnect to refresh the configuration, or disconnect to remove Tolaria from those third-party config files.",
"mcp.setup.setupDescription": "Tolaria can add its MCP server to external AI tools for this vault, but it will not touch third-party config files until you confirm here.",
"mcp.setup.reconnect": "Reconnect External AI Tools",
"mcp.setup.connect": "Connect External AI Tools",
"mcp.setup.disconnect": "Disconnect",
"mcp.setup.connecting": "Connecting…",
"mcp.setup.disconnecting": "Disconnecting…",
"mcp.setup.nodeRequirement": "This setup requires Node.js 18+ on PATH and an MCP-compatible desktop tool. Tolaria checks Node.js before writing config so the tool is not left pointing at a broken command.",
"mcp.setup.writeEntryDescription": "Confirming this action will write or update Tolaria's single {entry} MCP entry in:",
"mcp.setup.clientPathsDescription": "Claude Code CLI reads ~/.claude.json, Gemini CLI reads ~/.gemini/settings.json, Cursor reads ~/.cursor/mcp.json, and the generic ~/.config/mcp/mcp.json path is picked up by other MCP-compatible tools. Closing leaves all files untouched unless you connect or disconnect, reconnecting is idempotent, and disconnecting removes Tolaria's entry again.",
"mcp.setup.geminiGuidanceDescription": "Gemini CLI needs its own install and sign-in. Use Restore Tolaria AI Guidance when you want a vault-root GEMINI.md compatibility shim that points Gemini back to the shared AGENTS.md instructions without overwriting custom guidance.",
"mcp.setup.manual.title": "Manual MCP config",
"mcp.setup.manual.copy": "Copy MCP config",
"mcp.setup.manual.loading": "Loading exact MCP config...",
"mcp.setup.manual.unavailable": "Exact config is available after a vault is open.",
"mcp.toast.connected": "Tolaria external AI tools connected successfully",
"mcp.toast.refreshed": "Tolaria external AI tools setup refreshed successfully",
"mcp.toast.disconnected": "Tolaria external AI tools disconnected successfully",
"mcp.toast.alreadyDisconnected": "Tolaria external AI tools were already disconnected",
"mcp.toast.configCopied": "Tolaria MCP config copied to clipboard",
"mcp.toast.configCopyFailed": "Copy MCP config failed: {error}",
"mcp.toast.setupFailed": "External AI tool setup failed: {error}",
"mcp.toast.disconnectFailed": "External AI tool disconnect failed: {error}",
"mcp.error.clipboardUnavailable": "Clipboard API is unavailable",
"save.toast.saved": "Saved",
"save.toast.nothingToSave": "Nothing to save",
"save.toast.missingActiveVault": "Select or restore a vault before saving.",
"save.error.failed": "Save failed: {error}",
"save.error.autoFailed": "Auto-save failed: {error}",
"save.error.invalidPath": "Save failed: The note path is invalid on this platform. Rename the note or move it to a valid folder, then try again.",
"savedViews.reordered": "Views reordered",
"locale.en": "English",
"sidebar.nav.inbox": "Inbox",
"sidebar.nav.allNotes": "All Notes",

View File

@@ -12,6 +12,7 @@
"command.openLanguageSettings.keywords": "idioma configuración regional i18n internacionalización localización inglés italiano francés alemán ruso español portugués chino japonés coreano 中文",
"command.useSystemLanguage": "Usar el idioma del sistema",
"command.openH1Setting": "Abrir la configuración de renombrado automático de H1",
"command.toggleGitignoredFilesVisibility": "Alternar la visibilidad de los archivos Gitignored",
"command.contribute": "Contribuir",
"command.checkUpdates": "Buscar actualizaciones",
"command.group.navigation": "Navegación",
@@ -68,6 +69,10 @@
"command.view.toggleAiPanel": "Activar/desactivar el panel de IA",
"command.view.newAiChat": "Nuevo chat de IA",
"command.view.toggleBacklinks": "Activar/desactivar enlaces de retroceso",
"command.view.moveViewUp": "Mover vista hacia arriba",
"command.view.moveViewDown": "Mover vista hacia abajo",
"command.view.moveNamedViewUp": "Mover {name} hacia arriba",
"command.view.moveNamedViewDown": "Mover {name} hacia abajo",
"command.view.zoomIn": "Acercar ({zoom} %)",
"command.view.zoomOut": "Alejar ({zoom} %)",
"command.view.resetZoom": "Restablecer el zoom",
@@ -81,6 +86,7 @@
"command.settings.repairVault": "Reparar el repositorio",
"command.settings.useLightMode": "Usar modo claro",
"command.settings.useDarkMode": "Usar modo oscuro",
"command.settings.toggleGitignoredFilesVisibility": "Activar/desactivar la visibilidad de los archivos ignorados por Git",
"command.ai.openAgents": "Abrir agentes de IA",
"command.ai.restoreGuidance": "Restaurar la guía de IA de Tolaria",
"command.ai.switchToAgent": "Cambiar el agente de IA a {agent}",
@@ -115,6 +121,10 @@
"settings.titles.description": "Elija si desea que Tolaria sincronice automáticamente los nombres de los archivos de notas sin título a partir del primer título H1.",
"settings.titles.autoRename": "Cambiar automáticamente el nombre de las notas sin título a partir del primer H1",
"settings.titles.autoRenameDescription": "Cuando esta opción está activada, Tolaria cambia el nombre de los archivos de notas sin título tan pronto como el primer H1 se convierte en un título real. Desactive esta opción para que el nombre del archivo no se modifique hasta que lo cambie manualmente desde la barra de navegación.",
"settings.vaultContent.title": "Contenido del repositorio",
"settings.vaultContent.description": "Controle si los archivos que coinciden con el archivo .gitignore del repositorio aparecen en Tolaria.",
"settings.vaultContent.hideGitignored": "Ocultar archivos y carpetas ignorados por Git",
"settings.vaultContent.hideGitignoredDescription": "Mantiene los archivos de la caja fuerte generados y solo locales fuera de las notas, la búsqueda, la apertura rápida y las carpetas.",
"settings.aiAgents.title": "Agentes de IA",
"settings.aiAgents.description": "Elija qué agente de IA de CLI utiliza Tolaria en el panel de IA y en la paleta de comandos.",
"settings.aiAgents.default": "Agente de IA predeterminado",
@@ -138,6 +148,68 @@
"settings.cancel": "Cancelar",
"settings.save": "Guardar",
"common.cancel": "Cancelar",
"ai.permission.safe.short": "Safe",
"ai.permission.safe.control": "Vault Safe",
"ai.permission.safe.tooltip": "Vault Safe limita el acceso de los agentes a las herramientas de archivos, búsqueda y edición.",
"ai.permission.powerUser.short": "Usuario avanzado",
"ai.permission.powerUser.control": "Usuario avanzado",
"ai.permission.powerUser.tooltip": "Power User también permite ejecutar comandos de shell locales para este almacén.",
"ai.permission.modeAria": "Modo de permisos de agente de IA",
"ai.permission.changed": "Modo de permisos de IA cambiado a {label}. Se aplicará al próximo mensaje.",
"ai.panel.title": "Agente de IA",
"ai.panel.status.checking": "Comprobando disponibilidad",
"ai.panel.status.missing": "{agent} · no instalado",
"ai.panel.status.ready": "{agent} · {mode}",
"ai.panel.copyMcpConfig": "Copiar configuración de MCP",
"ai.panel.mcpConfig": "Configuración de MCP",
"ai.panel.newChat": "Nuevo chat de IA",
"ai.panel.close": "Cerrar el panel de IA",
"ai.panel.linkedCount": "+ {count} vinculados",
"ai.panel.empty.checkingTitle": "Comprobando la disponibilidad del agente de IA",
"ai.panel.empty.checkingDescription": "Se podrán enviar mensajes cuando el agente seleccionado esté disponible",
"ai.panel.empty.missingTitle": "{agent} no está disponible en esta máquina",
"ai.panel.empty.missingDescription": "Instálelo o cambie el agente de IA predeterminado en Configuración",
"ai.panel.empty.withContextTitle": "Pregúntele a {agent} sobre esta nota y su contexto vinculado",
"ai.panel.empty.noContextTitle": "Abra una nota y luego pregúntele a {agent} al respecto",
"ai.panel.empty.withContextDescription": "Resumir, encontrar conexiones, desarrollar ideas",
"ai.panel.empty.noContextDescription": "La IA utilizará la nota activa como contexto",
"ai.panel.placeholder.checking": "Comprobando la disponibilidad del agente de IA...",
"ai.panel.placeholder.missing": "{agent} no está instalado. Abra Agentes de IA en Configuración.",
"ai.panel.placeholder.ready": "Preguntar a {agent}",
"ai.panel.send": "Enviar mensaje",
"mcp.setup.manageTitle": "Administrar herramientas de IA externas",
"mcp.setup.setupTitle": "Configurar herramientas de IA externas",
"mcp.setup.connectedDescription": "Tolaria ya está conectada a herramientas de IA externas para esta bóveda. Vuelva a conectarse para actualizar la configuración o desconéctese para eliminar Tolaria de esos archivos de configuración de terceros.",
"mcp.setup.setupDescription": "Tolaria puede agregar su servidor MCP a las herramientas de IA externas para esta bóveda, pero no modificará los archivos de configuración de terceros hasta que usted lo confirme aquí.",
"mcp.setup.reconnect": "Volver a conectar las herramientas externas de IA",
"mcp.setup.connect": "Conectar herramientas de IA externas",
"mcp.setup.disconnect": "Desconectar",
"mcp.setup.connecting": "Conectando…",
"mcp.setup.disconnecting": "Desconectando…",
"mcp.setup.nodeRequirement": "Esta configuración requiere Node.js 18 o superior en PATH y una herramienta de escritorio compatible con MCP. Tolaria verifica Node.js antes de escribir la configuración para que la herramienta no apunte a un comando que no funcione.",
"mcp.setup.writeEntryDescription": "Al confirmar esta acción, se escribirá o actualizará la única entrada MCP de Tolaria ({entry}) en:",
"mcp.setup.clientPathsDescription": "La CLI de Claude Code lee ~/.claude.json, la CLI de Gemini lee ~/.gemini/settings.json, Cursor lee ~/.cursor/mcp.json y otras herramientas compatibles con MCP leen la ruta genérica ~/.config/mcp/mcp.json. Al cerrar, todos los archivos permanecen intactos a menos que se conecte o se desconecte; la reconexión es idempotente y la desconexión vuelve a eliminar la entrada de Tolaria.",
"mcp.setup.geminiGuidanceDescription": "La CLI de Gemini requiere su propia instalación e inicio de sesión. Utilice Restore Tolaria AI Guidance cuando desee un shim de compatibilidad con GEMINI.md en la raíz del vault que dirija a Gemini de nuevo a las instrucciones compartidas de AGENTS.md sin sobrescribir la guía personalizada.",
"mcp.setup.manual.title": "Configuración manual de MCP",
"mcp.setup.manual.copy": "Copiar la configuración de MCP",
"mcp.setup.manual.loading": "Cargando la configuración exacta de MCP...",
"mcp.setup.manual.unavailable": "La configuración exacta está disponible después de abrir un vault.",
"mcp.toast.connected": "Herramientas de IA externas de Tolaria conectadas correctamente",
"mcp.toast.refreshed": "Configuración de las herramientas externas de IA de Tolaria actualizada correctamente",
"mcp.toast.disconnected": "Herramientas externas de IA de Tolaria desconectadas correctamente",
"mcp.toast.alreadyDisconnected": "Las herramientas externas de IA de Tolaria ya estaban desconectadas",
"mcp.toast.configCopied": "Configuración de Tolaria MCP copiada al portapapeles",
"mcp.toast.configCopyFailed": "Error al copiar la configuración de MCP: {error}",
"mcp.toast.setupFailed": "Error al configurar la herramienta de IA externa: {error}",
"mcp.toast.disconnectFailed": "Error al desconectar la herramienta de IA externa: {error}",
"mcp.error.clipboardUnavailable": "La API del portapapeles no está disponible",
"save.toast.saved": "Guardado",
"save.toast.nothingToSave": "No hay nada que guardar",
"save.toast.missingActiveVault": "Seleccione o restaure un almacén antes de guardar.",
"save.error.failed": "Error al guardar: {error}",
"save.error.autoFailed": "Error al guardar automáticamente: {error}",
"save.error.invalidPath": "Error al guardar: la ruta de la nota no es válida en esta plataforma. Cambie el nombre de la nota o muévala a una carpeta válida y, a continuación, vuelva a intentarlo.",
"savedViews.reordered": "Vistas reordenadas",
"locale.en": "Inglés",
"sidebar.nav.inbox": "Bandeja de entrada",
"sidebar.nav.allNotes": "Todas las notas",
@@ -149,6 +221,9 @@
"sidebar.action.createView": "Crear vista",
"sidebar.action.editView": "Editar vista",
"sidebar.action.deleteView": "Eliminar vista",
"sidebar.action.reorderView": "Reorganizar vista",
"sidebar.action.moveViewUp": "Mover vista hacia arriba",
"sidebar.action.moveViewDown": "Mover vista hacia abajo",
"sidebar.action.customizeSections": "Personalizar secciones",
"sidebar.action.renameSection": "Cambiar el nombre de la sección…",
"sidebar.action.customizeIconColor": "Personalizar ícono y color…",
@@ -318,6 +393,8 @@
"status.vault.cloneGit": "Clonar repositorio Git",
"status.vault.cloneGettingStarted": "Clonar el vault de introducción",
"status.vault.notFound": "No se encontró el almacén: {path}",
"status.vault.reloading": "Volviendo a cargar el vault...",
"status.vault.reloadingTooltip": "Volviendo a cargar el repositorio desde el disco",
"status.vault.remove": "Eliminar {label} de la lista",
"status.remote.noneConfigured": "No se ha configurado ningún repositorio remoto",
"status.remote.inSync": "Sincronizado con el repositorio remoto",

View File

@@ -12,6 +12,7 @@
"command.openLanguageSettings.keywords": "idioma configuración regional i18n internacionalización localización inglés italiano francés alemán ruso español portugués chino japonés coreano 中文",
"command.useSystemLanguage": "Usar el idioma del sistema",
"command.openH1Setting": "Abrir la configuración de renombrado automático de H1",
"command.toggleGitignoredFilesVisibility": "Activar/desactivar la visibilidad de los archivos Gitignored",
"command.contribute": "Contribuir",
"command.checkUpdates": "Buscar actualizaciones",
"command.group.navigation": "Navegación",
@@ -68,6 +69,10 @@
"command.view.toggleAiPanel": "Activar/desactivar el panel de IA",
"command.view.newAiChat": "Nuevo chat de IA",
"command.view.toggleBacklinks": "Activar/desactivar los enlaces de retroceso",
"command.view.moveViewUp": "Subir la vista",
"command.view.moveViewDown": "Mover vista hacia abajo",
"command.view.moveNamedViewUp": "Subir {name}",
"command.view.moveNamedViewDown": "Mover {name} hacia abajo",
"command.view.zoomIn": "Acercar ({zoom} %)",
"command.view.zoomOut": "Alejar ({zoom} %)",
"command.view.resetZoom": "Restablecer el zoom",
@@ -81,6 +86,7 @@
"command.settings.repairVault": "Reparar el almacén",
"command.settings.useLightMode": "Usar modo claro",
"command.settings.useDarkMode": "Usar modo oscuro",
"command.settings.toggleGitignoredFilesVisibility": "Activar/desactivar la visibilidad de los archivos gitignored",
"command.ai.openAgents": "Abrir agentes de IA",
"command.ai.restoreGuidance": "Restaurar la guía de IA de Tolaria",
"command.ai.switchToAgent": "Cambiar el agente de IA a {agent}",
@@ -115,6 +121,10 @@
"settings.titles.description": "Decida si Tolaria sincroniza automáticamente los nombres de los archivos de las notas sin título a partir del primer título H1.",
"settings.titles.autoRename": "Cambiar automático del nombre de las notas sin título a partir del primer H1",
"settings.titles.autoRenameDescription": "Cuando esta opción está activada, Tolaria cambia el nombre de los archivos de notas sin título en cuanto el primer H1 se convierte en un título real. Desactive esta opción para que el nombre del archivo no cambie hasta que lo renombre manualmente desde la barra de navegación.",
"settings.vaultContent.title": "Contenido del repositorio",
"settings.vaultContent.description": "Controla si los archivos que coinciden con el .gitignore del repositorio aparecen en Tolaria.",
"settings.vaultContent.hideGitignored": "Ocultar archivos y carpetas ignorados por Git",
"settings.vaultContent.hideGitignoredDescription": "Evita que los archivos del almacén generados y solo locales aparezcan en las notas, la búsqueda, la apertura rápida y las carpetas.",
"settings.aiAgents.title": "Agentes de IA",
"settings.aiAgents.description": "Elija qué agente de IA de CLI utiliza Tolaria en el panel de IA y en la paleta de comandos.",
"settings.aiAgents.default": "Agente de IA predeterminado",
@@ -138,6 +148,68 @@
"settings.cancel": "Cancelar",
"settings.save": "Guardar",
"common.cancel": "Cancelar",
"ai.permission.safe.short": "Safe",
"ai.permission.safe.control": "Vault Safe",
"ai.permission.safe.tooltip": "Con Vault Safe, los agentes solo pueden acceder a las herramientas de archivos, búsqueda y edición.",
"ai.permission.powerUser.short": "Usuario avanzado",
"ai.permission.powerUser.control": "Usuario avanzado",
"ai.permission.powerUser.tooltip": "Power User también permite ejecutar comandos de shell locales en este almacén.",
"ai.permission.modeAria": "Modo de permisos de IA para agentes",
"ai.permission.changed": "Modo de permisos de IA cambiado a {label}. Se aplicará al siguiente mensaje.",
"ai.panel.title": "Agente de IA",
"ai.panel.status.checking": "Comprobando disponibilidad",
"ai.panel.status.missing": "{agent} · no instalado",
"ai.panel.status.ready": "{agent} · {mode}",
"ai.panel.copyMcpConfig": "Copiar configuración de MCP",
"ai.panel.mcpConfig": "Configuración de MCP",
"ai.panel.newChat": "Nuevo chat de IA",
"ai.panel.close": "Cerrar el panel de IA",
"ai.panel.linkedCount": "+ {count} vinculados",
"ai.panel.empty.checkingTitle": "Comprobando la disponibilidad del agente de IA",
"ai.panel.empty.checkingDescription": "Se podrán enviar mensajes cuando el agente seleccionado esté disponible",
"ai.panel.empty.missingTitle": "{agent} no está disponible en este equipo",
"ai.panel.empty.missingDescription": "Instálelo o cambie el agente de IA predeterminado en Ajustes",
"ai.panel.empty.withContextTitle": "Preguntar a {agent} sobre esta nota y su contexto vinculado",
"ai.panel.empty.noContextTitle": "Abre una nota y, a continuación, pregúntale a {agent} sobre ella",
"ai.panel.empty.withContextDescription": "Resumir, encontrar conexiones, desarrollar ideas",
"ai.panel.empty.noContextDescription": "La IA utilizará la nota activa como contexto",
"ai.panel.placeholder.checking": "Comprobando la disponibilidad del agente de IA...",
"ai.panel.placeholder.missing": "{agent} no está instalado. Abre Agentes de IA en Ajustes.",
"ai.panel.placeholder.ready": "Preguntar a {agent}",
"ai.panel.send": "Enviar mensaje",
"mcp.setup.manageTitle": "Gestionar herramientas externas de IA",
"mcp.setup.setupTitle": "Configurar herramientas de IA externas",
"mcp.setup.connectedDescription": "Tolaria ya está conectada a herramientas de IA externas para este almacén. Vuelve a conectar Tolaria para actualizar la configuración o desconéctala para eliminarla de esos archivos de configuración de terceros.",
"mcp.setup.setupDescription": "Tolaria puede añadir su servidor MCP a las herramientas de IA externas de esta vault, pero no modificará los archivos de configuración de terceros hasta que lo confirmes aquí.",
"mcp.setup.reconnect": "Volver a conectar las herramientas externas de IA",
"mcp.setup.connect": "Conectar herramientas externas de IA",
"mcp.setup.disconnect": "Desconectar",
"mcp.setup.connecting": "Conectando…",
"mcp.setup.disconnecting": "Desconectando…",
"mcp.setup.nodeRequirement": "Esta configuración requiere Node.js 18 o superior en PATH y una herramienta de escritorio compatible con MCP. Tolaria comprueba Node.js antes de escribir la configuración para que la herramienta no apunte a un comando que no funcione.",
"mcp.setup.writeEntryDescription": "Al confirmar esta acción, se escribirá o se actualizará la única entrada MCP de Tolaria ({entry}) en:",
"mcp.setup.clientPathsDescription": "La CLI de Claude Code lee ~/.claude.json, la CLI de Gemini lee ~/.gemini/settings.json, Cursor lee ~/.cursor/mcp.json y otras herramientas compatibles con MCP leen la ruta genérica ~/.config/mcp/mcp.json. Al cerrar la aplicación, todos los archivos permanecen intactos a menos que te conectes o te desconectes; volver a conectarte es idempotente y, al desconectarte, se vuelve a eliminar la entrada de Tolaria.",
"mcp.setup.geminiGuidanceDescription": "La CLI de Gemini requiere su propia instalación e inicio de sesión. Utiliza Restore Tolaria AI Guidance cuando quieras una capa de compatibilidad GEMINI.md en la raíz del almacén que redirija a Gemini a las instrucciones compartidas de AGENTS.md sin sobrescribir la guía personalizada.",
"mcp.setup.manual.title": "Configuración manual de MCP",
"mcp.setup.manual.copy": "Copiar la configuración de MCP",
"mcp.setup.manual.loading": "Cargando la configuración exacta de MCP...",
"mcp.setup.manual.unavailable": "La configuración exacta está disponible después de abrir un almacén.",
"mcp.toast.connected": "Herramientas de IA externas de Tolaria conectadas correctamente",
"mcp.toast.refreshed": "Configuración de las herramientas externas de IA de Tolaria actualizada correctamente",
"mcp.toast.disconnected": "Herramientas de IA externas de Tolaria desconectadas correctamente",
"mcp.toast.alreadyDisconnected": "Las herramientas externas de IA de Tolaria ya estaban desconectadas",
"mcp.toast.configCopied": "Configuración de Tolaria MCP copiada al portapapeles",
"mcp.toast.configCopyFailed": "Error al copiar la configuración de MCP: {error}",
"mcp.toast.setupFailed": "Error al configurar la herramienta de IA externa: {error}",
"mcp.toast.disconnectFailed": "Error al desconectar la herramienta de IA externa: {error}",
"mcp.error.clipboardUnavailable": "La API del portapapeles no está disponible",
"save.toast.saved": "Guardado",
"save.toast.nothingToSave": "No hay nada que guardar",
"save.toast.missingActiveVault": "Seleccione o restaure un almacén antes de guardar.",
"save.error.failed": "Error al guardar: {error}",
"save.error.autoFailed": "Error en el guardado automático: {error}",
"save.error.invalidPath": "Error al guardar: la ruta de la nota no es válida en esta plataforma. Cambie el nombre de la nota o muévala a una carpeta válida y, a continuación, inténtelo de nuevo.",
"savedViews.reordered": "Vistas reordenadas",
"locale.en": "Español",
"sidebar.nav.inbox": "Bandeja de entrada",
"sidebar.nav.allNotes": "Todas las notas",
@@ -149,6 +221,9 @@
"sidebar.action.createView": "Crear vista",
"sidebar.action.editView": "Editar vista",
"sidebar.action.deleteView": "Eliminar vista",
"sidebar.action.reorderView": "Reordenar vista",
"sidebar.action.moveViewUp": "Subir vista",
"sidebar.action.moveViewDown": "Mover vista hacia abajo",
"sidebar.action.customizeSections": "Personalizar secciones",
"sidebar.action.renameSection": "Cambiar el nombre de la sección…",
"sidebar.action.customizeIconColor": "Personalizar icono y color…",
@@ -318,6 +393,8 @@
"status.vault.cloneGit": "Clonar repositorio Git",
"status.vault.cloneGettingStarted": "Clonar el almacén «Primeros pasos»",
"status.vault.notFound": "No se ha encontrado el almacén: {path}",
"status.vault.reloading": "Volviendo a cargar el repositorio...",
"status.vault.reloadingTooltip": "Recargando el repositorio desde el disco",
"status.vault.remove": "Eliminar {label} de la lista",
"status.remote.noneConfigured": "No hay ningún repositorio remoto configurado",
"status.remote.inSync": "Sincronizado con el repositorio remoto",

View File

@@ -12,6 +12,7 @@
"command.openLanguageSettings.keywords": "langue paramètres régionaux i18n internationalisation localisation anglais italien français allemand russe espagnol portugais chinois japonais coréen 中文",
"command.useSystemLanguage": "Utiliser la langue du système",
"command.openH1Setting": "Ouvrir le paramètre de renommage automatique des titres H1",
"command.toggleGitignoredFilesVisibility": "Activer/désactiver la visibilité des fichiers Gitignored",
"command.contribute": "Contribuer",
"command.checkUpdates": "Rechercher les mises à jour",
"command.group.navigation": "Navigation",
@@ -68,6 +69,10 @@
"command.view.toggleAiPanel": "Activer/désactiver le panneau d'IA",
"command.view.newAiChat": "Nouveau chat IA",
"command.view.toggleBacklinks": "Activer/désactiver les backlinks",
"command.view.moveViewUp": "Déplacer la vue vers le haut",
"command.view.moveViewDown": "Déplacer la vue vers le bas",
"command.view.moveNamedViewUp": "Déplacer {name} vers le haut",
"command.view.moveNamedViewDown": "Déplacer {name} vers le bas",
"command.view.zoomIn": "Zoom avant ({zoom} %)",
"command.view.zoomOut": "Zoom arrière ({zoom} %)",
"command.view.resetZoom": "Réinitialiser le zoom",
@@ -81,6 +86,7 @@
"command.settings.repairVault": "Réparer le coffre-fort",
"command.settings.useLightMode": "Utiliser le mode clair",
"command.settings.useDarkMode": "Utiliser le mode sombre",
"command.settings.toggleGitignoredFilesVisibility": "Activer/désactiver la visibilité des fichiers Gitignored",
"command.ai.openAgents": "Ouvrir les agents d'IA",
"command.ai.restoreGuidance": "Restaurer les conseils de l'IA Tolaria",
"command.ai.switchToAgent": "Basculer l'agent d'IA sur {agent}",
@@ -115,6 +121,10 @@
"settings.titles.description": "Indiquez si Tolaria doit synchroniser automatiquement les noms de fichier des notes sans titre à partir du premier titre H1.",
"settings.titles.autoRename": "Renommer automatiquement les notes sans titre à partir du premier H1",
"settings.titles.autoRenameDescription": "Lorsque cette option est activée, Tolaria renomme les fichiers de notes sans titre dès que le premier H1 devient un véritable titre. Désactivez cette option pour que le nom de fichier reste inchangé jusqu'à ce que vous le renommiez manuellement depuis la barre de navigation.",
"settings.vaultContent.title": "Contenu du coffre-fort",
"settings.vaultContent.description": "Contrôler si les fichiers correspondants au fichier .gitignore du coffre-fort apparaissent dans Tolaria.",
"settings.vaultContent.hideGitignored": "Masquer les fichiers et les dossiers ignorés par Git",
"settings.vaultContent.hideGitignoredDescription": "Exclut les fichiers du coffre-fort générés et locaux des notes, de la recherche, de l'ouverture rapide et des dossiers.",
"settings.aiAgents.title": "Agents d'IA",
"settings.aiAgents.description": "Choisissez l'agent d'IA CLI que Tolaria utilise dans le panneau d'IA et la palette de commandes.",
"settings.aiAgents.default": "Agent d'IA par défaut",
@@ -138,6 +148,68 @@
"settings.cancel": "Annuler",
"settings.save": "Enregistrer",
"common.cancel": "Annuler",
"ai.permission.safe.short": "Safe",
"ai.permission.safe.control": "Vault Safe",
"ai.permission.safe.tooltip": "Avec Vault Safe, les agents n'ont accès qu'aux outils de gestion de fichiers, de recherche et d'édition.",
"ai.permission.powerUser.short": "Utilisateur avancé",
"ai.permission.powerUser.control": "Utilisateur avancé",
"ai.permission.powerUser.tooltip": "Power User autorise également les commandes shell locales pour ce coffre-fort.",
"ai.permission.modeAria": "Mode d'autorisation de l'agent d'IA",
"ai.permission.changed": "Mode d'autorisation de l'IA modifié : {label}. Il s'appliquera au prochain message.",
"ai.panel.title": "Agent IA",
"ai.panel.status.checking": "Vérification de la disponibilité",
"ai.panel.status.missing": "{agent} · non installé",
"ai.panel.status.ready": "{agent} · {mode}",
"ai.panel.copyMcpConfig": "Copier la configuration MCP",
"ai.panel.mcpConfig": "Configuration MCP",
"ai.panel.newChat": "Nouveau chat IA",
"ai.panel.close": "Fermer le panneau d'IA",
"ai.panel.linkedCount": "+ {count} liés",
"ai.panel.empty.checkingTitle": "Vérification de la disponibilité de l'agent IA",
"ai.panel.empty.checkingDescription": "Les messages pourront être envoyés lorsque l'agent sélectionné sera disponible",
"ai.panel.empty.missingTitle": "{agent} n'est pas disponible sur cet ordinateur",
"ai.panel.empty.missingDescription": "Installez-le ou modifiez l'agent d'IA par défaut dans les Paramètres",
"ai.panel.empty.withContextTitle": "Interroger {agent} sur cette note et son contexte associé",
"ai.panel.empty.noContextTitle": "Ouvrez une note, puis posez des questions à {agent} à son sujet",
"ai.panel.empty.withContextDescription": "Résumer, trouver des liens, développer des idées",
"ai.panel.empty.noContextDescription": "L'IA utilisera la note active comme contexte",
"ai.panel.placeholder.checking": "Vérification de la disponibilité de l'agent d'IA…",
"ai.panel.placeholder.missing": "{agent} n'est pas installé. Ouvrez Agents IA dans les Paramètres.",
"ai.panel.placeholder.ready": "Demander à {agent}",
"ai.panel.send": "Envoyer un message",
"mcp.setup.manageTitle": "Gérer les outils d'IA externes",
"mcp.setup.setupTitle": "Configurer des outils d'IA externes",
"mcp.setup.connectedDescription": "Tolaria est déjà connectée à des outils d'IA externes pour ce coffre-fort. Reconnectez-vous pour actualiser la configuration, ou déconnectez-vous pour supprimer Tolaria de ces fichiers de configuration tiers.",
"mcp.setup.setupDescription": "Tolaria peut ajouter son serveur MCP aux outils d'IA externes pour ce coffre-fort, mais elle ne modifiera pas les fichiers de configuration tiers tant que vous n'aurez pas donné votre confirmation ici.",
"mcp.setup.reconnect": "Reconnecter les outils d'IA externes",
"mcp.setup.connect": "Connecter des outils d'IA externes",
"mcp.setup.disconnect": "Déconnecter",
"mcp.setup.connecting": "Connexion en cours…",
"mcp.setup.disconnecting": "Déconnexion en cours…",
"mcp.setup.nodeRequirement": "Cette configuration nécessite Node.js 18 ou une version ultérieure dans le PATH et un outil de bureau compatible MCP. Tolaria vérifie Node.js avant d'écrire la configuration afin que l'outil ne pointe pas vers une commande défaillante.",
"mcp.setup.writeEntryDescription": "Si vous confirmez cette action, la seule entrée MCP {entry} de Tolaria sera écrite ou mise à jour dans :",
"mcp.setup.clientPathsDescription": "La CLI de Claude Code lit ~/.claude.json, la CLI de Gemini lit ~/.gemini/settings.json, Cursor lit ~/.cursor/mcp.json, et le chemin générique ~/.config/mcp/mcp.json est pris en charge par d'autres outils compatibles MCP. Lorsque vous fermez Tolaria, tous les fichiers restent inchangés, sauf si vous vous connectez ou vous déconnectez ; la reconnexion est idempotente et la déconnexion supprime à nouveau l'entrée de Tolaria.",
"mcp.setup.geminiGuidanceDescription": "La CLI Gemini nécessite sa propre installation et sa propre connexion. Utilisez Restore Tolaria AI Guidance lorsque vous souhaitez obtenir un calque de compatibilité GEMINI.md au niveau de la racine du coffre-fort, qui renvoie Gemini vers les instructions AGENTS.md partagées sans écraser les instructions personnalisées.",
"mcp.setup.manual.title": "Configuration MCP manuelle",
"mcp.setup.manual.copy": "Copier la configuration MCP",
"mcp.setup.manual.loading": "Chargement de la configuration MCP exacte…",
"mcp.setup.manual.unavailable": "La configuration exacte est disponible après l'ouverture d'un coffre-fort.",
"mcp.toast.connected": "Outils d'IA externes Tolaria connectés avec succès",
"mcp.toast.refreshed": "Configuration des outils d'IA externes Tolaria actualisée avec succès",
"mcp.toast.disconnected": "Outils d'IA externes Tolaria déconnectés avec succès",
"mcp.toast.alreadyDisconnected": "Les outils d'IA externes de Tolaria étaient déjà déconnectés",
"mcp.toast.configCopied": "Configuration MCP de Tolaria copiée dans le presse-papiers",
"mcp.toast.configCopyFailed": "Échec de la copie de la configuration MCP : {error}",
"mcp.toast.setupFailed": "Échec de la configuration de l'outil d'IA externe : {error}",
"mcp.toast.disconnectFailed": "Échec de la déconnexion de l'outil d'IA externe : {error}",
"mcp.error.clipboardUnavailable": "L'API du presse-papiers n'est pas disponible",
"save.toast.saved": "Enregistré",
"save.toast.nothingToSave": "Rien à enregistrer",
"save.toast.missingActiveVault": "Sélectionnez ou restaurez un coffre avant d'enregistrer.",
"save.error.failed": "Échec de l'enregistrement : {error}",
"save.error.autoFailed": "Échec de l'enregistrement automatique : {error}",
"save.error.invalidPath": "Échec de l'enregistrement : le chemin d'accès de la note n'est pas valide sur cette plateforme. Renommez la note ou déplacez-la dans un dossier valide, puis réessayez.",
"savedViews.reordered": "Vues réorganisées",
"locale.en": "Français",
"sidebar.nav.inbox": "Boîte de réception",
"sidebar.nav.allNotes": "Toutes les notes",
@@ -149,6 +221,9 @@
"sidebar.action.createView": "Créer une vue",
"sidebar.action.editView": "Modifier la vue",
"sidebar.action.deleteView": "Supprimer la vue",
"sidebar.action.reorderView": "Réorganiser la vue",
"sidebar.action.moveViewUp": "Déplacer la vue vers le haut",
"sidebar.action.moveViewDown": "Déplacer la vue vers le bas",
"sidebar.action.customizeSections": "Personnaliser les sections",
"sidebar.action.renameSection": "Renommer la section…",
"sidebar.action.customizeIconColor": "Personnaliser l'icône et la couleur…",
@@ -318,6 +393,8 @@
"status.vault.cloneGit": "Cloner le dépôt Git",
"status.vault.cloneGettingStarted": "Cloner le coffre-fort « Premiers pas »",
"status.vault.notFound": "Coffre-fort introuvable : {path}",
"status.vault.reloading": "Rechargement du coffre-fort…",
"status.vault.reloadingTooltip": "Rechargement du coffre à partir du disque",
"status.vault.remove": "Supprimer {label} de la liste",
"status.remote.noneConfigured": "Aucun serveur distant configuré",
"status.remote.inSync": "Synchronisé avec le dépôt distant",

View File

@@ -12,6 +12,7 @@
"command.openLanguageSettings.keywords": "lingua locale i18n internazionalizzazione localizzazione inglese italiano francese tedesco russo spagnolo portoghese cinese giapponese coreano 中文",
"command.useSystemLanguage": "Usa la lingua di sistema",
"command.openH1Setting": "Apri l'impostazione di rinomina automatica H1",
"command.toggleGitignoredFilesVisibility": "Attiva/disattiva la visibilità dei file Gitignored",
"command.contribute": "Contribuisci",
"command.checkUpdates": "Verifica disponibilità aggiornamenti",
"command.group.navigation": "Navigazione",
@@ -68,6 +69,10 @@
"command.view.toggleAiPanel": "Attiva/disattiva pannello IA",
"command.view.newAiChat": "Nuova chat con l'IA",
"command.view.toggleBacklinks": "Attiva/disattiva backlink",
"command.view.moveViewUp": "Sposta vista in alto",
"command.view.moveViewDown": "Sposta vista in basso",
"command.view.moveNamedViewUp": "Sposta {name} in alto",
"command.view.moveNamedViewDown": "Sposta {name} in basso",
"command.view.zoomIn": "Ingrandisci ({zoom}%)",
"command.view.zoomOut": "Riduci ({zoom}%)",
"command.view.resetZoom": "Ripristina zoom",
@@ -81,6 +86,7 @@
"command.settings.repairVault": "Ripara Vault",
"command.settings.useLightMode": "Usa modalità chiara",
"command.settings.useDarkMode": "Usa modalità scura",
"command.settings.toggleGitignoredFilesVisibility": "Attiva/disattiva la visibilità dei file Gitignored",
"command.ai.openAgents": "Apri agenti IA",
"command.ai.restoreGuidance": "Ripristina la guida IA di Tolaria",
"command.ai.switchToAgent": "Passa ad agente IA {agent}",
@@ -115,6 +121,10 @@
"settings.titles.description": "Scegli se desideri che Tolaria sincronizzi automaticamente i nomi dei file delle note senza titolo in base al primo titolo H1.",
"settings.titles.autoRename": "Rinomina automaticamente le note senza titolo in base al primo titolo H1",
"settings.titles.autoRenameDescription": "Se questa opzione è abilitata, Tolaria rinomina i file delle note senza titolo non appena il primo H1 diventa un titolo effettivo. Disattiva questa opzione per mantenere invariato il nome del file finché non lo rinomini manualmente dalla barra di navigazione.",
"settings.vaultContent.title": "Contenuto del vault",
"settings.vaultContent.description": "Controlla se i file corrispondenti al file .gitignore del vault vengono visualizzati in Tolaria.",
"settings.vaultContent.hideGitignored": "Nascondi file e cartelle ignorati da Git",
"settings.vaultContent.hideGitignoredDescription": "Esclude i file del vault generati e quelli solo locali dalle note, dalla ricerca, dall'apertura rapida e dalle cartelle.",
"settings.aiAgents.title": "Agenti IA",
"settings.aiAgents.description": "Scegli quale agente IA CLI deve utilizzare Tolaria nel pannello IA e nella palette dei comandi.",
"settings.aiAgents.default": "Agente IA predefinito",
@@ -138,6 +148,68 @@
"settings.cancel": "Annulla",
"settings.save": "Salva",
"common.cancel": "Annulla",
"ai.permission.safe.short": "Safe",
"ai.permission.safe.control": "Vault Safe",
"ai.permission.safe.tooltip": "Con Vault Safe, gli agenti possono accedere solo agli strumenti per i file, la ricerca e la modifica.",
"ai.permission.powerUser.short": "Power User",
"ai.permission.powerUser.control": "Power User",
"ai.permission.powerUser.tooltip": "Power User consente inoltre l'esecuzione di comandi shell locali per questo vault.",
"ai.permission.modeAria": "Modalità di autorizzazione dell'agente IA",
"ai.permission.changed": "Modalità di autorizzazione IA modificata in {label}. Si applicherà al messaggio successivo.",
"ai.panel.title": "AI Agent",
"ai.panel.status.checking": "Verifica della disponibilità in corso",
"ai.panel.status.missing": "{agent} · non installato",
"ai.panel.status.ready": "{agent} · {mode}",
"ai.panel.copyMcpConfig": "Copia configurazione MCP",
"ai.panel.mcpConfig": "Configurazione MCP",
"ai.panel.newChat": "Nuova chat con l'IA",
"ai.panel.close": "Chiudi il pannello IA",
"ai.panel.linkedCount": "+ {count} collegati",
"ai.panel.empty.checkingTitle": "Verifica della disponibilità dell'agente IA",
"ai.panel.empty.checkingDescription": "È possibile inviare messaggi quando l'agente selezionato è disponibile",
"ai.panel.empty.missingTitle": "{agent} non è disponibile su questo computer",
"ai.panel.empty.missingDescription": "Installalo o cambia l'agente IA predefinito nelle Impostazioni",
"ai.panel.empty.withContextTitle": "Chiedi a {agent} informazioni su questa nota e sul contesto a cui è collegata",
"ai.panel.empty.noContextTitle": "Apri una nota, quindi chiedi a {agent} informazioni al riguardo",
"ai.panel.empty.withContextDescription": "Riassumi, trova connessioni, sviluppa idee",
"ai.panel.empty.noContextDescription": "L'IA utilizzerà la nota attiva come contesto",
"ai.panel.placeholder.checking": "Verifica della disponibilità dell'agente IA in corso...",
"ai.panel.placeholder.missing": "{agent} non è installato. Apri Agenti IA in Impostazioni.",
"ai.panel.placeholder.ready": "Chiedi a {agent}",
"ai.panel.send": "Invia messaggio",
"mcp.setup.manageTitle": "Gestisci strumenti di IA esterni",
"mcp.setup.setupTitle": "Configura strumenti di IA esterni",
"mcp.setup.connectedDescription": "Tolaria è già connessa a strumenti di IA esterni per questo vault. Riconnetti per aggiornare la configurazione o disconnetti per rimuovere Tolaria da quei file di configurazione di terze parti.",
"mcp.setup.setupDescription": "Tolaria può aggiungere il suo server MCP agli strumenti di IA esterni per questo vault, ma non modificherà i file di configurazione di terze parti finché non confermerai qui.",
"mcp.setup.reconnect": "Riconnetti gli strumenti di IA esterni",
"mcp.setup.connect": "Connetti strumenti di IA esterni",
"mcp.setup.disconnect": "Disconnetti",
"mcp.setup.connecting": "Connessione in corso…",
"mcp.setup.disconnecting": "Disconnessione in corso…",
"mcp.setup.nodeRequirement": "Per questa configurazione sono necessari Node.js 18 o versioni successive nel PATH e uno strumento desktop compatibile con MCP. Tolaria verifica Node.js prima di scrivere la configurazione, in modo che lo strumento non punti a un comando non funzionante.",
"mcp.setup.writeEntryDescription": "Confermando questa azione, verrà scritta o aggiornata l'unica voce MCP {entry} di Tolaria in:",
"mcp.setup.clientPathsDescription": "La CLI di Claude Code legge ~/.claude.json, la CLI di Gemini legge ~/.gemini/settings.json, Cursor legge ~/.cursor/mcp.json e il percorso generico ~/.config/mcp/mcp.json viene utilizzato da altri strumenti compatibili con MCP. Alla chiusura, tutti i file rimangono inalterati a meno che non ci si connetta o si disconnetta; la riconnessione è idempotente e la disconnessione rimuove nuovamente la voce di Tolaria.",
"mcp.setup.geminiGuidanceDescription": "La CLI di Gemini richiede una propria installazione e un proprio accesso. Utilizza Restore Tolaria AI Guidance quando desideri uno shim di compatibilità GEMINI.md a livello di vault-root che reindirizzi Gemini alle istruzioni condivise di AGENTS.md senza sovrascrivere la guida personalizzata.",
"mcp.setup.manual.title": "Configurazione MCP manuale",
"mcp.setup.manual.copy": "Copia la configurazione MCP",
"mcp.setup.manual.loading": "Caricamento della configurazione MCP esatta in corso...",
"mcp.setup.manual.unavailable": "La configurazione esatta è disponibile dopo l'apertura di un vault.",
"mcp.toast.connected": "Strumenti di IA esterni di Tolaria collegati correttamente",
"mcp.toast.refreshed": "Configurazione degli strumenti di IA esterni di Tolaria aggiornata correttamente",
"mcp.toast.disconnected": "Strumenti di IA esterni di Tolaria disconnessi correttamente",
"mcp.toast.alreadyDisconnected": "Gli strumenti di IA esterni di Tolaria erano già disconnessi",
"mcp.toast.configCopied": "Configurazione MCP di Tolaria copiata negli appunti",
"mcp.toast.configCopyFailed": "Copia della configurazione MCP non riuscita: {error}",
"mcp.toast.setupFailed": "Configurazione dello strumento di IA esterno non riuscita: {error}",
"mcp.toast.disconnectFailed": "Disconnessione dello strumento di IA esterno non riuscita: {error}",
"mcp.error.clipboardUnavailable": "API della clipboard non disponibile",
"save.toast.saved": "Salvato",
"save.toast.nothingToSave": "Niente da salvare",
"save.toast.missingActiveVault": "Seleziona o ripristina un vault prima di salvare.",
"save.error.failed": "Salvataggio non riuscito: {error}",
"save.error.autoFailed": "Salvataggio automatico non riuscito: {error}",
"save.error.invalidPath": "Salvataggio non riuscito: il percorso della nota non è valido su questa piattaforma. Rinomina la nota o spostala in una cartella valida, quindi riprova.",
"savedViews.reordered": "Viste riordinate",
"locale.en": "Inglese",
"sidebar.nav.inbox": "Inbox",
"sidebar.nav.allNotes": "Tutte le note",
@@ -149,6 +221,9 @@
"sidebar.action.createView": "Crea vista",
"sidebar.action.editView": "Modifica vista",
"sidebar.action.deleteView": "Elimina vista",
"sidebar.action.reorderView": "Riordina vista",
"sidebar.action.moveViewUp": "Sposta vista in alto",
"sidebar.action.moveViewDown": "Sposta vista in basso",
"sidebar.action.customizeSections": "Personalizza sezioni",
"sidebar.action.renameSection": "Rinomina sezione…",
"sidebar.action.customizeIconColor": "Personalizza icona e colore…",
@@ -318,6 +393,8 @@
"status.vault.cloneGit": "Clona repository Git",
"status.vault.cloneGettingStarted": "Clona il vault Getting Started",
"status.vault.notFound": "Vault non trovato: {path}",
"status.vault.reloading": "Ricaricamento del vault in corso...",
"status.vault.reloadingTooltip": "Ricaricamento del vault dal disco in corso...",
"status.vault.remove": "Rimuovi {label} dall'elenco",
"status.remote.noneConfigured": "Nessun remoto configurato",
"status.remote.inSync": "Sincronizzato con il repository remoto",

View File

@@ -12,6 +12,7 @@
"command.openLanguageSettings.keywords": "言語 ロケール i18n 国際化 ローカリゼーション 英語 イタリア語 フランス語 ドイツ語 ロシア語 スペイン語 ポルトガル語 中国語 日本語 韓国語 中文",
"command.useSystemLanguage": "システム言語を使用",
"command.openH1Setting": "H1 自動名前変更設定を開く",
"command.toggleGitignoredFilesVisibility": "Gitignoredファイルの表示/非表示を切り替える",
"command.contribute": "投稿する",
"command.checkUpdates": "更新の確認",
"command.group.navigation": "ナビゲーション",
@@ -68,6 +69,10 @@
"command.view.toggleAiPanel": "AIパネルの切り替え",
"command.view.newAiChat": "新しいAIチャット",
"command.view.toggleBacklinks": "バックリンクの切り替え",
"command.view.moveViewUp": "ビューを上に移動",
"command.view.moveViewDown": "ビューを下に移動",
"command.view.moveNamedViewUp": "{name}を上に移動",
"command.view.moveNamedViewDown": "{name}を下に移動",
"command.view.zoomIn": "拡大({zoom}%",
"command.view.zoomOut": "ズームアウト({zoom}%",
"command.view.resetZoom": "ズームをリセット",
@@ -81,6 +86,7 @@
"command.settings.repairVault": "Vault を修復",
"command.settings.useLightMode": "ライトモードを使用",
"command.settings.useDarkMode": "ダークモードを使用",
"command.settings.toggleGitignoredFilesVisibility": "Gitignoredファイルの表示/非表示を切り替える",
"command.ai.openAgents": "AIエージェントを開く",
"command.ai.restoreGuidance": "Tolaria AIガイダンスを復元",
"command.ai.switchToAgent": "AIエージェントを{agent}に切り替える",
@@ -115,6 +121,10 @@
"settings.titles.description": "Tolaria が、タイトルのないノートのファイル名を最初の H1 タイトルに基づいて自動的に同期するかどうかを選択します。",
"settings.titles.autoRename": "タイトルのないートのファイル名を最初のH1から自動的に変更する",
"settings.titles.autoRenameDescription": "この設定を有効にすると、Tolariaは最初のH1が実際のタイトルになった時点で、タイトルのないートファイルの名前を変更します。この設定をオフにすると、ブレッドクラムバーから手動で名前を変更するまで、ファイル名は変更されません。",
"settings.vaultContent.title": "Vaultのコンテンツ",
"settings.vaultContent.description": "Vaultの.gitignoreで一致したファイルをTolariaに表示するかどうかを制御します。",
"settings.vaultContent.hideGitignored": "Gitによって無視されるファイルとフォルダーを非表示にする",
"settings.vaultContent.hideGitignoredDescription": "生成されたVaultファイルとローカル専用のVaultファイルを、ート、検索、クイックオープン、フォルダーから除外します。",
"settings.aiAgents.title": "AIエージェント",
"settings.aiAgents.description": "TolariaがAIパネルとコマンドパレットで使用するCLI AIエージェントを選択します。",
"settings.aiAgents.default": "デフォルトのAIエージェント",
@@ -138,6 +148,68 @@
"settings.cancel": "キャンセル",
"settings.save": "保存",
"common.cancel": "キャンセル",
"ai.permission.safe.short": "Safe",
"ai.permission.safe.control": "Vault Safe",
"ai.permission.safe.tooltip": "Vault Safeでは、エージェントはファイル、検索、編集のツールのみを使用できます。",
"ai.permission.powerUser.short": "Power User",
"ai.permission.powerUser.control": "Power User",
"ai.permission.powerUser.tooltip": "Power User は、この Vault に対してローカルのシェルコマンドも許可します。",
"ai.permission.modeAria": "AIエージェントのアクセス許可モード",
"ai.permission.changed": "AI 権限モードが {label} に変更されました。これは次のメッセージに適用されます。",
"ai.panel.title": "AI Agent",
"ai.panel.status.checking": "利用可能状況を確認中",
"ai.panel.status.missing": "{agent} · インストールされていません",
"ai.panel.status.ready": "{agent} · {mode}",
"ai.panel.copyMcpConfig": "MCP構成をコピー",
"ai.panel.mcpConfig": "MCP config",
"ai.panel.newChat": "新しいAIチャット",
"ai.panel.close": "AIパネルを閉じる",
"ai.panel.linkedCount": "+ {count}件リンク済み",
"ai.panel.empty.checkingTitle": "AIエージェントの対応可能状況を確認中",
"ai.panel.empty.checkingDescription": "選択したエージェントの準備が整ったら、メッセージを送信できます",
"ai.panel.empty.missingTitle": "{agent}はこのマシンでは利用できません",
"ai.panel.empty.missingDescription": "インストールするか、設定でデフォルトのAIエージェントを切り替えてください",
"ai.panel.empty.withContextTitle": "{agent}にこのノートとその関連コンテキストについて尋ねる",
"ai.panel.empty.noContextTitle": "ノートを開いてから、{agent}にそのノートについて尋ねます",
"ai.panel.empty.withContextDescription": "要約し、つながりを見つけ、アイデアを発展させる",
"ai.panel.empty.noContextDescription": "AIはアクティブなートをコンテキストとして使用します",
"ai.panel.placeholder.checking": "AIエージェントの利用可能状況を確認しています...",
"ai.panel.placeholder.missing": "{agent}がインストールされていません。[設定] で [AI エージェント] を開きます。",
"ai.panel.placeholder.ready": "{agent}に尋ねる",
"ai.panel.send": "メッセージを送信",
"mcp.setup.manageTitle": "外部AIツールの管理",
"mcp.setup.setupTitle": "外部AIツールを設定する",
"mcp.setup.connectedDescription": "Tolariaは、このVaultで既に外部AIツールに接続されています。再接続して構成を更新するか、接続を解除してこれらのサードパーティ構成ファイルからTolariaを削除してください。",
"mcp.setup.setupDescription": "Tolariaは、このVaultの外部AIツールに自社のMCPサーバーを追加できますが、お客様がここで確認するまで、サードパーティの構成ファイルには一切触れません。",
"mcp.setup.reconnect": "外部AIツールを再接続",
"mcp.setup.connect": "外部AIツールを接続",
"mcp.setup.disconnect": "切断",
"mcp.setup.connecting": "接続中…",
"mcp.setup.disconnecting": "切断中…",
"mcp.setup.nodeRequirement": "このセットアップには、PATH に Node.js 18 以上が設定されていること、および MCP 対応のデスクトップツールが必要です。Tolariaは、構成を書き込む前にNode.jsを確認するため、ツールが機能しないコマンドを指す状態になることはありません。",
"mcp.setup.writeEntryDescription": "このアクションを確認すると、Tolariaの単一の{entry} MCPエントリが次の場所に書き込まれるか、更新されます",
"mcp.setup.clientPathsDescription": "Claude Code CLI は ~/.claude.json を読み取り、Gemini CLI は ~/.gemini/settings.json を読み取り、Cursor は ~/.cursor/mcp.json を読み取ります。また、一般的なパス ~/.config/mcp/mcp.json は、他の MCP 互換ツールによって読み取られます。閉じても、接続または切断しない限り、すべてのファイルは変更されません。再接続は冪等性があり、切断すると Tolaria のエントリは再度削除されます。",
"mcp.setup.geminiGuidanceDescription": "Gemini CLIは、独自のインストールとサインインが必要です。カスタムガイダンスを上書きせずに、Gemini を共有の AGENTS.md 指示に戻すための vault-root GEMINI.md 互換シムが必要な場合は、「Restore Tolaria AI Guidance」を使用します。",
"mcp.setup.manual.title": "手動の MCP 設定",
"mcp.setup.manual.copy": "MCP構成をコピー",
"mcp.setup.manual.loading": "正確なMCP構成を読み込み中...",
"mcp.setup.manual.unavailable": "正確な構成は、ボルトが開かれると利用可能になります。",
"mcp.toast.connected": "Tolariaの外部AIツールが正常に接続されました",
"mcp.toast.refreshed": "Tolariaの外部AIツールの設定が正常に更新されました",
"mcp.toast.disconnected": "Tolariaの外部AIツールが正常に切断されました",
"mcp.toast.alreadyDisconnected": "Tolariaの外部AIツールはすでに切断されていました",
"mcp.toast.configCopied": "Tolaria MCP設定がクリップボードにコピーされました",
"mcp.toast.configCopyFailed": "Tolaria MCP構成のクリップボードへのコピーに失敗しました{error}",
"mcp.toast.setupFailed": "外部AIツールの設定に失敗しました{error}",
"mcp.toast.disconnectFailed": "外部AIツールの切断に失敗しました{error}",
"mcp.error.clipboardUnavailable": "クリップボードAPIは利用できません",
"save.toast.saved": "保存済み",
"save.toast.nothingToSave": "保存するものがありません",
"save.toast.missingActiveVault": "保存する前に、ボルトを選択するか復元してください。",
"save.error.failed": "保存に失敗しました:{error}",
"save.error.autoFailed": "自動保存に失敗しました:{error}",
"save.error.invalidPath": "保存に失敗しました:このプラットフォームでは、ノートのパスが無効です。ノートの名前を変更するか、有効なフォルダーに移動してから、もう一度お試しください。",
"savedViews.reordered": "ビューの並べ替え完了",
"locale.en": "英語",
"sidebar.nav.inbox": "受信トレイ",
"sidebar.nav.allNotes": "すべてのノート",
@@ -149,6 +221,9 @@
"sidebar.action.createView": "ビューを作成",
"sidebar.action.editView": "ビューを編集",
"sidebar.action.deleteView": "ビューを削除",
"sidebar.action.reorderView": "ビューの並べ替え",
"sidebar.action.moveViewUp": "ビューを上に移動",
"sidebar.action.moveViewDown": "ビューを下に移動",
"sidebar.action.customizeSections": "セクションをカスタマイズ",
"sidebar.action.renameSection": "セクション名を変更…",
"sidebar.action.customizeIconColor": "アイコンと色をカスタマイズ…",
@@ -318,6 +393,8 @@
"status.vault.cloneGit": "Gitリポジトリをクローンする",
"status.vault.cloneGettingStarted": "Getting Started Vault をクローンする",
"status.vault.notFound": "ボールトが見つかりません:{path}",
"status.vault.reloading": "Vault を再読み込み中...",
"status.vault.reloadingTooltip": "ディスクから Vault を再読み込み中",
"status.vault.remove": "{label}をリストから削除",
"status.remote.noneConfigured": "リモートが設定されていません",
"status.remote.inSync": "リモートと同期済み",

View File

@@ -12,6 +12,7 @@
"command.openLanguageSettings.keywords": "언어 로케일 i18n 국제화 현지화 영어 이탈리아어 프랑스어 독일어 러시아어 스페인어 포르투갈어 중국어 일본어 한국어 中文",
"command.useSystemLanguage": "시스템 언어 사용",
"command.openH1Setting": "H1 자동 이름 변경 설정 열기",
"command.toggleGitignoredFilesVisibility": "Gitignored 파일 표시/숨기기",
"command.contribute": "기여하기",
"command.checkUpdates": "업데이트 확인",
"command.group.navigation": "탐색",
@@ -68,6 +69,10 @@
"command.view.toggleAiPanel": "AI 패널 켜기/끄기",
"command.view.newAiChat": "새 AI 채팅",
"command.view.toggleBacklinks": "백링크 켜기/끄기",
"command.view.moveViewUp": "뷰 위로 이동",
"command.view.moveViewDown": "뷰 아래로 이동",
"command.view.moveNamedViewUp": "{name} 위로 이동",
"command.view.moveNamedViewDown": "{name} 아래로 이동",
"command.view.zoomIn": "확대({zoom}%)",
"command.view.zoomOut": "축소({zoom}%)",
"command.view.resetZoom": "확대/축소 재설정",
@@ -81,6 +86,7 @@
"command.settings.repairVault": "Vault 복구",
"command.settings.useLightMode": "라이트 모드 사용",
"command.settings.useDarkMode": "다크 모드 사용",
"command.settings.toggleGitignoredFilesVisibility": "Gitignored 파일 표시/숨기기",
"command.ai.openAgents": "AI 에이전트 열기",
"command.ai.restoreGuidance": "Tolaria AI 가이드ANCE 복원",
"command.ai.switchToAgent": "AI 에이전트를 {agent}(으)로 전환",
@@ -115,6 +121,10 @@
"settings.titles.description": "Tolaria가 제목 없는 노트의 파일 이름을 첫 번째 H1 제목에서 자동으로 동기화할지 여부를 선택하세요.",
"settings.titles.autoRename": "첫 번째 H1 제목으로 제목 없는 노트 파일 이름 자동 변경",
"settings.titles.autoRenameDescription": "이 옵션을 활성화하면 Tolaria는 첫 번째 H1이 실제 제목이 되는 즉시 제목 없는 노트 파일의 이름을 변경합니다. 이 옵션을 끄면 브레드크럼 바에서 수동으로 파일 이름을 변경할 때까지 파일 이름이 변경되지 않습니다.",
"settings.vaultContent.title": "Vault 콘텐츠",
"settings.vaultContent.description": "Vault .gitignore와 일치하는 파일이 Tolaria에 표시되는지 여부를 제어합니다.",
"settings.vaultContent.hideGitignored": "Git에서 무시하는 파일 및 폴더 숨기기",
"settings.vaultContent.hideGitignoredDescription": "생성된 Vault 파일과 로컬 전용 Vault 파일을 노트, 검색, 빠른 열기 기능, 폴더에서 제외합니다.",
"settings.aiAgents.title": "AI 에이전트",
"settings.aiAgents.description": "Tolaria가 AI 패널과 명령 팔레트에서 사용하는 CLI AI 에이전트를 선택하세요.",
"settings.aiAgents.default": "기본 AI 에이전트",
@@ -138,6 +148,68 @@
"settings.cancel": "취소",
"settings.save": "저장",
"common.cancel": "취소",
"ai.permission.safe.short": "Safe",
"ai.permission.safe.control": "Vault Safe",
"ai.permission.safe.tooltip": "Vault Safe를 사용하면 에이전트가 파일, 검색, 편집 도구만 사용할 수 있도록 제한할 수 있습니다.",
"ai.permission.powerUser.short": "Power User",
"ai.permission.powerUser.control": "Power User",
"ai.permission.powerUser.tooltip": "Power User는 또한 이 Vault에 대해 로컬 셸 명령도 허용합니다.",
"ai.permission.modeAria": "AI 에이전트 권한 모드",
"ai.permission.changed": "AI 권한 모드가 {label}(으)로 변경되었습니다. 이 설정은 다음 메시지에 적용됩니다.",
"ai.panel.title": "AI 에이전트",
"ai.panel.status.checking": "사용 가능 여부 확인 중",
"ai.panel.status.missing": "{agent} · 설치되지 않음",
"ai.panel.status.ready": "{agent} · {mode}",
"ai.panel.copyMcpConfig": "MCP 구성 복사",
"ai.panel.mcpConfig": "MCP 구성",
"ai.panel.newChat": "새 AI 채팅",
"ai.panel.close": "AI 패널 닫기",
"ai.panel.linkedCount": "+ 연결된 {count}개",
"ai.panel.empty.checkingTitle": "AI 에이전트 이용 가능 여부 확인 중",
"ai.panel.empty.checkingDescription": "선택한 에이전트가 준비되면 메시지를 보낼 수 있습니다.",
"ai.panel.empty.missingTitle": "이 컴퓨터에서는 {agent}을(를) 사용할 수 없습니다.",
"ai.panel.empty.missingDescription": "설치하거나 설정에서 기본 AI 에이전트를 변경하세요.",
"ai.panel.empty.withContextTitle": "{agent}에게 이 노트와 연결된 컨텍스트에 대해 물어보세요",
"ai.panel.empty.noContextTitle": "노트를 열고 {agent}에게 해당 노트에 대해 질문하세요.",
"ai.panel.empty.withContextDescription": "요약하고, 연결 고리를 찾고, 아이디어를 확장하세요",
"ai.panel.empty.noContextDescription": "AI는 활성 노트를 배경 정보로 사용합니다.",
"ai.panel.placeholder.checking": "AI 에이전트 사용 가능 여부 확인 중...",
"ai.panel.placeholder.missing": "{agent}이(가) 설치되어 있지 않습니다. 설정에서 AI Agents를 여세요.",
"ai.panel.placeholder.ready": "{agent}에게 물어보기",
"ai.panel.send": "메시지 보내기",
"mcp.setup.manageTitle": "외부 AI 도구 관리",
"mcp.setup.setupTitle": "외부 AI 도구 설정",
"mcp.setup.connectedDescription": "Tolaria는 이미 이 Vault에 대해 외부 AI 도구에 연결되어 있습니다. 구성을 새로 고치려면 다시 연결하고, 해당 타사 구성 파일에서 Tolaria를 제거하려면 연결을 해제하세요.",
"mcp.setup.setupDescription": "Tolaria는 이 보관함에 대해 외부 AI 도구에 자체 MCP 서버를 추가할 수 있지만, 여기에서 사용자가 확인하기 전까지는 제3자 구성 파일에 영향을 미치지 않습니다.",
"mcp.setup.reconnect": "외부 AI 도구 다시 연결",
"mcp.setup.connect": "외부 AI 도구 연결",
"mcp.setup.disconnect": "연결 해제",
"mcp.setup.connecting": "연결 중…",
"mcp.setup.disconnecting": "연결 해제 중…",
"mcp.setup.nodeRequirement": "이 설정을 사용하려면 PATH에 Node.js 18 이상이 있어야 하며 MCP와 호환되는 데스크톱 도구가 필요합니다. Tolaria는 구성 파일을 작성하기 전에 Node.js를 확인하여 도구가 잘못된 명령어를 가리키지 않도록 합니다.",
"mcp.setup.writeEntryDescription": "이 작업을 확인하면 Tolaria의 단일 {entry} MCP 항목이 다음 위치에 작성되거나 업데이트됩니다:",
"mcp.setup.clientPathsDescription": "Claude Code CLI는 ~/.claude.json 파일을 읽고, Gemini CLI는 ~/.gemini/settings.json 파일을 읽고, Cursor는 ~/.cursor/mcp.json 파일을 읽으며, 일반 경로인 ~/.config/mcp/mcp.json은 다른 MCP 호환 도구에서 읽습니다. 종료 시 연결하거나 연결 해제하지 않는 한 모든 파일이 그대로 유지됩니다. 다시 연결하는 것은 멱등적이며, 연결 해제 시 Tolaria의 항목이 다시 제거됩니다.",
"mcp.setup.geminiGuidanceDescription": "Gemini CLI는 별도의 설치 및 로그인이 필요합니다. 사용자 지정 가이드를 덮어쓰지 않고 Gemini를 공유 AGENTS.md 지침으로 다시 연결하는 볼트 루트 GEMINI.md 호환성 시름을 원할 경우, 'Restore Tolaria AI Guidance'를 사용하세요.",
"mcp.setup.manual.title": "수동 MCP 구성",
"mcp.setup.manual.copy": "MCP 구성 복사",
"mcp.setup.manual.loading": "정확한 MCP 구성 로드 중...",
"mcp.setup.manual.unavailable": "정확한 구성은 보관소가 열린 후에 사용할 수 있습니다.",
"mcp.toast.connected": "Tolaria 외부 AI 도구가 성공적으로 연결되었습니다.",
"mcp.toast.refreshed": "Tolaria 외부 AI 도구 설정이 성공적으로 새로 고침되었습니다",
"mcp.toast.disconnected": "Tolaria 외부 AI 도구 연결이 성공적으로 해제되었습니다",
"mcp.toast.alreadyDisconnected": "Tolaria 외부 AI 도구 연결이 이미 해제되었습니다",
"mcp.toast.configCopied": "Tolaria MCP 구성이 클립보드에 복사되었습니다",
"mcp.toast.configCopyFailed": "MCP 구성 복사 실패: {error}",
"mcp.toast.setupFailed": "외부 AI 도구 설정에 실패했습니다: {error}",
"mcp.toast.disconnectFailed": "외부 AI 도구 연결 해제 실패: {error}",
"mcp.error.clipboardUnavailable": "클립보드 API를 사용할 수 없습니다",
"save.toast.saved": "저장됨",
"save.toast.nothingToSave": "저장할 항목이 없습니다",
"save.toast.missingActiveVault": "저장하기 전에 보관소를 선택하거나 복원하세요.",
"save.error.failed": "저장 실패: {error}",
"save.error.autoFailed": "자동 저장 실패: {error}",
"save.error.invalidPath": "저장 실패: 이 플랫폼에서는 노트 경로가 유효하지 않습니다. 노트 이름을 바꾸거나 유효한 폴더로 이동한 후 다시 시도하세요.",
"savedViews.reordered": "보기 순서 재정렬됨",
"locale.en": "English",
"sidebar.nav.inbox": "받은 편지함",
"sidebar.nav.allNotes": "모든 노트",
@@ -149,6 +221,9 @@
"sidebar.action.createView": "보기 생성",
"sidebar.action.editView": "보기 편집",
"sidebar.action.deleteView": "보기 삭제",
"sidebar.action.reorderView": "보기 순서 재정렬",
"sidebar.action.moveViewUp": "보기 위로 이동",
"sidebar.action.moveViewDown": "보기 아래로 이동",
"sidebar.action.customizeSections": "섹션 사용자 지정",
"sidebar.action.renameSection": "섹션 이름 변경…",
"sidebar.action.customizeIconColor": "아이콘 및 색상 사용자 지정…",
@@ -318,6 +393,8 @@
"status.vault.cloneGit": "Git 리포지토리 복제",
"status.vault.cloneGettingStarted": "Getting Started Vault 복제",
"status.vault.notFound": "Vault를 찾을 수 없음: {path}",
"status.vault.reloading": "보관함 다시 로드 중...",
"status.vault.reloadingTooltip": "디스크에서 Vault 다시 로드 중",
"status.vault.remove": "목록에서 {label} 제거",
"status.remote.noneConfigured": "구성된 원격 없음",
"status.remote.inSync": "원격과 동기화됨",

View File

@@ -12,6 +12,7 @@
"command.openLanguageSettings.keywords": "idioma localidade i18n internacionalização localização inglês italiano francês alemão russo espanhol português chinês japonês coreano 中文",
"command.useSystemLanguage": "Usar o idioma do sistema",
"command.openH1Setting": "Abrir configuração de renomeação automática de H1",
"command.toggleGitignoredFilesVisibility": "Alternar visibilidade dos arquivos Gitignored",
"command.contribute": "Contribuir",
"command.checkUpdates": "Verificar atualizações",
"command.group.navigation": "Navegação",
@@ -68,6 +69,10 @@
"command.view.toggleAiPanel": "Alternar painel de IA",
"command.view.newAiChat": "Novo chat de IA",
"command.view.toggleBacklinks": "Alternar backlinks",
"command.view.moveViewUp": "Mover visualização para cima",
"command.view.moveViewDown": "Mover visualização para baixo",
"command.view.moveNamedViewUp": "Mover {name} para cima",
"command.view.moveNamedViewDown": "Mover {name} para baixo",
"command.view.zoomIn": "Aumentar zoom ({zoom}%)",
"command.view.zoomOut": "Diminuir zoom ({zoom}%)",
"command.view.resetZoom": "Redefinir zoom",
@@ -81,6 +86,7 @@
"command.settings.repairVault": "Reparar cofre",
"command.settings.useLightMode": "Usar modo claro",
"command.settings.useDarkMode": "Usar modo escuro",
"command.settings.toggleGitignoredFilesVisibility": "Alternar visibilidade dos arquivos ignorados pelo Git",
"command.ai.openAgents": "Abrir agentes de IA",
"command.ai.restoreGuidance": "Restaurar orientação de IA da Tolaria",
"command.ai.switchToAgent": "Trocar o agente de IA para {agent}",
@@ -115,6 +121,10 @@
"settings.titles.description": "Escolha se o Tolaria deve sincronizar automaticamente os nomes de arquivo das notas sem título com base no primeiro título H1.",
"settings.titles.autoRename": "Renomear automaticamente notas sem título com base no primeiro H1",
"settings.titles.autoRenameDescription": "Quando ativada, a Tolaria renomeia os arquivos de anotações sem título assim que o primeiro H1 se torna um título de fato. Desative esta opção para manter o nome do arquivo inalterado até que você o renomeie manualmente na barra de navegação.",
"settings.vaultContent.title": "Conteúdo do cofre",
"settings.vaultContent.description": "Controle se os arquivos correspondentes ao .gitignore do cofre aparecem no Tolaria.",
"settings.vaultContent.hideGitignored": "Ocultar arquivos e pastas ignorados pelo Git",
"settings.vaultContent.hideGitignoredDescription": "Mantém os arquivos do cofre gerados e apenas locais fora das anotações, da pesquisa, da abertura rápida e das pastas.",
"settings.aiAgents.title": "Agentes de IA",
"settings.aiAgents.description": "Escolha qual agente de IA de CLI o Tolaria usa no painel de IA e na paleta de comandos.",
"settings.aiAgents.default": "Agente de IA padrão",
@@ -138,6 +148,68 @@
"settings.cancel": "Cancelar",
"settings.save": "Salvar",
"common.cancel": "Cancelar",
"ai.permission.safe.short": "Safe",
"ai.permission.safe.control": "Vault Safe",
"ai.permission.safe.tooltip": "O Vault Safe limita o acesso dos agentes às ferramentas de arquivo, pesquisa e edição.",
"ai.permission.powerUser.short": "Usuário avançado",
"ai.permission.powerUser.control": "Usuário avançado",
"ai.permission.powerUser.tooltip": "O Power User também permite comandos de shell locais para este cofre.",
"ai.permission.modeAria": "Modo de permissão de IA do agente",
"ai.permission.changed": "Modo de permissão de IA alterado para {label}. Será aplicado à próxima mensagem.",
"ai.panel.title": "Agente de IA",
"ai.panel.status.checking": "Verificando disponibilidade",
"ai.panel.status.missing": "{agent} · não instalado",
"ai.panel.status.ready": "{agent} · {mode}",
"ai.panel.copyMcpConfig": "Copiar configuração do MCP",
"ai.panel.mcpConfig": "Configuração do MCP",
"ai.panel.newChat": "Novo chat de IA",
"ai.panel.close": "Fechar painel de IA",
"ai.panel.linkedCount": "+ {count} vinculados",
"ai.panel.empty.checkingTitle": "Verificando a disponibilidade do agente de IA",
"ai.panel.empty.checkingDescription": "As mensagens podem ser enviadas quando o agente selecionado estiver disponível",
"ai.panel.empty.missingTitle": "{agent} não está disponível nesta máquina",
"ai.panel.empty.missingDescription": "Instale-o ou altere o agente de IA padrão em Configurações",
"ai.panel.empty.withContextTitle": "Pergunte ao {agent} sobre esta nota e o contexto vinculado a ela",
"ai.panel.empty.noContextTitle": "Abra uma nota e pergunte ao {agent} sobre ela",
"ai.panel.empty.withContextDescription": "Resuma, encontre conexões, desenvolva ideias",
"ai.panel.empty.noContextDescription": "A IA usará a nota ativa como contexto",
"ai.panel.placeholder.checking": "Verificando a disponibilidade do agente de IA...",
"ai.panel.placeholder.missing": "{agent} não está instalado. Abra Agentes de IA em Configurações.",
"ai.panel.placeholder.ready": "Perguntar ao {agent}",
"ai.panel.send": "Enviar mensagem",
"mcp.setup.manageTitle": "Gerenciar ferramentas externas de IA",
"mcp.setup.setupTitle": "Configurar ferramentas externas de IA",
"mcp.setup.connectedDescription": "O Tolaria já está conectado a ferramentas externas de IA para este vault. Reconecte-se para atualizar a configuração ou desconecte-se para remover o Tolaria desses arquivos de configuração de terceiros.",
"mcp.setup.setupDescription": "O Tolaria pode adicionar seu servidor MCP a ferramentas externas de IA para este vault, mas não fará alterações em arquivos de configuração de terceiros até que você confirme aqui.",
"mcp.setup.reconnect": "Reconectar ferramentas externas de IA",
"mcp.setup.connect": "Conectar ferramentas externas de IA",
"mcp.setup.disconnect": "Desconectar",
"mcp.setup.connecting": "Conectando…",
"mcp.setup.disconnecting": "Desconectando…",
"mcp.setup.nodeRequirement": "Esta configuração requer o Node.js 18 ou superior no PATH e uma ferramenta de desktop compatível com MCP. O Tolaria verifica o Node.js antes de gravar a configuração para que a ferramenta não fique apontando para um comando inválido.",
"mcp.setup.writeEntryDescription": "Ao confirmar esta ação, a única entrada MCP {entry} do Tolaria será gravada ou atualizada em:",
"mcp.setup.clientPathsDescription": "A CLI do Claude Code lê ~/.claude.json, a CLI do Gemini lê ~/.gemini/settings.json, o Cursor lê ~/.cursor/mcp.json e o caminho genérico ~/.config/mcp/mcp.json é usado por outras ferramentas compatíveis com MCP. Ao fechar o aplicativo, todos os arquivos permanecem inalterados, a menos que você se conecte ou se desconecte. A reconexão é idempotente, e a desconexão remove novamente a entrada do Tolaria.",
"mcp.setup.geminiGuidanceDescription": "A CLI do Gemini precisa de instalação e login próprios. Use Restore Tolaria AI Guidance quando quiser um shim de compatibilidade com GEMINI.md na raiz do cofre que direcione o Gemini de volta para as instruções compartilhadas do AGENTS.md sem substituir a orientação personalizada.",
"mcp.setup.manual.title": "Configuração manual do MCP",
"mcp.setup.manual.copy": "Copiar configuração do MCP",
"mcp.setup.manual.loading": "Carregando configuração exata do MCP...",
"mcp.setup.manual.unavailable": "A configuração exata fica disponível após a abertura de um vault.",
"mcp.toast.connected": "Ferramentas externas de IA da Tolaria conectadas com sucesso",
"mcp.toast.refreshed": "Configuração das ferramentas externas de IA da Tolaria atualizada com sucesso",
"mcp.toast.disconnected": "Ferramentas externas de IA da Tolaria desconectadas com sucesso",
"mcp.toast.alreadyDisconnected": "As ferramentas externas de IA da Tolaria já estavam desconectadas",
"mcp.toast.configCopied": "Configuração do MCP da Tolaria copiada para a área de transferência",
"mcp.toast.configCopyFailed": "Falha ao copiar a configuração do MCP: {error}",
"mcp.toast.setupFailed": "Falha na configuração da ferramenta de IA externa: {error}",
"mcp.toast.disconnectFailed": "Falha ao desconectar a ferramenta de IA externa: {error}",
"mcp.error.clipboardUnavailable": "A API da área de transferência não está disponível",
"save.toast.saved": "Salvo",
"save.toast.nothingToSave": "Nada para salvar",
"save.toast.missingActiveVault": "Selecione ou restaure um cofre antes de salvar.",
"save.error.failed": "Falha ao salvar: {error}",
"save.error.autoFailed": "Falha no salvamento automático: {error}",
"save.error.invalidPath": "Falha ao salvar: o caminho da nota é inválido nesta plataforma. Renomeie a nota ou mova-a para uma pasta válida e tente novamente.",
"savedViews.reordered": "Visualizações reordenadas",
"locale.en": "Inglês",
"sidebar.nav.inbox": "Caixa de entrada",
"sidebar.nav.allNotes": "Todas as anotações",
@@ -149,6 +221,9 @@
"sidebar.action.createView": "Criar visualização",
"sidebar.action.editView": "Editar visualização",
"sidebar.action.deleteView": "Excluir visualização",
"sidebar.action.reorderView": "Reordenar visualização",
"sidebar.action.moveViewUp": "Mover visualização para cima",
"sidebar.action.moveViewDown": "Mover visualização para baixo",
"sidebar.action.customizeSections": "Personalizar seções",
"sidebar.action.renameSection": "Renomear seção…",
"sidebar.action.customizeIconColor": "Personalizar ícone e cor…",
@@ -318,6 +393,8 @@
"status.vault.cloneGit": "Clonar repositório Git",
"status.vault.cloneGettingStarted": "Clonar cofre de introdução",
"status.vault.notFound": "Cofre não encontrado: {path}",
"status.vault.reloading": "Recarregando o cofre...",
"status.vault.reloadingTooltip": "Recarregando o vault a partir do disco",
"status.vault.remove": "Remover {label} da lista",
"status.remote.noneConfigured": "Nenhum repositório remoto configurado",
"status.remote.inSync": "Sincronizado com o repositório remoto",

View File

@@ -12,6 +12,7 @@
"command.openLanguageSettings.keywords": "idioma localização i18n internacionalização localização inglês italiano francês alemão russo espanhol português chinês japonês coreano 中文",
"command.useSystemLanguage": "Utilizar o idioma do sistema",
"command.openH1Setting": "Abrir a definição de renomeação automática de H1",
"command.toggleGitignoredFilesVisibility": "Alternar a visibilidade dos ficheiros Gitignored",
"command.contribute": "Contribuir",
"command.checkUpdates": "Verificar atualizações",
"command.group.navigation": "Navegação",
@@ -68,6 +69,10 @@
"command.view.toggleAiPanel": "Ativar/desativar painel de IA",
"command.view.newAiChat": "Novo chat de IA",
"command.view.toggleBacklinks": "Ativar/desativar backlinks",
"command.view.moveViewUp": "Mover vista para cima",
"command.view.moveViewDown": "Mover vista para baixo",
"command.view.moveNamedViewUp": "Mover {name} para cima",
"command.view.moveNamedViewDown": "Mover {name} para baixo",
"command.view.zoomIn": "Aumentar ({zoom}%)",
"command.view.zoomOut": "Reduzir ({zoom}%)",
"command.view.resetZoom": "Repor o zoom",
@@ -81,6 +86,7 @@
"command.settings.repairVault": "Reparar cofre",
"command.settings.useLightMode": "Usar modo claro",
"command.settings.useDarkMode": "Usar modo escuro",
"command.settings.toggleGitignoredFilesVisibility": "Alternar a visibilidade dos ficheiros Gitignored",
"command.ai.openAgents": "Abrir agentes de IA",
"command.ai.restoreGuidance": "Repor a orientação de IA da Tolaria",
"command.ai.switchToAgent": "Mudar o agente de IA para {agent}",
@@ -115,6 +121,10 @@
"settings.titles.description": "Escolha se a Tolaria deve sincronizar automaticamente os nomes de ficheiro das notas sem título com base no primeiro título H1.",
"settings.titles.autoRename": "Renomear automaticamente notas sem título com base no primeiro H1",
"settings.titles.autoRenameDescription": "Quando esta opção está ativada, a Tolaria muda o nome dos ficheiros de notas sem título assim que o primeiro H1 se torna um título verdadeiro. Desative esta opção para manter o nome do ficheiro inalterado até o renomear manualmente na barra de navegação.",
"settings.vaultContent.title": "Conteúdo do cofre",
"settings.vaultContent.description": "Controlar se os ficheiros correspondentes ao .gitignore do cofre aparecem na Tolaria.",
"settings.vaultContent.hideGitignored": "Ocultar ficheiros e pastas ignorados pelo Git",
"settings.vaultContent.hideGitignoredDescription": "Mantém os ficheiros do cofre gerados e apenas locais fora das notas, da pesquisa, da abertura rápida e das pastas.",
"settings.aiAgents.title": "Agentes de IA",
"settings.aiAgents.description": "Escolha que agente de IA de CLI é utilizado pela Tolaria no painel de IA e na paleta de comandos.",
"settings.aiAgents.default": "Agente de IA predefinido",
@@ -138,6 +148,68 @@
"settings.cancel": "Cancelar",
"settings.save": "Guardar",
"common.cancel": "Cancelar",
"ai.permission.safe.short": "Safe",
"ai.permission.safe.control": "Vault Safe",
"ai.permission.safe.tooltip": "O Vault Safe limita os agentes às ferramentas de ficheiros, de pesquisa e de edição.",
"ai.permission.powerUser.short": "Utilizador avançado",
"ai.permission.powerUser.control": "Utilizador avançado",
"ai.permission.powerUser.tooltip": "O Power User também permite comandos de shell locais para este cofre.",
"ai.permission.modeAria": "Modo de permissão de agente de IA",
"ai.permission.changed": "Modo de permissão de IA alterado para {label}. Será aplicado à próxima mensagem.",
"ai.panel.title": "Agente de IA",
"ai.panel.status.checking": "A verificar a disponibilidade",
"ai.panel.status.missing": "{agent} · não instalado",
"ai.panel.status.ready": "{agent} · {mode}",
"ai.panel.copyMcpConfig": "Copiar configuração de MCP",
"ai.panel.mcpConfig": "Configuração do MCP",
"ai.panel.newChat": "Nova chat de IA",
"ai.panel.close": "Fechar painel de IA",
"ai.panel.linkedCount": "+ {count} associados",
"ai.panel.empty.checkingTitle": "A verificar a disponibilidade do agente de IA",
"ai.panel.empty.checkingDescription": "As mensagens podem ser enviadas quando o agente selecionado estiver disponível",
"ai.panel.empty.missingTitle": "{agent} não está disponível neste computador",
"ai.panel.empty.missingDescription": "Instale-o ou altere o agente de IA predefinido nas Definições",
"ai.panel.empty.withContextTitle": "Perguntar ao {agent} sobre esta nota e o seu contexto associado",
"ai.panel.empty.noContextTitle": "Abra uma nota e, em seguida, pergunte ao {agent} sobre ela",
"ai.panel.empty.withContextDescription": "Resumir, encontrar ligações, desenvolver ideias",
"ai.panel.empty.noContextDescription": "A IA utilizará a nota ativa como contexto",
"ai.panel.placeholder.checking": "A verificar a disponibilidade do agente de IA...",
"ai.panel.placeholder.missing": "O {agent} não está instalado. Abra Agentes de IA em Definições.",
"ai.panel.placeholder.ready": "Perguntar ao {agent}",
"ai.panel.send": "Enviar mensagem",
"mcp.setup.manageTitle": "Gerir ferramentas de IA externas",
"mcp.setup.setupTitle": "Configurar ferramentas de IA externas",
"mcp.setup.connectedDescription": "A Tolaria já está ligada a ferramentas de IA externas para este cofre. Volte a ligar para atualizar a configuração ou desligue para remover a Tolaria desses ficheiros de configuração de terceiros.",
"mcp.setup.setupDescription": "A Tolaria pode adicionar o seu servidor MCP a ferramentas de IA externas para este cofre, mas não irá alterar ficheiros de configuração de terceiros sem a sua confirmação aqui.",
"mcp.setup.reconnect": "Voltar a ligar ferramentas de IA externas",
"mcp.setup.connect": "Ligar ferramentas de IA externas",
"mcp.setup.disconnect": "Desligar",
"mcp.setup.connecting": "A estabelecer ligação…",
"mcp.setup.disconnecting": "A desligar…",
"mcp.setup.nodeRequirement": "Esta configuração requer o Node.js 18 ou superior no PATH e uma ferramenta de desktop compatível com MCP. A Tolaria verifica o Node.js antes de escrever a configuração, para que a ferramenta não fique a apontar para um comando inválido.",
"mcp.setup.writeEntryDescription": "Ao confirmar esta ação, a única entrada MCP {entry} da Tolaria será escrita ou atualizada em:",
"mcp.setup.clientPathsDescription": "A CLI do Claude Code lê ~/.claude.json, a CLI do Gemini lê ~/.gemini/settings.json, o Cursor lê ~/.cursor/mcp.json e o caminho genérico ~/.config/mcp/mcp.json é detetado por outras ferramentas compatíveis com MCP. Ao fechar, todos os ficheiros permanecem inalterados, a menos que estabeleça ou interrompa a ligação; a restabelecimento da ligação é idempotente e a interrupção da ligação remove novamente a entrada da Tolaria.",
"mcp.setup.geminiGuidanceDescription": "A CLI do Gemini precisa da sua própria instalação e início de sessão. Utilize Restore Tolaria AI Guidance quando quiser uma camada de compatibilidade GEMINI.md na raiz do cofre que encaminhe o Gemini de volta para as instruções partilhadas do AGENTS.md sem substituir as orientações personalizadas.",
"mcp.setup.manual.title": "Configuração manual do MCP",
"mcp.setup.manual.copy": "Copiar configuração de MCP",
"mcp.setup.manual.loading": "A carregar a configuração exata do MCP...",
"mcp.setup.manual.unavailable": "A configuração exata fica disponível após a abertura de um cofre.",
"mcp.toast.connected": "Ferramentas de IA externas da Tolaria ligadas com sucesso",
"mcp.toast.refreshed": "Configuração das ferramentas de IA externas da Tolaria atualizada com sucesso",
"mcp.toast.disconnected": "Ferramentas externas de IA da Tolaria desligadas com sucesso",
"mcp.toast.alreadyDisconnected": "As ferramentas de IA externas da Tolaria já estavam desligadas",
"mcp.toast.configCopied": "Configuração de MCP da Tolaria copiada para a área de transferência",
"mcp.toast.configCopyFailed": "Falha ao copiar a configuração do MCP: {error}",
"mcp.toast.setupFailed": "Falha na configuração da ferramenta de IA externa: {error}",
"mcp.toast.disconnectFailed": "Falha ao desligar a ferramenta de IA externa: {error}",
"mcp.error.clipboardUnavailable": "A API da área de transferência não está disponível",
"save.toast.saved": "Guardado",
"save.toast.nothingToSave": "Nada para guardar",
"save.toast.missingActiveVault": "Selecione ou restaure um cofre antes de guardar.",
"save.error.failed": "Falha ao guardar: {error}",
"save.error.autoFailed": "Falha na gravação automática: {error}",
"save.error.invalidPath": "Falha ao guardar: o caminho da nota é inválido nesta plataforma. Mude o nome da nota ou mova-a para uma pasta válida e tente novamente.",
"savedViews.reordered": "Vistas reordenadas",
"locale.en": "Inglês",
"sidebar.nav.inbox": "Caixa de entrada",
"sidebar.nav.allNotes": "Todas as notas",
@@ -149,6 +221,9 @@
"sidebar.action.createView": "Criar vista",
"sidebar.action.editView": "Editar vista",
"sidebar.action.deleteView": "Eliminar vista",
"sidebar.action.reorderView": "Reordenar vista",
"sidebar.action.moveViewUp": "Mover vista para cima",
"sidebar.action.moveViewDown": "Mover vista para baixo",
"sidebar.action.customizeSections": "Personalizar secções",
"sidebar.action.renameSection": "Mudar o nome da secção…",
"sidebar.action.customizeIconColor": "Personalizar ícone e cor…",
@@ -318,6 +393,8 @@
"status.vault.cloneGit": "Clonar repositório Git",
"status.vault.cloneGettingStarted": "Clonar cofre Getting Started",
"status.vault.notFound": "Cofre não encontrado: {path}",
"status.vault.reloading": "A recarregar o cofre...",
"status.vault.reloadingTooltip": "A recarregar o cofre a partir do disco",
"status.vault.remove": "Remover {label} da lista",
"status.remote.noneConfigured": "Nenhum repositório remoto configurado",
"status.remote.inSync": "Sincronizado com o repositório remoto",

View File

@@ -12,6 +12,7 @@
"command.openLanguageSettings.keywords": "язык регион i18n интернационализация локализация английский итальянский французский немецкий русский испанский португальский китайский японский корейский 中文",
"command.useSystemLanguage": "Использовать язык системы",
"command.openH1Setting": "Открыть настройку автоматического переименования H1",
"command.toggleGitignoredFilesVisibility": "Переключить отображение файлов Gitignored",
"command.contribute": "Внести свой вклад",
"command.checkUpdates": "Проверить наличие обновлений",
"command.group.navigation": "Навигация",
@@ -68,6 +69,10 @@
"command.view.toggleAiPanel": "Переключить панель ИИ",
"command.view.newAiChat": "Новый AI-чат",
"command.view.toggleBacklinks": "Переключить обратные ссылки",
"command.view.moveViewUp": "Переместить представление вверх",
"command.view.moveViewDown": "Переместить представление вниз",
"command.view.moveNamedViewUp": "Переместить {name} вверх",
"command.view.moveNamedViewDown": "Переместить {name} вниз",
"command.view.zoomIn": "Увеличить ({zoom}%)",
"command.view.zoomOut": "Уменьшить ({zoom}%)",
"command.view.resetZoom": "Сбросить масштаб",
@@ -81,6 +86,7 @@
"command.settings.repairVault": "Восстановить хранилище",
"command.settings.useLightMode": "Использовать светлый режим",
"command.settings.useDarkMode": "Использовать темный режим",
"command.settings.toggleGitignoredFilesVisibility": "Переключить отображение файлов, игнорируемых Git",
"command.ai.openAgents": "Открыть AI Agents",
"command.ai.restoreGuidance": "Восстановить руководство Tolaria AI",
"command.ai.switchToAgent": "Переключить AI-агента на {agent}",
@@ -115,6 +121,10 @@
"settings.titles.description": "Выберите, должна ли Tolaria автоматически синхронизировать имена файлов заметок без названия на основе первого заголовка H1.",
"settings.titles.autoRename": "Автоматически переименовывать заметки без названия по первому заголовку H1",
"settings.titles.autoRenameDescription": "Если эта функция включена, Tolaria переименовывает файлы заметок без названия, как только первый заголовок H1 становится настоящим заголовком. Отключите эту опцию, чтобы имя файла оставалось неизменным, пока вы не переименуете его вручную в строке навигации.",
"settings.vaultContent.title": "Содержимое хранилища",
"settings.vaultContent.description": "Управляйте тем, будут ли отображаться в Tolaria файлы, соответствующие списку .gitignore хранилища.",
"settings.vaultContent.hideGitignored": "Скрыть файлы и папки, игнорируемые Git",
"settings.vaultContent.hideGitignoredDescription": "Не допускает включения сгенерированных и только локальных файлов хранилища в заметки, поиск, быстрое открытие и папки.",
"settings.aiAgents.title": "AI-агенты",
"settings.aiAgents.description": "Выберите, какой CLI-агент ИИ Tolaria будет использовать на панели ИИ и в палитре команд.",
"settings.aiAgents.default": "AI-агент по умолчанию",
@@ -138,6 +148,68 @@
"settings.cancel": "Отменить",
"settings.save": "Сохранить",
"common.cancel": "Отменить",
"ai.permission.safe.short": "Safe",
"ai.permission.safe.control": "Vault Safe",
"ai.permission.safe.tooltip": "Vault Safe ограничивает доступ агентов только инструментами для работы с файлами, поиска и редактирования.",
"ai.permission.powerUser.short": "Power User",
"ai.permission.powerUser.control": "Power User",
"ai.permission.powerUser.tooltip": "Режим Power User также разрешает использование локальных команд оболочки для этого хранилища.",
"ai.permission.modeAria": "Режим разрешений для агента ИИ",
"ai.permission.changed": "Режим разрешений для ИИ изменен на {label}. Он будет применен к следующему сообщению.",
"ai.panel.title": "AI Agent",
"ai.panel.status.checking": "Проверка доступности",
"ai.panel.status.missing": "{agent} · не установлен",
"ai.panel.status.ready": "{agent} · {mode}",
"ai.panel.copyMcpConfig": "Копировать конфигурацию MCP",
"ai.panel.mcpConfig": "Конфигурация MCP",
"ai.panel.newChat": "Новый AI-чат",
"ai.panel.close": "Закрыть панель ИИ",
"ai.panel.linkedCount": "+ {count} связано",
"ai.panel.empty.checkingTitle": "Проверка доступности AI-агента",
"ai.panel.empty.checkingDescription": "Сообщения можно будет отправлять, когда выбранный агент будет готов",
"ai.panel.empty.missingTitle": "{agent} недоступен на этом компьютере",
"ai.panel.empty.missingDescription": "Установите его или измените агент ИИ по умолчанию в настройках.",
"ai.panel.empty.withContextTitle": "Спросите {agent} об этой заметке и связанном с ней контексте",
"ai.panel.empty.noContextTitle": "Откройте заметку, а затем спросите {agent} о ней",
"ai.panel.empty.withContextDescription": "Делайте выводы, находите связи, развивайте идеи",
"ai.panel.empty.noContextDescription": "ИИ будет использовать активную заметку в качестве контекста",
"ai.panel.placeholder.checking": "Проверка доступности AI-агента...",
"ai.panel.placeholder.missing": "{agent} не установлен. Откройте «AI Agents» в настройках.",
"ai.panel.placeholder.ready": "Спросить {agent}",
"ai.panel.send": "Отправить сообщение",
"mcp.setup.manageTitle": "Управление внешними инструментами ИИ",
"mcp.setup.setupTitle": "Настроить внешние инструменты ИИ",
"mcp.setup.connectedDescription": "Tolaria уже подключена к внешним инструментам ИИ для этого хранилища. Подключитесь повторно, чтобы обновить конфигурацию, или отключитесь, чтобы удалить Tolaria из этих сторонних файлов конфигурации.",
"mcp.setup.setupDescription": "Tolaria может добавить свой MCP-сервер к внешним инструментам ИИ для этого хранилища, но не будет изменять сторонние файлы конфигурации, пока вы не подтвердите это здесь.",
"mcp.setup.reconnect": "Повторно подключить внешние инструменты ИИ",
"mcp.setup.connect": "Подключить внешние инструменты ИИ",
"mcp.setup.disconnect": "Отключить",
"mcp.setup.connecting": "Подключение…",
"mcp.setup.disconnecting": "Отключение…",
"mcp.setup.nodeRequirement": "Для этой настройки требуется Node.js 18+ в PATH и совместимый с MCP настольный инструмент. Tolaria проверяет Node.js перед записью конфигурации, чтобы инструмент не указывал на неработающую команду.",
"mcp.setup.writeEntryDescription": "При подтверждении этого действия будет записана или обновлена единственная запись Tolaria {entry} в MCP в:",
"mcp.setup.clientPathsDescription": "CLI Claude Code считывает файл ~/.claude.json, CLI Gemini считывает файл ~/.gemini/settings.json, Cursor считывает файл ~/.cursor/mcp.json, а общий путь ~/.config/mcp/mcp.json используется другими MCP-совместимыми инструментами. При закрытии приложения все файлы остаются без изменений, если вы не подключаетесь или не отключаетесь. Повторное подключение является идемпотентным, а отключение снова удаляет запись Tolaria.",
"mcp.setup.geminiGuidanceDescription": "Для Gemini CLI требуется отдельная установка и вход в систему. Используйте команду Restore Tolaria AI Guidance, если вам нужна оболочка совместимости GEMINI.md в корне хранилища, которая перенаправляет Gemini обратно к общим инструкциям AGENTS.md, не перезаписывая пользовательские инструкции.",
"mcp.setup.manual.title": "Ручная настройка MCP",
"mcp.setup.manual.copy": "Копировать конфигурацию MCP",
"mcp.setup.manual.loading": "Загрузка точной конфигурации MCP...",
"mcp.setup.manual.unavailable": "Точная конфигурация становится доступна после открытия хранилища.",
"mcp.toast.connected": "Внешние инструменты ИИ Tolaria успешно подключены",
"mcp.toast.refreshed": "Настройки внешних инструментов ИИ Tolaria успешно обновлены",
"mcp.toast.disconnected": "Внешние инструменты ИИ Tolaria успешно отключены",
"mcp.toast.alreadyDisconnected": "Внешние инструменты ИИ Tolaria уже отключены",
"mcp.toast.configCopied": "Конфигурация Tolaria MCP скопирована в буфер обмена",
"mcp.toast.configCopyFailed": "Не удалось скопировать конфигурацию MCP: {error}",
"mcp.toast.setupFailed": "Не удалось настроить внешний инструмент ИИ: {error}",
"mcp.toast.disconnectFailed": "Не удалось отключить внешний инструмент ИИ: {error}",
"mcp.error.clipboardUnavailable": "API буфера обмена недоступен",
"save.toast.saved": "Сохранено",
"save.toast.nothingToSave": "Нет данных для сохранения",
"save.toast.missingActiveVault": "Перед сохранением выберите или восстановите хранилище.",
"save.error.failed": "Ошибка сохранения: {error}",
"save.error.autoFailed": "Ошибка автоматического сохранения: {error}",
"save.error.invalidPath": "Ошибка сохранения: путь к заметке недействителен на этой платформе. Переименуйте заметку или переместите ее в допустимую папку, а затем повторите попытку.",
"savedViews.reordered": "Представления переупорядочены",
"locale.en": "Английский",
"sidebar.nav.inbox": "Входящие",
"sidebar.nav.allNotes": "Все заметки",
@@ -149,6 +221,9 @@
"sidebar.action.createView": "Создать представление",
"sidebar.action.editView": "Редактировать представление",
"sidebar.action.deleteView": "Удалить представление",
"sidebar.action.reorderView": "Изменить порядок представлений",
"sidebar.action.moveViewUp": "Переместить представление вверх",
"sidebar.action.moveViewDown": "Переместить представление вниз",
"sidebar.action.customizeSections": "Настроить разделы",
"sidebar.action.renameSection": "Переименовать раздел…",
"sidebar.action.customizeIconColor": "Настроить значок и цвет…",
@@ -318,6 +393,8 @@
"status.vault.cloneGit": "Клонировать Git-репозиторий",
"status.vault.cloneGettingStarted": "Клонировать хранилище «Начало работы»",
"status.vault.notFound": "Хранилище не найдено: {path}",
"status.vault.reloading": "Перезагрузка хранилища...",
"status.vault.reloadingTooltip": "Перезагрузка хранилища с диска",
"status.vault.remove": "Удалить {label} из списка",
"status.remote.noneConfigured": "Ни один удаленный репозиторий не настроен",
"status.remote.inSync": "Синхронизировано с удаленным репозиторием",

View File

@@ -12,6 +12,7 @@
"command.openLanguageSettings.keywords": "语言 区域 i18n 国际化 本地化 中文 english italian french german russian spanish portuguese japanese korean",
"command.useSystemLanguage": "使用系统语言",
"command.openH1Setting": "打开 H1 自动重命名设置",
"command.toggleGitignoredFilesVisibility": "切换 Gitignored 文件的可见性",
"command.contribute": "参与贡献",
"command.checkUpdates": "检查更新",
"command.group.navigation": "导航",
@@ -68,6 +69,10 @@
"command.view.toggleAiPanel": "切换 AI 面板",
"command.view.newAiChat": "新建 AI 对话",
"command.view.toggleBacklinks": "切换反向链接",
"command.view.moveViewUp": "向上移动视图",
"command.view.moveViewDown": "向下移动视图",
"command.view.moveNamedViewUp": "向上移动 {name}",
"command.view.moveNamedViewDown": "向下移动 {name}",
"command.view.zoomIn": "放大({zoom}%",
"command.view.zoomOut": "缩小({zoom}%",
"command.view.resetZoom": "重置缩放",
@@ -81,6 +86,7 @@
"command.settings.repairVault": "修复仓库",
"command.settings.useLightMode": "使用浅色模式",
"command.settings.useDarkMode": "使用深色模式",
"command.settings.toggleGitignoredFilesVisibility": "切换 Gitignored 文件的可见性",
"command.ai.openAgents": "打开 AI 代理设置",
"command.ai.restoreGuidance": "恢复 Tolaria AI 指导文件",
"command.ai.switchToAgent": "将 AI 代理切换为 {agent}",
@@ -115,6 +121,10 @@
"settings.titles.description": "选择 Tolaria 是否根据第一个 H1 标题自动同步未命名笔记的文件名。",
"settings.titles.autoRename": "根据第一个 H1 自动重命名未命名笔记",
"settings.titles.autoRenameDescription": "启用后,只要第一个 H1 成为真实标题Tolaria 就会重命名 untitled-note 文件。关闭后,文件名会保持不变,直到你从面包屑栏手动重命名。",
"settings.vaultContent.title": "保管库内容",
"settings.vaultContent.description": "控制与 Vault .gitignore 文件匹配的文件是否显示在 Tolaria 中。",
"settings.vaultContent.hideGitignored": "隐藏 Git 忽略的文件和文件夹",
"settings.vaultContent.hideGitignoredDescription": "将生成的 Vault 文件和仅限本地的 Vault 文件排除在笔记、搜索、快速打开和文件夹之外。",
"settings.aiAgents.title": "AI 代理",
"settings.aiAgents.description": "选择 Tolaria 在 AI 面板和命令面板中使用的 CLI AI 代理。",
"settings.aiAgents.default": "默认 AI 代理",
@@ -138,6 +148,68 @@
"settings.cancel": "取消",
"settings.save": "保存",
"common.cancel": "取消",
"ai.permission.safe.short": "安全",
"ai.permission.safe.control": "Vault Safe",
"ai.permission.safe.tooltip": "Vault Safe 将代理的权限限制为仅能使用文件、搜索和编辑工具。",
"ai.permission.powerUser.short": "高级用户",
"ai.permission.powerUser.control": "高级用户",
"ai.permission.powerUser.tooltip": "高级用户还可以对此保管库执行本地 shell 命令。",
"ai.permission.modeAria": "AI 客服机器人权限模式",
"ai.permission.changed": "AI 权限模式已更改为 {label}。此更改将应用于下一条消息。",
"ai.panel.title": "AI 代理",
"ai.panel.status.checking": "正在检查可用性",
"ai.panel.status.missing": "{agent} · 未安装",
"ai.panel.status.ready": "{agent} · {mode}",
"ai.panel.copyMcpConfig": "复制 MCP 配置",
"ai.panel.mcpConfig": "MCP 配置",
"ai.panel.newChat": "新建 AI 聊天",
"ai.panel.close": "关闭 AI 面板",
"ai.panel.linkedCount": "+ {count} 个已关联",
"ai.panel.empty.checkingTitle": "正在检查 AI 客服专员的可用状态",
"ai.panel.empty.checkingDescription": "所选代理就绪后,即可发送消息",
"ai.panel.empty.missingTitle": "{agent} 在此计算机上不可用",
"ai.panel.empty.missingDescription": "请安装该应用,或在“设置”中切换默认 AI 代理",
"ai.panel.empty.withContextTitle": "向{agent}询问有关此笔记及其关联上下文的问题",
"ai.panel.empty.noContextTitle": "打开一个笔记,然后向{agent}询问有关该笔记的问题",
"ai.panel.empty.withContextDescription": "总结、发现关联、拓展想法",
"ai.panel.empty.noContextDescription": "人工智能将使用当前笔记作为上下文",
"ai.panel.placeholder.checking": "正在检查 AI 代理的可用性……",
"ai.panel.placeholder.missing": "未安装 {agent}。在“设置”中打开 AI 代理。",
"ai.panel.placeholder.ready": "询问 {agent}",
"ai.panel.send": "发送消息",
"mcp.setup.manageTitle": "管理外部 AI 工具",
"mcp.setup.setupTitle": "设置外部 AI 工具",
"mcp.setup.connectedDescription": "对于此 VaultTolaria 已经连接到外部 AI 工具。重新连接以刷新配置,或断开连接以从这些第三方配置文件中删除 Tolaria。",
"mcp.setup.setupDescription": "Tolaria 可以将其 MCP 服务器添加到此 Vault 的外部 AI 工具中但只有在您在此处确认后Tolaria 才会修改第三方配置文件。",
"mcp.setup.reconnect": "重新关联外部 AI 工具",
"mcp.setup.connect": "关联外部 AI 工具",
"mcp.setup.disconnect": "断开连接",
"mcp.setup.connecting": "正在连接…",
"mcp.setup.disconnecting": "正在断开连接……",
"mcp.setup.nodeRequirement": "此设置需要 PATH 中包含 Node.js 18+ 及以上版本,并需要一个与 MCP 兼容的桌面工具。Tolaria 会在写入配置之前检查 Node.js以确保该工具不会指向无效的命令。",
"mcp.setup.writeEntryDescription": "确认此操作将写入或更新 Tolaria 在以下位置的单个 {entry} MCP 条目:",
"mcp.setup.clientPathsDescription": "Claude Code CLI 读取 ~/.claude.jsonGemini CLI 读取 ~/.gemini/settings.jsonCursor 读取 ~/.cursor/mcp.json而通用路径 ~/.config/mcp/mcp.json 则由其他与 MCP 兼容的工具读取。关闭应用程序后,除非您连接或断开连接,否则所有文件都不会受到影响。重新连接是幂等操作,而断开连接会再次删除 Tolaria 的条目。",
"mcp.setup.geminiGuidanceDescription": "Gemini CLI 需要单独安装并登录。如果您希望获得一个 Vault 根目录 GEMINI.md 兼容性 shim使 Gemini 重新指向共享的 AGENTS.md 指南而不覆盖自定义指南请使用“Restore Tolaria AI Guidance”。",
"mcp.setup.manual.title": "手动 MCP 配置",
"mcp.setup.manual.copy": "复制 MCP 配置",
"mcp.setup.manual.loading": "正在加载精确的 MCP 配置……",
"mcp.setup.manual.unavailable": "精确配置将在 Vault 打开后可用。",
"mcp.toast.connected": "已成功连接 Tolaria 外部 AI 工具",
"mcp.toast.refreshed": "Tolaria 外部 AI 工具设置已成功刷新",
"mcp.toast.disconnected": "已成功断开 Tolaria 外部 AI 工具的连接",
"mcp.toast.alreadyDisconnected": "Tolaria 外部 AI 工具已断开连接",
"mcp.toast.configCopied": "Tolaria MCP 配置已复制到剪贴板",
"mcp.toast.configCopyFailed": "复制 MCP 配置失败:{error}",
"mcp.toast.setupFailed": "外部 AI 工具设置失败:{error}",
"mcp.toast.disconnectFailed": "断开外部 AI 工具连接失败:{error}",
"mcp.error.clipboardUnavailable": "剪贴板 API 无法使用",
"save.toast.saved": "已保存",
"save.toast.nothingToSave": "没有要保存的内容",
"save.toast.missingActiveVault": "在保存之前,请选择或恢复一个保管库。",
"save.error.failed": "保存失败:{error}",
"save.error.autoFailed": "自动保存失败:{error}",
"save.error.invalidPath": "保存失败:笔记路径在此平台上无效。请重命名该笔记或将其移动到有效文件夹,然后重试。",
"savedViews.reordered": "视图已重新排序",
"locale.en": "英文",
"sidebar.nav.inbox": "收件箱",
"sidebar.nav.allNotes": "全部笔记",
@@ -149,6 +221,9 @@
"sidebar.action.createView": "创建视图",
"sidebar.action.editView": "编辑视图",
"sidebar.action.deleteView": "删除视图",
"sidebar.action.reorderView": "重新排序视图",
"sidebar.action.moveViewUp": "向上移动视图",
"sidebar.action.moveViewDown": "向下移动视图",
"sidebar.action.customizeSections": "自定义分组",
"sidebar.action.renameSection": "重命名分组…",
"sidebar.action.customizeIconColor": "自定义图标和颜色…",
@@ -318,6 +393,8 @@
"status.vault.cloneGit": "克隆 Git 仓库",
"status.vault.cloneGettingStarted": "克隆入门仓库",
"status.vault.notFound": "未找到仓库:{path}",
"status.vault.reloading": "正在重新加载 Vault...",
"status.vault.reloadingTooltip": "正在从磁盘重新加载 Vault",
"status.vault.remove": "从列表移除 {label}",
"status.remote.noneConfigured": "未配置远程仓库",
"status.remote.inSync": "已与远程同步",

View File

@@ -1,5 +1,7 @@
import type { VaultEntry } from '../types'
export type FilePreviewKind = 'image' | 'pdf'
const IMAGE_PREVIEW_EXTENSIONS = new Set([
'apng',
'avif',
@@ -14,6 +16,7 @@ const IMAGE_PREVIEW_EXTENSIONS = new Set([
'tiff',
'webp',
])
const PDF_PREVIEW_EXTENSIONS = new Set(['pdf'])
function extensionFromFilename(filename: string): string | null {
const lastSegment = filename.split(/[\\/]/u).pop() ?? filename
@@ -27,9 +30,25 @@ export function previewExtension(entry: Pick<VaultEntry, 'filename' | 'path'>):
}
export function isImagePreviewEntry(entry: Pick<VaultEntry, 'fileKind' | 'filename' | 'path'>): boolean {
if (entry.fileKind !== 'binary') return false
return filePreviewKind(entry) === 'image'
}
export function isPdfPreviewEntry(entry: Pick<VaultEntry, 'fileKind' | 'filename' | 'path'>): boolean {
return filePreviewKind(entry) === 'pdf'
}
export function filePreviewKind(entry: Pick<VaultEntry, 'fileKind' | 'filename' | 'path'>): FilePreviewKind | null {
if (entry.fileKind && entry.fileKind !== 'binary') return null
const extension = previewExtension(entry)
return extension ? IMAGE_PREVIEW_EXTENSIONS.has(extension) : false
if (!extension) return null
if (IMAGE_PREVIEW_EXTENSIONS.has(extension)) return 'image'
if (PDF_PREVIEW_EXTENSIONS.has(extension)) return 'pdf'
return null
}
export function isFilePreviewEntry(entry: Pick<VaultEntry, 'fileKind' | 'filename' | 'path'>): boolean {
return filePreviewKind(entry) !== null
}
export function previewFileTypeLabel(entry: Pick<VaultEntry, 'filename' | 'path'>): string {