cantip 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,7 @@
1
1
  import { useEffect } from 'react'
2
2
 
3
+ import { t } from '~/lib/site'
4
+
3
5
  /**
4
6
  * The document body HTML is rendered server-side and injected via
5
7
  * `dangerouslySetInnerHTML`, so code blocks aren't React elements we can give a
@@ -21,8 +23,8 @@ const WRAP_ICON =
21
23
  const NOWRAP_ICON =
22
24
  '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>'
23
25
 
24
- const WRAP_LABEL = 'Переносить строки'
25
- const NOWRAP_LABEL = 'Не переносить строки'
26
+ const WRAP_LABEL = t('wrapLines')
27
+ const NOWRAP_LABEL = t('noWrapLines')
26
28
 
27
29
  export function CodeWrapToggle({ deps }: { deps?: string }) {
28
30
  useEffect(() => {
@@ -1,6 +1,7 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
2
  import { ChevronDown, ChevronUp, Search as SearchIcon, X } from 'lucide-react'
3
3
 
4
+ import { t } from '~/lib/site'
4
5
  import { cn } from '~/lib/utils'
5
6
 
6
7
  /**
@@ -171,7 +172,7 @@ export default function FindOnPage({ onClose }: { onClose: () => void }) {
171
172
  'flex items-center gap-1 rounded-2xl border bg-sidebar/95 px-2 py-1.5 shadow-lg backdrop-blur',
172
173
  )}
173
174
  role="search"
174
- aria-label="Найти на странице"
175
+ aria-label={t('findOnPage')}
175
176
  >
176
177
  <SearchIcon className="size-4 shrink-0 text-muted-foreground" />
177
178
  <input
@@ -179,7 +180,7 @@ export default function FindOnPage({ onClose }: { onClose: () => void }) {
179
180
  value={query}
180
181
  onChange={(e) => setQuery(e.target.value)}
181
182
  onKeyDown={onKeyDown}
182
- placeholder="Найти на странице…"
183
+ placeholder={t('findOnPagePlaceholder')}
183
184
  className="h-8 min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
184
185
  autoComplete="off"
185
186
  spellCheck={false}
@@ -198,7 +199,7 @@ export default function FindOnPage({ onClose }: { onClose: () => void }) {
198
199
  onClick={() => step(-1)}
199
200
  disabled={!has}
200
201
  className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-40"
201
- aria-label="Предыдущее совпадение"
202
+ aria-label={t('prevMatch')}
202
203
  >
203
204
  <ChevronUp className="size-4" />
204
205
  </button>
@@ -207,7 +208,7 @@ export default function FindOnPage({ onClose }: { onClose: () => void }) {
207
208
  onClick={() => step(1)}
208
209
  disabled={!has}
209
210
  className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-40"
210
- aria-label="Следующее совпадение"
211
+ aria-label={t('nextMatch')}
211
212
  >
212
213
  <ChevronDown className="size-4" />
213
214
  </button>
@@ -215,7 +216,7 @@ export default function FindOnPage({ onClose }: { onClose: () => void }) {
215
216
  type="button"
216
217
  onClick={onClose}
217
218
  className="rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
218
- aria-label="Закрыть поиск по странице"
219
+ aria-label={t('closeFindOnPage')}
219
220
  >
220
221
  <X className="size-4" />
221
222
  </button>
@@ -45,7 +45,7 @@ export default function MobileBottomBar({
45
45
  'fixed inset-x-3 bottom-[calc(env(safe-area-inset-bottom)+0.75rem)] z-100 md:hidden',
46
46
  'flex items-stretch gap-1 rounded-2xl border bg-sidebar/95 px-1.5 py-1 shadow-lg backdrop-blur',
47
47
  )}
48
- aria-label="Навигация"
48
+ aria-label={t('navigation')}
49
49
  >
50
50
  <Link to="/" className={tab} aria-label={t('home')}>
51
51
  <Home className="size-5" />
@@ -105,7 +105,7 @@ export default function MobileProjectsPanel({ activeId, open, onClose }: Props)
105
105
 
106
106
  {/* Theme toggle row — a normal "settings" row at the bottom of the menu. */}
107
107
  <div className="flex shrink-0 items-center justify-between border-t px-4 py-3">
108
- <span className="text-sm text-foreground">Тема оформления</span>
108
+ <span className="text-sm text-foreground">{t('theme')}</span>
109
109
  <ThemeToggle className="text-muted-foreground" />
110
110
  </div>
111
111
  </div>
@@ -4,6 +4,7 @@ import { ArrowUp, ChevronLeft, ChevronRight, List, Search as SearchIcon, X } fro
4
4
  import type { Heading } from '~/lib/content.server'
5
5
  import { TocLinks, tocHeadings } from '~/components/Toc'
6
6
  import FindOnPage from '~/components/FindOnPage'
7
+ import { t } from '~/lib/site'
7
8
  import { cn } from '~/lib/utils'
8
9
 
9
10
  /** Shared tab class — IDENTICAL to MobileBottomBar's `tab`, so this row reads as
@@ -119,29 +120,29 @@ export default function PageFloatingMenu({ headings }: { headings: Heading[] })
119
120
  'fixed inset-x-3 bottom-[calc(var(--mobile-bar-height)+env(safe-area-inset-bottom)+1.5rem)] z-80 md:hidden',
120
121
  'flex items-stretch gap-1 rounded-2xl border bg-sidebar/95 px-1.5 py-1 shadow-lg backdrop-blur',
121
122
  )}
122
- aria-label="Действия страницы"
123
+ aria-label={t('pageActions')}
123
124
  >
124
- <button type="button" onClick={scrollTop} className={tab} aria-label="Наверх">
125
+ <button type="button" onClick={scrollTop} className={tab} aria-label={t('scrollTop')}>
125
126
  <ArrowUp className="size-5" />
126
- <span>Наверх</span>
127
+ <span>{t('scrollTop')}</span>
127
128
  </button>
128
- <button type="button" onClick={openFind} className={tab} aria-label="Найти на странице">
129
+ <button type="button" onClick={openFind} className={tab} aria-label={t('findOnPage')}>
129
130
  <SearchIcon className="size-5" />
130
- <span>Найти</span>
131
+ <span>{t('find')}</span>
131
132
  </button>
132
- <button type="button" onClick={openToc} className={tab} aria-label="Содержание">
133
+ <button type="button" onClick={openToc} className={tab} aria-label={t('toc')}>
133
134
  <List className="size-5" />
134
- <span>Содержание</span>
135
+ <span>{t('toc')}</span>
135
136
  </button>
136
137
  <button
137
138
  type="button"
138
139
  onClick={() => setOpen(false)}
139
140
  className={tab}
140
- aria-label="Скрыть"
141
+ aria-label={t('hide')}
141
142
  aria-expanded
142
143
  >
143
144
  <ChevronRight className="size-5" />
144
- <span>Скрыть</span>
145
+ <span>{t('hide')}</span>
145
146
  </button>
146
147
  </nav>
147
148
  ) : (
@@ -157,7 +158,7 @@ export default function PageFloatingMenu({ headings }: { headings: Heading[] })
157
158
  type="button"
158
159
  onClick={() => setOpen(true)}
159
160
  className="flex items-center gap-1 rounded-xl border bg-sidebar/95 px-3 py-2 text-[0.6875rem] font-medium text-muted-foreground shadow-md backdrop-blur transition-colors hover:text-foreground"
160
- aria-label="Действия страницы"
161
+ aria-label={t('pageActions')}
161
162
  aria-expanded={false}
162
163
  >
163
164
  <ChevronLeft className="size-3.5" />
@@ -197,19 +198,19 @@ function TocSheet({ shown, onClose }: { shown: Heading[]; onClose: () => void })
197
198
  onClick={onClose}
198
199
  role="dialog"
199
200
  aria-modal="true"
200
- aria-label="Содержание"
201
+ aria-label={t('toc')}
201
202
  >
202
203
  <div
203
204
  className="flex h-[75vh] flex-col overflow-hidden rounded-t-2xl border-t bg-popover pb-[env(safe-area-inset-bottom)]"
204
205
  onClick={(e) => e.stopPropagation()}
205
206
  >
206
207
  <div className="flex shrink-0 items-center justify-between border-b px-4 py-3">
207
- <span className="text-sm font-semibold text-foreground">Содержание</span>
208
+ <span className="text-sm font-semibold text-foreground">{t('toc')}</span>
208
209
  <button
209
210
  type="button"
210
211
  onClick={onClose}
211
212
  className="rounded-md p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
212
- aria-label="Закрыть"
213
+ aria-label={t('close')}
213
214
  >
214
215
  <X className="size-5" />
215
216
  </button>
@@ -584,10 +584,10 @@ export function Search({
584
584
  'inline-flex items-center gap-2 rounded-md border bg-background/60 px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground',
585
585
  className,
586
586
  )}
587
- aria-label="Поиск"
587
+ aria-label={t('search')}
588
588
  >
589
589
  <SearchIcon className="size-4 shrink-0" />
590
- <span className="max-md:hidden">Поиск…</span>
590
+ <span className="max-md:hidden">{t('searchEllipsis')}</span>
591
591
  <kbd className="ml-auto hidden rounded border px-1.5 text-xs md:inline">Ctrl K</kbd>
592
592
  </button>
593
593
  )}
@@ -604,7 +604,7 @@ export function Search({
604
604
  onMouseDown={dismiss}
605
605
  role="dialog"
606
606
  aria-modal="true"
607
- aria-label="Поиск по документации"
607
+ aria-label={t('searchDocs')}
608
608
  >
609
609
  <div
610
610
  className="fixed inset-0 flex flex-col overflow-hidden bg-popover md:static md:max-h-[80vh] md:w-full md:max-w-3xl md:rounded-lg md:border md:shadow-xl"
@@ -617,7 +617,7 @@ export function Search({
617
617
  ref={inputRef}
618
618
  value={query}
619
619
  onChange={(e) => setQuery(e.target.value)}
620
- placeholder="Поиск по документации…"
620
+ placeholder={t('searchDocsPlaceholder')}
621
621
  className="h-12 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground"
622
622
  autoComplete="off"
623
623
  spellCheck={false}
@@ -629,9 +629,9 @@ export function Search({
629
629
  'rounded p-1 hover:bg-accent hover:text-foreground',
630
630
  isExact ? 'bg-accent text-foreground' : 'text-muted-foreground',
631
631
  )}
632
- aria-label="Точное совпадение"
632
+ aria-label={t('exactMatch')}
633
633
  aria-pressed={isExact}
634
- title="Точное совпадение фразы"
634
+ title={t('exactMatchHint')}
635
635
  >
636
636
  <WholeWord className="size-4" />
637
637
  </button>
@@ -644,8 +644,8 @@ export function Search({
644
644
  onClick={reset}
645
645
  disabled={!query && !dir}
646
646
  className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-40"
647
- aria-label="Очистить поиск"
648
- title="Очистить"
647
+ aria-label={t('clearSearch')}
648
+ title={t('clear')}
649
649
  >
650
650
  <X className="size-4" />
651
651
  </button>
@@ -657,14 +657,14 @@ export function Search({
657
657
  {projectOptions.length > 0 && (
658
658
  <div className="border-b px-3 py-2 text-xs text-muted-foreground">
659
659
  <div className="flex flex-wrap items-center gap-2">
660
- <span className="shrink-0">Искать в:</span>
660
+ <span className="shrink-0">{t('searchIn')}</span>
661
661
  <select
662
662
  value={project}
663
663
  onChange={(e) => changeProject(e.target.value)}
664
664
  className="rounded border bg-background px-2 py-1 text-foreground outline-none focus:ring-1 focus:ring-ring"
665
665
  aria-label={t('searchProjectFilter')}
666
666
  >
667
- <option value="">Все проекты</option>
667
+ <option value="">{t('allProjects')}</option>
668
668
  {projectOptions.map((p) => (
669
669
  <option key={p} value={p}>
670
670
  {projectLabel(p)}
@@ -697,9 +697,9 @@ export function Search({
697
697
  setDirFocused(false)
698
698
  }
699
699
  }}
700
- placeholder="Папка (напр. требования/управление-клиентами)"
700
+ placeholder={t('folderPlaceholder')}
701
701
  className="w-full rounded border bg-background px-2 py-1 text-foreground outline-none placeholder:text-muted-foreground focus:ring-1 focus:ring-ring"
702
- aria-label="Папка"
702
+ aria-label={t('folder')}
703
703
  autoComplete="off"
704
704
  spellCheck={false}
705
705
  />
@@ -708,7 +708,7 @@ export function Search({
708
708
  type="button"
709
709
  onClick={() => commitDir('')}
710
710
  className="absolute right-1 top-1/2 -translate-y-1/2 rounded p-0.5 text-muted-foreground hover:bg-accent hover:text-foreground"
711
- aria-label="Очистить папку"
711
+ aria-label={t('clearFolder')}
712
712
  >
713
713
  <X className="size-3" />
714
714
  </button>
@@ -780,9 +780,9 @@ export function Search({
780
780
  'shrink-0 rounded border p-1 hover:bg-accent hover:text-foreground',
781
781
  showTree ? 'bg-accent text-foreground' : 'text-muted-foreground',
782
782
  )}
783
- aria-label="Выбрать папку из дерева"
783
+ aria-label={t('pickFolderFromTree')}
784
784
  aria-pressed={showTree}
785
- title="Дерево папок"
785
+ title={t('folderTree')}
786
786
  >
787
787
  <FolderTree className="size-4" />
788
788
  </button>
@@ -796,13 +796,13 @@ export function Search({
796
796
  <div className="min-h-0 flex-1 overflow-y-auto p-2">
797
797
  {!query.trim() ? (
798
798
  <p className="px-3 py-6 text-center text-sm text-muted-foreground">
799
- {ready ? 'Введите запрос для поиска' : 'Загрузка индекса…'}
799
+ {ready ? t('typeToSearch') : t('loadingIndex')}
800
800
  </p>
801
801
  ) : loading && results.length === 0 ? (
802
- <p className="px-3 py-6 text-center text-sm text-muted-foreground">Поиск…</p>
802
+ <p className="px-3 py-6 text-center text-sm text-muted-foreground">{t('searchEllipsis')}</p>
803
803
  ) : results.length === 0 ? (
804
804
  <p className="px-3 py-6 text-center text-sm text-muted-foreground">
805
- Ничего не найдено по запросу «{query.trim()}»
805
+ {t('noResultsFor')} «{query.trim()}»
806
806
  </p>
807
807
  ) : (
808
808
  <ul className="flex flex-col gap-1.5">
@@ -843,7 +843,7 @@ export function Search({
843
843
  onMouseDown={() => setShowTree(false)}
844
844
  role="dialog"
845
845
  aria-modal="true"
846
- aria-label="Выбор папки"
846
+ aria-label={t('folderPicker')}
847
847
  >
848
848
  <div
849
849
  className="fixed inset-0 flex flex-col overflow-hidden bg-popover md:static md:max-h-[70vh] md:w-full md:max-w-md md:rounded-lg md:border md:shadow-xl"
@@ -852,7 +852,7 @@ export function Search({
852
852
  <div className="flex items-center justify-between gap-2 border-b px-3 py-2">
853
853
  <span className="flex items-center gap-2 text-sm font-medium">
854
854
  <FolderTree className="size-4 shrink-0 text-muted-foreground" />
855
- Папка в «{projectLabel(project)}»
855
+ {t('folderIn')} «{projectLabel(project)}»
856
856
  </span>
857
857
  {/* Top-right X cancels, like the backdrop — no scope change. */}
858
858
  <button
@@ -878,7 +878,7 @@ export function Search({
878
878
  )}
879
879
  >
880
880
  <Folder className="size-3.5 shrink-0 text-muted-foreground" />
881
- <span className="truncate">{projectLabel(project)} (весь проект)</span>
881
+ <span className="truncate">{projectLabel(project)} ({t('wholeProject')})</span>
882
882
  </button>
883
883
  </li>
884
884
  {dirTree.map((node) => (
@@ -893,7 +893,7 @@ export function Search({
893
893
  </ul>
894
894
  ) : (
895
895
  <p className="px-3 py-6 text-center text-sm text-muted-foreground">
896
- В этом проекте нет вложенных папок
896
+ {t('noSubfolders')}
897
897
  </p>
898
898
  )}
899
899
  </div>
@@ -907,7 +907,7 @@ export function Search({
907
907
  onClick={() => setShowTree(false)}
908
908
  className="flex-1 rounded-md border px-3 py-1.5 text-sm hover:bg-accent hover:text-foreground max-md:py-2 md:flex-none"
909
909
  >
910
- Отмена
910
+ {t('cancel')}
911
911
  </button>
912
912
  <button
913
913
  type="button"
@@ -918,7 +918,7 @@ export function Search({
918
918
  }}
919
919
  className="flex-1 rounded-md border border-transparent bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 max-md:py-2 md:flex-none"
920
920
  >
921
- ОК
921
+ {t('ok')}
922
922
  </button>
923
923
  </div>
924
924
  </div>
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useState } from 'react'
2
2
  import { createPortal } from 'react-dom'
3
3
 
4
- import { ALL_SHORTCUTS, type ShortcutGroup, type ShortcutInfo } from '~/lib/useKeyboardShortcuts'
4
+ import { ALL_SHORTCUTS, groupLabelKey, type ShortcutGroup, type ShortcutInfo } from '~/lib/useKeyboardShortcuts'
5
5
  import { t } from '~/lib/site'
6
6
 
7
7
  /**
@@ -15,7 +15,7 @@ import { t } from '~/lib/site'
15
15
  * on mobile since `?` and the modal simply never get triggered.
16
16
  */
17
17
 
18
- const GROUP_ORDER: ShortcutGroup[] = ['Навигация', 'Дерево', 'Вкладки']
18
+ const GROUP_ORDER: ShortcutGroup[] = ['nav', 'tree', 'tabs']
19
19
 
20
20
  /**
21
21
  * Open the cheatsheet from anywhere (e.g. the TopBar button) by dispatching this
@@ -91,12 +91,12 @@ export function ShortcutsHelp() {
91
91
  {GROUP_ORDER.filter((g) => byGroup.has(g)).map((group) => (
92
92
  <div key={group} className="mb-4 last:mb-0">
93
93
  <div className="mb-1.5 text-xs font-medium tracking-wide text-muted-foreground uppercase">
94
- {group}
94
+ {t(groupLabelKey(group))}
95
95
  </div>
96
96
  <ul className="space-y-1">
97
97
  {byGroup.get(group)!.map((r, i) => (
98
98
  <li key={i} className="flex items-center justify-between gap-4 text-sm">
99
- <span className="text-foreground">{r.label}</span>
99
+ <span className="text-foreground">{t(r.labelKey)}</span>
100
100
  <kbd className="shrink-0 rounded border bg-muted px-1.5 py-0.5 font-mono text-xs text-muted-foreground">
101
101
  {r.hint}
102
102
  </kbd>
@@ -201,9 +201,9 @@ interface SearchHit {
201
201
  /** What the file-search modal matches against. */
202
202
  type SearchScope = 'all' | 'files' | 'directories'
203
203
  const SCOPE_OPTIONS: { value: SearchScope; label: string }[] = [
204
- { value: 'all', label: 'Все' },
205
- { value: 'files', label: 'Файлы' },
206
- { value: 'directories', label: 'Папки' },
204
+ { value: 'all', label: t('scopeAll') },
205
+ { value: 'files', label: t('scopeFiles') },
206
+ { value: 'directories', label: t('scopeDirectories') },
207
207
  ]
208
208
 
209
209
  /**
@@ -318,7 +318,7 @@ function FileSearchModal({
318
318
  value={query}
319
319
  onChange={(e) => setQuery(e.target.value)}
320
320
  onKeyDown={onInputKey}
321
- placeholder="Поиск по файлам..."
321
+ placeholder={t('fileSearchPlaceholder')}
322
322
  autoComplete="off"
323
323
  className="h-11 flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
324
324
  />
@@ -345,7 +345,7 @@ function FileSearchModal({
345
345
  </div>
346
346
  <ul ref={listRef} className="min-h-0 flex-1 overflow-y-auto p-1">
347
347
  {hits.length === 0 ? (
348
- <li className="px-3 py-6 text-center text-sm text-muted-foreground">Ничего не найдено</li>
348
+ <li className="px-3 py-6 text-center text-sm text-muted-foreground">{t('nothingFound')}</li>
349
349
  ) : (
350
350
  hits.map((hit, i) => {
351
351
  const isActive = i === active
@@ -457,7 +457,7 @@ function RowMenu({
457
457
  <button
458
458
  ref={btnRef}
459
459
  type="button"
460
- aria-label="Действия"
460
+ aria-label={t('treeActions')}
461
461
  aria-haspopup="menu"
462
462
  aria-expanded={open}
463
463
  // stop dblclick from opening a tab when interacting with the menu button
@@ -691,8 +691,8 @@ export default function Sidebar({ data, currentPath, open = false, className }:
691
691
  // field): `l` reveals the current page, `g c` collapses every folder. ---
692
692
  const treeShortcuts = useMemo<Shortcut[]>(
693
693
  () => [
694
- { keys: 'l', label: 'Найти текущую страницу в дереве', group: 'Дерево', run: locate },
695
- { keys: 'c', label: 'Свернуть все папки', group: 'Дерево', run: collapseAll },
694
+ { keys: 'l', label: 'locate page in tree', group: 'tree', run: locate },
695
+ { keys: 'c', label: 'collapse all folders', group: 'tree', run: collapseAll },
696
696
  ],
697
697
  [locate, collapseAll],
698
698
  )
@@ -820,7 +820,7 @@ export default function Sidebar({ data, currentPath, open = false, className }:
820
820
  onPointerDown={startResize}
821
821
  role="separator"
822
822
  aria-orientation="vertical"
823
- aria-label="Изменить ширину панели"
823
+ aria-label={t('resizePanel')}
824
824
  className={cn(
825
825
  'absolute right-0 top-0 z-10 hidden h-full w-1.5 cursor-col-resize md:block',
826
826
  'hover:bg-sidebar-accent',
@@ -833,8 +833,8 @@ export default function Sidebar({ data, currentPath, open = false, className }:
833
833
  variant="ghost"
834
834
  size="icon"
835
835
  onClick={() => setSearchModalOpen(true)}
836
- title="Поиск по файлам (Ctrl+P)"
837
- aria-label="Поиск по файлам"
836
+ title={t('fileSearchHint')}
837
+ aria-label={t('fileSearch')}
838
838
  className="size-9 text-muted-foreground md:size-6"
839
839
  >
840
840
  <Search className="size-4 md:size-3.5" />
@@ -844,8 +844,8 @@ export default function Sidebar({ data, currentPath, open = false, className }:
844
844
  variant="ghost"
845
845
  size="icon"
846
846
  onClick={locate}
847
- title="Найти в дереве (L)"
848
- aria-label="Найти текущую страницу в дереве"
847
+ title={t('locateInTreeHint')}
848
+ aria-label={t('locateInTree')}
849
849
  className="size-9 text-muted-foreground md:size-6"
850
850
  >
851
851
  <Locate className="size-4 md:size-3.5" />
@@ -855,8 +855,8 @@ export default function Sidebar({ data, currentPath, open = false, className }:
855
855
  variant="ghost"
856
856
  size="icon"
857
857
  onClick={collapseAll}
858
- title="Свернуть всё (C)"
859
- aria-label="Свернуть все папки"
858
+ title={t('collapseAllHint')}
859
+ aria-label={t('collapseAll')}
860
860
  className="size-9 text-muted-foreground md:size-6"
861
861
  >
862
862
  <ChevronsDownUp className="size-4 md:size-3.5" />
@@ -1001,7 +1001,7 @@ export default function Sidebar({ data, currentPath, open = false, className }:
1001
1001
  <RowMenu
1002
1002
  actions={[
1003
1003
  {
1004
- label: 'Открыть в новой вкладке (Shift+Enter)',
1004
+ label: t('openInNewTab'),
1005
1005
  onSelect: () => {
1006
1006
  item.setFocused()
1007
1007
  openFile(itemData.href!, itemData.name, true)
@@ -1014,7 +1014,7 @@ export default function Sidebar({ data, currentPath, open = false, className }:
1014
1014
  <RowMenu
1015
1015
  actions={[
1016
1016
  {
1017
- label: 'Развернуть рекурсивно (Shift+Enter)',
1017
+ label: t('expandRecursively'),
1018
1018
  onSelect: () => {
1019
1019
  item.setFocused()
1020
1020
  // expandAll on the item instance opens this folder and
@@ -1030,7 +1030,7 @@ export default function Sidebar({ data, currentPath, open = false, className }:
1030
1030
  )
1031
1031
  })}
1032
1032
  {isFiltering && matchingIds.size === 0 && (
1033
- <div className="p-4 text-center text-[0.8125rem] text-muted-foreground">Ничего не найдено</div>
1033
+ <div className="p-4 text-center text-[0.8125rem] text-muted-foreground">{t('nothingFound')}</div>
1034
1034
  )}
1035
1035
  </div>
1036
1036
  )}
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
5
  import { DropdownMenu, DropdownMenuItem } from "~/components/ui/dropdown-menu";
6
6
  import { useTabs, normTabPath } from "~/lib/tabs";
7
7
  import { useKeyboardShortcuts, type Shortcut } from "~/lib/useKeyboardShortcuts";
8
+ import { t } from "~/lib/site";
8
9
  import { cn } from "~/lib/utils";
9
10
 
10
11
  /**
@@ -106,8 +107,8 @@ export default function TabBar() {
106
107
  () => [
107
108
  {
108
109
  keys: "w",
109
- label: "Закрыть текущую вкладку",
110
- group: "Вкладки",
110
+ label: "close current tab",
111
+ group: "tabs",
111
112
  run: () => {
112
113
  if (tabs.some((t) => normTabPath(t.path) === cur)) closePath(cur);
113
114
  },
@@ -165,9 +166,9 @@ export default function TabBar() {
165
166
  <span className="min-w-0 truncate">{tab.title}</span>
166
167
  <button
167
168
  type="button"
168
- aria-label={`Закрыть ${tab.title}`}
169
+ aria-label={`${t('close')} ${tab.title}`}
169
170
  // The `w` shortcut closes the *active* tab, so only hint it there.
170
- title={isActive ? "Закрыть (W)" : undefined}
171
+ title={isActive ? t('closeTabHint') : undefined}
171
172
  onClick={(e) => close(e, tab.path)}
172
173
  className={cn(
173
174
  "flex size-5 shrink-0 items-center justify-center rounded hover:bg-foreground/10",
@@ -207,7 +208,7 @@ export default function TabBar() {
207
208
  vertical-dots button opens a dropdown with "close all tabs". */}
208
209
  <DropdownMenu
209
210
  align="end"
210
- label="Действия с вкладками"
211
+ label={t('tabActions')}
211
212
  className={({ open }) =>
212
213
  cn(
213
214
  "flex h-full w-9 shrink-0 items-center justify-center border-l text-muted-foreground",
@@ -219,7 +220,7 @@ export default function TabBar() {
219
220
  >
220
221
  <DropdownMenuItem onSelect={closeAll}>
221
222
  <X className="size-4 shrink-0 text-muted-foreground" />
222
- <span>Закрыть все вкладки ({tabs.length})</span>
223
+ <span>{t('closeAllTabs')} ({tabs.length})</span>
223
224
  </DropdownMenuItem>
224
225
  </DropdownMenu>
225
226
  </div>
@@ -66,7 +66,7 @@ export function TocLinks({ shown }: { shown: Heading[] }) {
66
66
  const activeSlug = useActiveHeading(shown)
67
67
 
68
68
  if (shown.length === 0) {
69
- return <p className="m-0 text-muted-foreground">Нет содержания</p>
69
+ return <p className="m-0 text-muted-foreground">{t('noToc')}</p>
70
70
  }
71
71
  return (
72
72
  <ul className="m-0 list-none p-0">
@@ -94,6 +94,77 @@ export const UI_STRINGS: Record<string, Record<string, string>> = {
94
94
  files: 'Файлы',
95
95
  search: 'Поиск',
96
96
  toggleTheme: 'Переключить тему',
97
+ // File-search (Ctrl/Cmd+P) in the sidebar
98
+ fileSearchPlaceholder: 'Поиск по файлам...',
99
+ fileSearch: 'Поиск по файлам',
100
+ fileSearchHint: 'Поиск по файлам (Ctrl+P)',
101
+ scopeAll: 'Все',
102
+ scopeFiles: 'Файлы',
103
+ scopeDirectories: 'Папки',
104
+ nothingFound: 'Ничего не найдено',
105
+ // Sidebar tree actions
106
+ treeActions: 'Действия',
107
+ resizePanel: 'Изменить ширину панели',
108
+ locateInTree: 'Найти текущую страницу в дереве',
109
+ locateInTreeHint: 'Найти в дереве (L)',
110
+ collapseAll: 'Свернуть все папки',
111
+ collapseAllHint: 'Свернуть всё (C)',
112
+ openInNewTab: 'Открыть в новой вкладке (Shift+Enter)',
113
+ expandRecursively: 'Развернуть рекурсивно (Shift+Enter)',
114
+ // Content search (Ctrl/Cmd+K)
115
+ searchDocs: 'Поиск по документации',
116
+ searchDocsPlaceholder: 'Поиск по документации…',
117
+ exactMatch: 'Точное совпадение',
118
+ exactMatchHint: 'Точное совпадение фразы',
119
+ clearSearch: 'Очистить поиск',
120
+ clear: 'Очистить',
121
+ searchIn: 'Искать в:',
122
+ allProjects: 'Все проекты',
123
+ wholeProject: 'весь проект',
124
+ folder: 'Папка',
125
+ folderPlaceholder: 'Папка (напр. требования/управление-клиентами)',
126
+ clearFolder: 'Очистить папку',
127
+ pickFolderFromTree: 'Выбрать папку из дерева',
128
+ folderTree: 'Дерево папок',
129
+ folderPicker: 'Выбор папки',
130
+ typeToSearch: 'Введите запрос для поиска',
131
+ loadingIndex: 'Загрузка индекса…',
132
+ noResultsFor: 'Ничего не найдено по запросу',
133
+ cancel: 'Отмена',
134
+ ok: 'ОК',
135
+ searchEllipsis: 'Поиск…',
136
+ folderIn: 'Папка в',
137
+ noSubfolders: 'В этом проекте нет вложенных папок',
138
+ // Keyboard-shortcut cheatsheet: group display names + per-shortcut labels.
139
+ groupNav: 'Навигация',
140
+ groupTree: 'Дерево',
141
+ groupTabs: 'Вкладки',
142
+ scSearchContent: 'Поиск по содержимому',
143
+ scShowShortcuts: 'Показать список горячих клавиш',
144
+ scSearchFile: 'Поиск файла по имени',
145
+ scLocate: 'Найти текущую страницу в дереве',
146
+ scCollapseAll: 'Свернуть все папки',
147
+ scCloseTab: 'Закрыть текущую вкладку',
148
+ scShiftEnter: 'Файл — открыть в новой вкладке; папку — развернуть рекурсивно',
149
+ // Misc chrome (Toc, mobile menus, code-wrap, find-on-page, tabs)
150
+ noToc: 'Нет содержания',
151
+ theme: 'Тема оформления',
152
+ wrapLines: 'Переносить строки',
153
+ noWrapLines: 'Не переносить строки',
154
+ navigation: 'Навигация',
155
+ pageActions: 'Действия страницы',
156
+ scrollTop: 'Наверх',
157
+ findOnPage: 'Найти на странице',
158
+ findOnPagePlaceholder: 'Найти на странице…',
159
+ toc: 'Содержание',
160
+ hide: 'Скрыть',
161
+ find: 'Найти',
162
+ prevMatch: 'Предыдущее совпадение',
163
+ nextMatch: 'Следующее совпадение',
164
+ closeFindOnPage: 'Закрыть поиск по странице',
165
+ closeTabHint: 'Закрыть (W)',
166
+ tabActions: 'Действия с вкладками',
167
+ closeAllTabs: 'Закрыть все вкладки',
97
168
  },
98
169
  en: {
99
170
  projects: 'Projects',
@@ -111,6 +182,72 @@ export const UI_STRINGS: Record<string, Record<string, string>> = {
111
182
  files: 'Files',
112
183
  search: 'Search',
113
184
  toggleTheme: 'Toggle theme',
185
+ fileSearchPlaceholder: 'Search files...',
186
+ fileSearch: 'Search files',
187
+ fileSearchHint: 'Search files (Ctrl+P)',
188
+ scopeAll: 'All',
189
+ scopeFiles: 'Files',
190
+ scopeDirectories: 'Folders',
191
+ nothingFound: 'Nothing found',
192
+ treeActions: 'Actions',
193
+ resizePanel: 'Resize panel',
194
+ locateInTree: 'Locate current page in the tree',
195
+ locateInTreeHint: 'Locate in tree (L)',
196
+ collapseAll: 'Collapse all folders',
197
+ collapseAllHint: 'Collapse all (C)',
198
+ openInNewTab: 'Open in a new tab (Shift+Enter)',
199
+ expandRecursively: 'Expand recursively (Shift+Enter)',
200
+ searchDocs: 'Search the docs',
201
+ searchDocsPlaceholder: 'Search the docs…',
202
+ exactMatch: 'Exact match',
203
+ exactMatchHint: 'Exact phrase match',
204
+ clearSearch: 'Clear search',
205
+ clear: 'Clear',
206
+ searchIn: 'Search in:',
207
+ allProjects: 'All projects',
208
+ wholeProject: 'whole project',
209
+ folder: 'Folder',
210
+ folderPlaceholder: 'Folder (e.g. guides/getting-started)',
211
+ clearFolder: 'Clear folder',
212
+ pickFolderFromTree: 'Pick a folder from the tree',
213
+ folderTree: 'Folder tree',
214
+ folderPicker: 'Folder picker',
215
+ typeToSearch: 'Type to search',
216
+ loadingIndex: 'Loading index…',
217
+ noResultsFor: 'No results for',
218
+ cancel: 'Cancel',
219
+ ok: 'OK',
220
+ searchEllipsis: 'Search…',
221
+ folderIn: 'Folder in',
222
+ noSubfolders: 'This project has no nested folders',
223
+ groupNav: 'Navigation',
224
+ groupTree: 'Tree',
225
+ groupTabs: 'Tabs',
226
+ scSearchContent: 'Search content',
227
+ scShowShortcuts: 'Show keyboard shortcuts',
228
+ scSearchFile: 'Find a file by name',
229
+ scLocate: 'Locate current page in the tree',
230
+ scCollapseAll: 'Collapse all folders',
231
+ scCloseTab: 'Close current tab',
232
+ scShiftEnter: 'File — open in a new tab; folder — expand recursively',
233
+ noToc: 'No table of contents',
234
+ theme: 'Theme',
235
+ wrapLines: 'Wrap lines',
236
+ noWrapLines: 'Don’t wrap lines',
237
+ navigation: 'Navigation',
238
+ pageActions: 'Page actions',
239
+ scrollTop: 'Top',
240
+ findOnPage: 'Find on page',
241
+ findOnPagePlaceholder: 'Find on page…',
242
+ toc: 'Contents',
243
+ hide: 'Hide',
244
+ find: 'Find',
245
+ prevMatch: 'Previous match',
246
+ nextMatch: 'Next match',
247
+ closeFindOnPage: 'Close find on page',
248
+ closeTabHint: 'Close (W)',
249
+ tabActions: 'Tab actions',
250
+ closeAllTabs: 'Close all tabs',
114
251
  },
115
252
  }
116
253
 
@@ -14,13 +14,23 @@ import { useEffect } from 'react'
14
14
  * of "g then c").
15
15
  */
16
16
 
17
- export type ShortcutGroup = 'Дерево' | 'Вкладки' | 'Навигация'
17
+ /**
18
+ * Stable, language-neutral group keys. The display name is resolved via `t()`
19
+ * from the `group<Key>` UI strings (see defaults.ts) at render time, so the
20
+ * cheatsheet localizes with the rest of the chrome.
21
+ */
22
+ export type ShortcutGroup = 'tree' | 'tabs' | 'nav'
23
+
24
+ /** Map a group key to its UI-string key, for `t(groupLabelKey(group))`. */
25
+ export function groupLabelKey(group: ShortcutGroup): string {
26
+ return group === 'tree' ? 'groupTree' : group === 'tabs' ? 'groupTabs' : 'groupNav'
27
+ }
18
28
 
19
29
  /** A binding the caller wires to behavior, plus the metadata the `?` overlay shows. */
20
30
  export type Shortcut = {
21
31
  /** Single key (e.g. 'l') OR a two-key sequence (e.g. ['g', 'c']). Compared case-insensitively. */
22
32
  keys: string | [string, string]
23
- /** What it does shown in the cheatsheet. */
33
+ /** What it does. Live bindings aren't shown in the cheatsheet; this is dev-facing. */
24
34
  label: string
25
35
  /** Cheatsheet section. */
26
36
  group: ShortcutGroup
@@ -31,12 +41,14 @@ export type Shortcut = {
31
41
  /**
32
42
  * A display-only entry for shortcuts owned elsewhere (the existing Cmd+K / Cmd+P
33
43
  * chords, the Shift+Enter row action) so the `?` overlay can list everything in
34
- * one place without those handlers being re-registered here.
44
+ * one place without those handlers being re-registered here. `labelKey` is a UI
45
+ * string key resolved via `t()` at render.
35
46
  */
36
47
  export type ShortcutInfo = {
37
48
  /** Human-readable key hint, e.g. '⌘K', 'Shift+Enter'. */
38
49
  hint: string
39
- label: string
50
+ /** UI-string key for the description (resolved via `t()` in the overlay). */
51
+ labelKey: string
40
52
  group: ShortcutGroup
41
53
  }
42
54
 
@@ -48,13 +60,13 @@ export type ShortcutInfo = {
48
60
  * Keep in sync when adding a binding.
49
61
  */
50
62
  export const ALL_SHORTCUTS: ShortcutInfo[] = [
51
- { hint: '⌘/Ctrl K', label: 'Поиск по содержимому', group: 'Навигация' },
52
- { hint: '?', label: 'Показать список горячих клавиш', group: 'Навигация' },
53
- { hint: '⌘/Ctrl P', label: 'Поиск файла по имени', group: 'Дерево' },
54
- { hint: 'l', label: 'Найти текущую страницу в дереве', group: 'Дерево' },
55
- { hint: 'c', label: 'Свернуть все папки', group: 'Дерево' },
56
- { hint: 'w', label: 'Закрыть текущую вкладку', group: 'Вкладки' },
57
- { hint: 'Shift+Enter', label: 'Файл — открыть в новой вкладке; папку — развернуть рекурсивно', group: 'Дерево' },
63
+ { hint: '⌘/Ctrl K', labelKey: 'scSearchContent', group: 'nav' },
64
+ { hint: '?', labelKey: 'scShowShortcuts', group: 'nav' },
65
+ { hint: '⌘/Ctrl P', labelKey: 'scSearchFile', group: 'tree' },
66
+ { hint: 'l', labelKey: 'scLocate', group: 'tree' },
67
+ { hint: 'c', labelKey: 'scCollapseAll', group: 'tree' },
68
+ { hint: 'w', labelKey: 'scCloseTab', group: 'tabs' },
69
+ { hint: 'Shift+Enter', labelKey: 'scShiftEnter', group: 'tree' },
58
70
  ]
59
71
 
60
72
  /** True when focus is in an editable surface — single-key shortcuts must not fire there. */
@@ -1387,7 +1387,78 @@ var UI_STRINGS = {
1387
1387
  home: "\u0413\u043B\u0430\u0432\u043D\u0430\u044F",
1388
1388
  files: "\u0424\u0430\u0439\u043B\u044B",
1389
1389
  search: "\u041F\u043E\u0438\u0441\u043A",
1390
- toggleTheme: "\u041F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0442\u0435\u043C\u0443"
1390
+ toggleTheme: "\u041F\u0435\u0440\u0435\u043A\u043B\u044E\u0447\u0438\u0442\u044C \u0442\u0435\u043C\u0443",
1391
+ // File-search (Ctrl/Cmd+P) in the sidebar
1392
+ fileSearchPlaceholder: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0444\u0430\u0439\u043B\u0430\u043C...",
1393
+ fileSearch: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0444\u0430\u0439\u043B\u0430\u043C",
1394
+ fileSearchHint: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0444\u0430\u0439\u043B\u0430\u043C (Ctrl+P)",
1395
+ scopeAll: "\u0412\u0441\u0435",
1396
+ scopeFiles: "\u0424\u0430\u0439\u043B\u044B",
1397
+ scopeDirectories: "\u041F\u0430\u043F\u043A\u0438",
1398
+ nothingFound: "\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E",
1399
+ // Sidebar tree actions
1400
+ treeActions: "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F",
1401
+ resizePanel: "\u0418\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0448\u0438\u0440\u0438\u043D\u0443 \u043F\u0430\u043D\u0435\u043B\u0438",
1402
+ locateInTree: "\u041D\u0430\u0439\u0442\u0438 \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443 \u0432 \u0434\u0435\u0440\u0435\u0432\u0435",
1403
+ locateInTreeHint: "\u041D\u0430\u0439\u0442\u0438 \u0432 \u0434\u0435\u0440\u0435\u0432\u0435 (L)",
1404
+ collapseAll: "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u0441\u0435 \u043F\u0430\u043F\u043A\u0438",
1405
+ collapseAllHint: "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u0441\u0451 (C)",
1406
+ openInNewTab: "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u043A\u043B\u0430\u0434\u043A\u0435 (Shift+Enter)",
1407
+ expandRecursively: "\u0420\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0440\u0435\u043A\u0443\u0440\u0441\u0438\u0432\u043D\u043E (Shift+Enter)",
1408
+ // Content search (Ctrl/Cmd+K)
1409
+ searchDocs: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u0438",
1410
+ searchDocsPlaceholder: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u0446\u0438\u0438\u2026",
1411
+ exactMatch: "\u0422\u043E\u0447\u043D\u043E\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0435",
1412
+ exactMatchHint: "\u0422\u043E\u0447\u043D\u043E\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0435 \u0444\u0440\u0430\u0437\u044B",
1413
+ clearSearch: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u043F\u043E\u0438\u0441\u043A",
1414
+ clear: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C",
1415
+ searchIn: "\u0418\u0441\u043A\u0430\u0442\u044C \u0432:",
1416
+ allProjects: "\u0412\u0441\u0435 \u043F\u0440\u043E\u0435\u043A\u0442\u044B",
1417
+ wholeProject: "\u0432\u0435\u0441\u044C \u043F\u0440\u043E\u0435\u043A\u0442",
1418
+ folder: "\u041F\u0430\u043F\u043A\u0430",
1419
+ folderPlaceholder: "\u041F\u0430\u043F\u043A\u0430 (\u043D\u0430\u043F\u0440. \u0442\u0440\u0435\u0431\u043E\u0432\u0430\u043D\u0438\u044F/\u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u0435-\u043A\u043B\u0438\u0435\u043D\u0442\u0430\u043C\u0438)",
1420
+ clearFolder: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u043F\u0430\u043F\u043A\u0443",
1421
+ pickFolderFromTree: "\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u043F\u0430\u043F\u043A\u0443 \u0438\u0437 \u0434\u0435\u0440\u0435\u0432\u0430",
1422
+ folderTree: "\u0414\u0435\u0440\u0435\u0432\u043E \u043F\u0430\u043F\u043E\u043A",
1423
+ folderPicker: "\u0412\u044B\u0431\u043E\u0440 \u043F\u0430\u043F\u043A\u0438",
1424
+ typeToSearch: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0437\u0430\u043F\u0440\u043E\u0441 \u0434\u043B\u044F \u043F\u043E\u0438\u0441\u043A\u0430",
1425
+ loadingIndex: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0438\u043D\u0434\u0435\u043A\u0441\u0430\u2026",
1426
+ noResultsFor: "\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u043F\u043E \u0437\u0430\u043F\u0440\u043E\u0441\u0443",
1427
+ cancel: "\u041E\u0442\u043C\u0435\u043D\u0430",
1428
+ ok: "\u041E\u041A",
1429
+ searchEllipsis: "\u041F\u043E\u0438\u0441\u043A\u2026",
1430
+ folderIn: "\u041F\u0430\u043F\u043A\u0430 \u0432",
1431
+ noSubfolders: "\u0412 \u044D\u0442\u043E\u043C \u043F\u0440\u043E\u0435\u043A\u0442\u0435 \u043D\u0435\u0442 \u0432\u043B\u043E\u0436\u0435\u043D\u043D\u044B\u0445 \u043F\u0430\u043F\u043E\u043A",
1432
+ // Keyboard-shortcut cheatsheet: group display names + per-shortcut labels.
1433
+ groupNav: "\u041D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F",
1434
+ groupTree: "\u0414\u0435\u0440\u0435\u0432\u043E",
1435
+ groupTabs: "\u0412\u043A\u043B\u0430\u0434\u043A\u0438",
1436
+ scSearchContent: "\u041F\u043E\u0438\u0441\u043A \u043F\u043E \u0441\u043E\u0434\u0435\u0440\u0436\u0438\u043C\u043E\u043C\u0443",
1437
+ scShowShortcuts: "\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u0441\u043F\u0438\u0441\u043E\u043A \u0433\u043E\u0440\u044F\u0447\u0438\u0445 \u043A\u043B\u0430\u0432\u0438\u0448",
1438
+ scSearchFile: "\u041F\u043E\u0438\u0441\u043A \u0444\u0430\u0439\u043B\u0430 \u043F\u043E \u0438\u043C\u0435\u043D\u0438",
1439
+ scLocate: "\u041D\u0430\u0439\u0442\u0438 \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0443 \u0432 \u0434\u0435\u0440\u0435\u0432\u0435",
1440
+ scCollapseAll: "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0432\u0441\u0435 \u043F\u0430\u043F\u043A\u0438",
1441
+ scCloseTab: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C \u0442\u0435\u043A\u0443\u0449\u0443\u044E \u0432\u043A\u043B\u0430\u0434\u043A\u0443",
1442
+ scShiftEnter: "\u0424\u0430\u0439\u043B \u2014 \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u043A\u043B\u0430\u0434\u043A\u0435; \u043F\u0430\u043F\u043A\u0443 \u2014 \u0440\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u0442\u044C \u0440\u0435\u043A\u0443\u0440\u0441\u0438\u0432\u043D\u043E",
1443
+ // Misc chrome (Toc, mobile menus, code-wrap, find-on-page, tabs)
1444
+ noToc: "\u041D\u0435\u0442 \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u044F",
1445
+ theme: "\u0422\u0435\u043C\u0430 \u043E\u0444\u043E\u0440\u043C\u043B\u0435\u043D\u0438\u044F",
1446
+ wrapLines: "\u041F\u0435\u0440\u0435\u043D\u043E\u0441\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0438",
1447
+ noWrapLines: "\u041D\u0435 \u043F\u0435\u0440\u0435\u043D\u043E\u0441\u0438\u0442\u044C \u0441\u0442\u0440\u043E\u043A\u0438",
1448
+ navigation: "\u041D\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044F",
1449
+ pageActions: "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B",
1450
+ scrollTop: "\u041D\u0430\u0432\u0435\u0440\u0445",
1451
+ findOnPage: "\u041D\u0430\u0439\u0442\u0438 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435",
1452
+ findOnPagePlaceholder: "\u041D\u0430\u0439\u0442\u0438 \u043D\u0430 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435\u2026",
1453
+ toc: "\u0421\u043E\u0434\u0435\u0440\u0436\u0430\u043D\u0438\u0435",
1454
+ hide: "\u0421\u043A\u0440\u044B\u0442\u044C",
1455
+ find: "\u041D\u0430\u0439\u0442\u0438",
1456
+ prevMatch: "\u041F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0435\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0435",
1457
+ nextMatch: "\u0421\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u0435 \u0441\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0435",
1458
+ closeFindOnPage: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C \u043F\u043E\u0438\u0441\u043A \u043F\u043E \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435",
1459
+ closeTabHint: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C (W)",
1460
+ tabActions: "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044F \u0441 \u0432\u043A\u043B\u0430\u0434\u043A\u0430\u043C\u0438",
1461
+ closeAllTabs: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C \u0432\u0441\u0435 \u0432\u043A\u043B\u0430\u0434\u043A\u0438"
1391
1462
  },
1392
1463
  en: {
1393
1464
  projects: "Projects",
@@ -1404,7 +1475,73 @@ var UI_STRINGS = {
1404
1475
  home: "Home",
1405
1476
  files: "Files",
1406
1477
  search: "Search",
1407
- toggleTheme: "Toggle theme"
1478
+ toggleTheme: "Toggle theme",
1479
+ fileSearchPlaceholder: "Search files...",
1480
+ fileSearch: "Search files",
1481
+ fileSearchHint: "Search files (Ctrl+P)",
1482
+ scopeAll: "All",
1483
+ scopeFiles: "Files",
1484
+ scopeDirectories: "Folders",
1485
+ nothingFound: "Nothing found",
1486
+ treeActions: "Actions",
1487
+ resizePanel: "Resize panel",
1488
+ locateInTree: "Locate current page in the tree",
1489
+ locateInTreeHint: "Locate in tree (L)",
1490
+ collapseAll: "Collapse all folders",
1491
+ collapseAllHint: "Collapse all (C)",
1492
+ openInNewTab: "Open in a new tab (Shift+Enter)",
1493
+ expandRecursively: "Expand recursively (Shift+Enter)",
1494
+ searchDocs: "Search the docs",
1495
+ searchDocsPlaceholder: "Search the docs\u2026",
1496
+ exactMatch: "Exact match",
1497
+ exactMatchHint: "Exact phrase match",
1498
+ clearSearch: "Clear search",
1499
+ clear: "Clear",
1500
+ searchIn: "Search in:",
1501
+ allProjects: "All projects",
1502
+ wholeProject: "whole project",
1503
+ folder: "Folder",
1504
+ folderPlaceholder: "Folder (e.g. guides/getting-started)",
1505
+ clearFolder: "Clear folder",
1506
+ pickFolderFromTree: "Pick a folder from the tree",
1507
+ folderTree: "Folder tree",
1508
+ folderPicker: "Folder picker",
1509
+ typeToSearch: "Type to search",
1510
+ loadingIndex: "Loading index\u2026",
1511
+ noResultsFor: "No results for",
1512
+ cancel: "Cancel",
1513
+ ok: "OK",
1514
+ searchEllipsis: "Search\u2026",
1515
+ folderIn: "Folder in",
1516
+ noSubfolders: "This project has no nested folders",
1517
+ groupNav: "Navigation",
1518
+ groupTree: "Tree",
1519
+ groupTabs: "Tabs",
1520
+ scSearchContent: "Search content",
1521
+ scShowShortcuts: "Show keyboard shortcuts",
1522
+ scSearchFile: "Find a file by name",
1523
+ scLocate: "Locate current page in the tree",
1524
+ scCollapseAll: "Collapse all folders",
1525
+ scCloseTab: "Close current tab",
1526
+ scShiftEnter: "File \u2014 open in a new tab; folder \u2014 expand recursively",
1527
+ noToc: "No table of contents",
1528
+ theme: "Theme",
1529
+ wrapLines: "Wrap lines",
1530
+ noWrapLines: "Don\u2019t wrap lines",
1531
+ navigation: "Navigation",
1532
+ pageActions: "Page actions",
1533
+ scrollTop: "Top",
1534
+ findOnPage: "Find on page",
1535
+ findOnPagePlaceholder: "Find on page\u2026",
1536
+ toc: "Contents",
1537
+ hide: "Hide",
1538
+ find: "Find",
1539
+ prevMatch: "Previous match",
1540
+ nextMatch: "Next match",
1541
+ closeFindOnPage: "Close find on page",
1542
+ closeTabHint: "Close (W)",
1543
+ tabActions: "Tab actions",
1544
+ closeAllTabs: "Close all tabs"
1408
1545
  }
1409
1546
  };
1410
1547
  function defaultUiFor(lang) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cantip",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "description": "Config-driven Remix documentation site engine — ingest Obsidian/markdown vaults, build an SSR docs site. \"How (to)\" in Kyrgyz.",
6
6
  "keywords": [
7
7
  "documentation",