- {group}
+ {localizeCommandGroup(group, locale)}
{items.map((command, index) => {
const globalIndex = startIndex + index
@@ -430,6 +433,7 @@ function OpenCommandPalette({
selectedIndex={selectedIndex}
listRef={listRef}
emptyText={t('command.noMatches')}
+ locale={locale}
onHover={setSelectedIndex}
onSelect={handleSelectCommand}
/>
diff --git a/src/components/ConflictNoteBanner.tsx b/src/components/ConflictNoteBanner.tsx
index 41cf577c..171ef11c 100644
--- a/src/components/ConflictNoteBanner.tsx
+++ b/src/components/ConflictNoteBanner.tsx
@@ -1,11 +1,13 @@
import { AlertTriangle } from 'lucide-react'
+import { translate, type AppLocale } from '../lib/i18n'
interface ConflictNoteBannerProps {
onKeepMine: () => void
onKeepTheirs: () => void
+ locale?: AppLocale
}
-export function ConflictNoteBanner({ onKeepMine, onKeepTheirs }: ConflictNoteBannerProps) {
+export function ConflictNoteBanner({ onKeepMine, onKeepTheirs, locale = 'en' }: ConflictNoteBannerProps) {
return (
-
This note has a merge conflict
+
{translate(locale, 'editor.banner.conflict')}
diff --git a/src/components/DynamicPropertiesPanel.test.tsx b/src/components/DynamicPropertiesPanel.test.tsx
index ce4180c2..c645e306 100644
--- a/src/components/DynamicPropertiesPanel.test.tsx
+++ b/src/components/DynamicPropertiesPanel.test.tsx
@@ -205,6 +205,19 @@ describe('DynamicPropertiesPanel', () => {
expect(screen.getByText('some-team')).toBeInTheDocument()
})
+ it('localizes UI actions without translating stored property names', () => {
+ renderPanel({
+ frontmatter: { Status: 'Active', 'Belongs to': 'some-team' },
+ onAddProperty,
+ locale: 'zh-Hans',
+ })
+
+ expect(screen.getByText('Type')).toBeInTheDocument()
+ expect(screen.getByText('Status')).toBeInTheDocument()
+ expect(screen.getByText('Belongs to')).toBeInTheDocument()
+ expect(screen.getByRole('button', { name: '添加属性' })).toBeInTheDocument()
+ })
+
it('hides custom field with wikilink value from Properties', () => {
renderPanel({ frontmatter: { Mentor: '[[person/luca]]' } })
// Mentor contains a wikilink → shown in Relationships, not Properties
diff --git a/src/components/DynamicPropertiesPanel.tsx b/src/components/DynamicPropertiesPanel.tsx
index a1b11bcf..623d6429 100644
--- a/src/components/DynamicPropertiesPanel.tsx
+++ b/src/components/DynamicPropertiesPanel.tsx
@@ -21,6 +21,7 @@ import {
PROPERTY_PANEL_ROW_STYLE,
} from './propertyPanelLayout'
import { humanizePropertyKey } from '../utils/propertyLabels'
+import { translate, type AppLocale } from '../lib/i18n'
import { canonicalSystemMetadataKey, hasSystemMetadataKey } from '../utils/systemMetadata'
// eslint-disable-next-line react-refresh/only-export-components -- utility co-located with component
@@ -32,7 +33,7 @@ export function containsWikilinks(value: FrontmatterValue): boolean {
const PROPERTY_ROW_CLASS_NAME = 'group/prop grid min-h-7 min-w-0 grid-cols-2 items-center gap-2 rounded px-1.5 outline-none transition-colors hover:bg-muted focus:bg-muted focus:ring-1 focus:ring-primary'
-function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultStatuses, vaultTags, onStartEdit, onSave, onSaveList, onUpdate, onDelete, onDisplayModeChange }: {
+function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultStatuses, vaultTags, locale, onStartEdit, onSave, onSaveList, onUpdate, onDelete, onDisplayModeChange }: {
propKey: string; value: FrontmatterValue; editingKey: string | null
displayMode: PropertyDisplayMode; autoMode: PropertyDisplayMode
vaultStatuses: string[]; vaultTags: string[]
@@ -40,6 +41,7 @@ function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultS
onSaveList: (key: string, items: string[]) => void
onUpdate?: (key: string, value: FrontmatterValue) => void; onDelete?: (key: string) => void
onDisplayModeChange: (key: string, mode: PropertyDisplayMode | null) => void
+ locale: AppLocale
}) {
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.target !== e.currentTarget) {
@@ -57,7 +59,7 @@ function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultS
{humanizePropertyKey(propKey)}
{onDelete && (
-
+
)}
@@ -67,7 +69,7 @@ function PropertyRow({ propKey, value, editingKey, displayMode, autoMode, vaultS
)
}
-function AddPropertyButton({ onClick, disabled }: { onClick: () => void; disabled: boolean }) {
+function AddPropertyButton({ locale, onClick, disabled }: { locale: AppLocale; onClick: () => void; disabled: boolean }) {
return (
@@ -230,6 +232,7 @@ function useSuggestedPropertyActions({
export function DynamicPropertiesPanel({
entry, frontmatter, entries,
onUpdateProperty, onDeleteProperty, onAddProperty, onNavigate, onCreateMissingType,
+ locale = 'en',
}: {
entry: VaultEntry
content?: string | null
@@ -240,6 +243,7 @@ export function DynamicPropertiesPanel({
onAddProperty?: (key: string, value: FrontmatterValue) => void
onNavigate?: (target: string) => void
onCreateMissingType?: (typeName: string) => boolean | void | Promise
+ locale?: AppLocale
}) {
const {
editingKey, setEditingKey, showAddDialog, setShowAddDialog, displayOverrides,
@@ -279,6 +283,7 @@ export function DynamicPropertiesPanel({
onNavigate={onNavigate}
missingTypeName={missingTypeName}
onCreateMissingType={onCreateMissingType}
+ locale={locale}
/>
{propertyEntries.map(([key, value]) => (
))}
{pendingSuggestedKey && editingKey === pendingSuggestedKey && (
@@ -308,6 +314,7 @@ export function DynamicPropertiesPanel({
onUpdate={undefined}
onDelete={undefined}
onDisplayModeChange={handleDisplayModeChange}
+ locale={locale}
/>
)}
{missingSuggested.map(({ key, label }) => (
@@ -320,6 +327,7 @@ export function DynamicPropertiesPanel({
))}
{!showAddDialog && (
setShowAddDialog(true)}
disabled={!onAddProperty}
/>
@@ -330,6 +338,7 @@ export function DynamicPropertiesPanel({
onAdd={handleAdd}
onCancel={() => setShowAddDialog(false)}
vaultStatuses={vaultStatuses}
+ locale={locale}
/>
)}
diff --git a/src/components/Editor.css b/src/components/Editor.css
index e4881d1f..b47abeb3 100644
--- a/src/components/Editor.css
+++ b/src/components/Editor.css
@@ -210,14 +210,8 @@
}
.raw-editor-codemirror {
- max-width: var(--editor-max-width, 760px);
width: 100%;
- margin: 0 auto;
-}
-
-.editor-content-layout--left .raw-editor-codemirror {
- margin-left: clamp(16px, 6%, 96px);
- margin-right: auto;
+ margin: 0;
}
/* --- Note Icon Area --- */
@@ -340,8 +334,7 @@
}
@container editor (max-width: 900px) {
- .editor-content-layout--left .editor-content-wrapper,
- .editor-content-layout--left .raw-editor-codemirror {
+ .editor-content-layout--left .editor-content-wrapper {
margin-left: auto;
margin-right: auto;
}
diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx
index 7ff9dddf..6d514eb3 100644
--- a/src/components/Editor.tsx
+++ b/src/components/Editor.tsx
@@ -5,6 +5,7 @@ import '@blocknote/mantine/style.css'
import 'katex/dist/katex.min.css'
import { uploadImageFile } from '../hooks/useImageDrop'
import { DEFAULT_AI_AGENT, type AiAgentId } from '../lib/aiAgents'
+import { translate, type AppLocale } from '../lib/i18n'
import { RUNTIME_STYLE_NONCE } from '../lib/runtimeStyleNonce'
import type { VaultEntry, GitCommit, NoteLayout, NoteStatus } from '../types'
import type { NoteListItem } from '../utils/ai-context'
@@ -96,6 +97,7 @@ interface EditorProps {
onKeepTheirs?: (path: string) => void
/** Registers a hook that flushes the raw editor buffer into app state before external actions. */
flushPendingRawContentRef?: React.MutableRefObject<((path: string) => void) | null>
+ locale?: AppLocale
}
function useEditorModeExclusion({
@@ -129,7 +131,7 @@ function useEditorModeExclusion({
return { handleToggleDiffExclusive, handleToggleRawExclusive }
}
-function EditorEmptyState() {
+function EditorEmptyState({ locale = 'en' }: { locale?: AppLocale }) {
const breadcrumbBarHeight = 52
const { onMouseDown } = useDragRegion()
const quickOpenShortcut = formatShortcutDisplay({ display: '⌘P / ⌘O' })
@@ -146,8 +148,8 @@ function EditorEmptyState() {
style={{ height: breadcrumbBarHeight }}
/>
-
Select a note to start editing
-
{quickOpenShortcut} to search · {newNoteShortcut} to create
+
{translate(locale, 'editor.empty.selectNote')}
+
{translate(locale, 'editor.empty.shortcuts', { quickOpen: quickOpenShortcut, newNote: newNoteShortcut })}
)
@@ -328,6 +330,7 @@ function EditorLayout({
onFileModified,
onVaultChanged,
onUnsupportedAiPaste,
+ locale,
}: {
tabs: Tab[]
activeTab: Tab | null
@@ -384,12 +387,13 @@ function EditorLayout({
onFileModified?: (relativePath: string) => void
onVaultChanged?: () => void
onUnsupportedAiPaste?: (message: string) => void
+ locale?: AppLocale
}) {
return (