@opencode/ui 2.0.22 → 2.0.24
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.
- package/dist/data-display/accordion/accordion.d.ts +3 -1
- package/dist/feedback/toast/toast.d.ts +3 -2
- package/dist/i18n/en.d.ts +5 -0
- package/dist/icons/catalog.d.ts +257 -0
- package/dist/icons/icon/additional-icons.d.ts +1 -0
- package/dist/icons/icon/icon.d.ts +1 -237
- package/package.json +5 -1
- package/src/actions/button/button.tsx +1 -0
- package/src/actions/icon-button/icon-button.tsx +1 -0
- package/src/actions/split-button/split-button.tsx +3 -0
- package/src/components/animated-number.tsx +13 -0
- package/src/components/app-icon.tsx +2 -0
- package/src/components/card.tsx +21 -0
- package/src/components/collapsible.tsx +1 -0
- package/src/components/context-menu.tsx +34 -0
- package/src/components/dock-surface.tsx +3 -0
- package/src/components/file-icon.tsx +12 -0
- package/src/components/image-preview.tsx +1 -0
- package/src/components/list.tsx +32 -0
- package/src/components/motion-spring.tsx +5 -0
- package/src/components/popover.tsx +12 -0
- package/src/components/provider-icon.tsx +1 -0
- package/src/components/resize-handle.tsx +8 -0
- package/src/components/scroll-view.tsx +39 -0
- package/src/components/spinner.tsx +2 -0
- package/src/components/text-field.tsx +6 -0
- package/src/components/text-reveal.tsx +19 -0
- package/src/components/text-strikethrough.tsx +8 -0
- package/src/components/typewriter.tsx +4 -0
- package/src/context/dialog.tsx +12 -0
- package/src/context/file.tsx +1 -0
- package/src/context/helper.tsx +4 -0
- package/src/context/i18n.tsx +20 -0
- package/src/context/index.ts +3 -0
- package/src/context/marked-base.tsx +7 -0
- package/src/context/marked-parser.tsx +7 -0
- package/src/context/marked.tsx +3 -0
- package/src/context/worker-pool.tsx +2 -0
- package/src/data-display/accordion/accordion.tsx +16 -2
- package/src/data-display/avatar/avatar.tsx +4 -0
- package/src/data-display/badge/badge.tsx +1 -0
- package/src/data-display/diff-changes/diff-changes.tsx +2 -0
- package/src/data-display/keybind/keybind.tsx +1 -0
- package/src/data-display/line-comment/line-comment.tsx +22 -0
- package/src/data-display/project-avatar/project-avatar.tsx +10 -0
- package/src/feedback/loader/loader.tsx +1 -0
- package/src/feedback/toast/description.ts +1 -0
- package/src/feedback/toast/toast.tsx +32 -6
- package/src/forms/checkbox/checkbox.tsx +1 -0
- package/src/forms/field/field.tsx +8 -0
- package/src/forms/inline-input/inline-input.tsx +4 -0
- package/src/forms/radio/radio.tsx +2 -0
- package/src/forms/select/select.tsx +11 -0
- package/src/forms/switch/switch.tsx +1 -0
- package/src/forms/text-input/text-input.tsx +3 -0
- package/src/hooks/create-auto-scroll.tsx +34 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-filtered-list.tsx +14 -0
- package/src/i18n/en.ts +10 -0
- package/src/i18n/no.ts +1 -0
- package/src/icons/catalog.ts +273 -0
- package/src/icons/icon/additional-icons.ts +1 -0
- package/src/icons/icon/icon.tsx +10 -250
- package/src/layout/divider/divider.tsx +1 -0
- package/src/navigation/menu/menu.tsx +13 -0
- package/src/navigation/segmented-control/segmented-control.tsx +16 -0
- package/src/navigation/tab-state-indicator.tsx +1 -0
- package/src/navigation/tabs/tabs.css +6 -72
- package/src/navigation/tabs/tabs.tsx +18 -0
- package/src/overlays/dialog/dialog.tsx +3 -0
- package/src/overlays/tooltip/tooltip.tsx +15 -0
- package/src/storybook/scaffold.tsx +3 -0
- package/src/styles/theme.css +1 -1
- package/src/theme/color.ts +27 -0
- package/src/theme/context.tsx +58 -0
- package/src/theme/default-themes.ts +35 -0
- package/src/theme/index.ts +3 -0
- package/src/theme/loader.ts +11 -0
- package/src/theme/resolve.ts +28 -0
- package/src/theme/v2/foreground.ts +8 -0
- package/src/theme/v2/resolve.ts +10 -0
- package/src/typography/text-shimmer/text-shimmer.tsx +1 -0
|
@@ -17,13 +17,17 @@ function groupOptions<T>(options: T[], groupBy?: (x: T) => string): { category:
|
|
|
17
17
|
if (!groupBy) {
|
|
18
18
|
return [{ category: "", options }]
|
|
19
19
|
}
|
|
20
|
+
|
|
20
21
|
const map = new Map<string, T[]>()
|
|
22
|
+
|
|
21
23
|
for (const opt of options) {
|
|
22
24
|
const key = groupBy(opt)
|
|
23
25
|
const arr = map.get(key)
|
|
26
|
+
|
|
24
27
|
if (arr) arr.push(opt)
|
|
25
28
|
else map.set(key, [opt])
|
|
26
29
|
}
|
|
30
|
+
|
|
27
31
|
return [...map.entries()].map(([category, opts]) => ({ category, options: opts }))
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -110,11 +114,15 @@ export function Select<T>(props: SelectProps<T>) {
|
|
|
110
114
|
|
|
111
115
|
const move = (item: T | undefined) => {
|
|
112
116
|
if (!local.onHighlight) return
|
|
117
|
+
|
|
113
118
|
if (!item) {
|
|
114
119
|
stop()
|
|
120
|
+
|
|
115
121
|
return
|
|
116
122
|
}
|
|
123
|
+
|
|
117
124
|
const key = keyFor(item)
|
|
125
|
+
|
|
118
126
|
if (state.key === key) return
|
|
119
127
|
state.cleanup?.()
|
|
120
128
|
state.cleanup = local.onHighlight(item)
|
|
@@ -178,6 +186,7 @@ export function Select<T>(props: SelectProps<T>) {
|
|
|
178
186
|
}}
|
|
179
187
|
onOpenChange={(open) => {
|
|
180
188
|
local.onOpenChange?.(open)
|
|
189
|
+
|
|
181
190
|
if (!open) stop()
|
|
182
191
|
}}
|
|
183
192
|
>
|
|
@@ -200,7 +209,9 @@ export function Select<T>(props: SelectProps<T>) {
|
|
|
200
209
|
<Value<T> data-slot="select-v2-value-text" class={local.valueClass}>
|
|
201
210
|
{(st) => {
|
|
202
211
|
const selected = st.selectedOption()
|
|
212
|
+
|
|
203
213
|
if (local.label && selected != null) return local.label(selected)
|
|
214
|
+
|
|
204
215
|
return selected != null ? (selected as string) : ""
|
|
205
216
|
}}
|
|
206
217
|
</Value>
|
|
@@ -12,6 +12,7 @@ export interface SwitchProps extends ParentProps<ComponentProps<typeof Root>> {
|
|
|
12
12
|
|
|
13
13
|
export function Switch(props: SwitchProps) {
|
|
14
14
|
const [local, others] = splitProps(props, ["children", "class", "appearance", "hideLabel", "description"])
|
|
15
|
+
|
|
15
16
|
return (
|
|
16
17
|
<Root {...others} class={local.class} data-component="switch" data-appearance={local.appearance ?? "compact"}>
|
|
17
18
|
<Input data-slot="switch-input" />
|
|
@@ -29,6 +29,7 @@ export interface TextInputProps extends Omit<ComponentProps<"input">, "type"> {
|
|
|
29
29
|
|
|
30
30
|
export function TextInput(props: TextInputProps) {
|
|
31
31
|
const i18n = useI18n()
|
|
32
|
+
|
|
32
33
|
const [local, inputProps] = splitProps(props, [
|
|
33
34
|
"class",
|
|
34
35
|
"classList",
|
|
@@ -89,8 +90,10 @@ export function TextInput(props: TextInputProps) {
|
|
|
89
90
|
onClick={(event) => {
|
|
90
91
|
if (local.showClearButton) {
|
|
91
92
|
local.onClearClick?.(event)
|
|
93
|
+
|
|
92
94
|
return
|
|
93
95
|
}
|
|
96
|
+
|
|
94
97
|
local.onCopyClick?.(event)
|
|
95
98
|
}}
|
|
96
99
|
>
|
|
@@ -53,10 +53,12 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
53
53
|
|
|
54
54
|
const isAuto = (el: HTMLElement) => {
|
|
55
55
|
const a = auto
|
|
56
|
+
|
|
56
57
|
if (!a) return false
|
|
57
58
|
|
|
58
59
|
if (Date.now() - a.time > 1500) {
|
|
59
60
|
auto = undefined
|
|
61
|
+
|
|
60
62
|
return false
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -65,10 +67,13 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
65
67
|
|
|
66
68
|
const scrollToBottomNow = (behavior: ScrollBehavior) => {
|
|
67
69
|
const el = store.scrollRef
|
|
70
|
+
|
|
68
71
|
if (!el) return
|
|
69
72
|
markAuto(el)
|
|
73
|
+
|
|
70
74
|
if (behavior === "smooth") {
|
|
71
75
|
el.scrollTo({ top: el.scrollHeight, behavior })
|
|
76
|
+
|
|
72
77
|
return
|
|
73
78
|
}
|
|
74
79
|
|
|
@@ -82,13 +87,16 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
82
87
|
if (force && store.userScrolled) setStore("userScrolled", false)
|
|
83
88
|
|
|
84
89
|
const el = store.scrollRef
|
|
90
|
+
|
|
85
91
|
if (!el) return
|
|
86
92
|
|
|
87
93
|
if (!force && store.userScrolled) return
|
|
88
94
|
|
|
89
95
|
const distance = distanceFromBottom(el)
|
|
96
|
+
|
|
90
97
|
if (distance < 2) {
|
|
91
98
|
markAuto(el)
|
|
99
|
+
|
|
92
100
|
return
|
|
93
101
|
}
|
|
94
102
|
|
|
@@ -99,11 +107,15 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
99
107
|
|
|
100
108
|
const stop = () => {
|
|
101
109
|
const el = store.scrollRef
|
|
110
|
+
|
|
102
111
|
if (!el) return
|
|
112
|
+
|
|
103
113
|
if (!canScroll(el)) {
|
|
104
114
|
if (store.userScrolled) setStore("userScrolled", false)
|
|
115
|
+
|
|
105
116
|
return
|
|
106
117
|
}
|
|
118
|
+
|
|
107
119
|
if (store.userScrolled) return
|
|
108
120
|
|
|
109
121
|
setStore("userScrolled", true)
|
|
@@ -118,27 +130,32 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
118
130
|
const el = store.scrollRef
|
|
119
131
|
const target = e.target instanceof Element ? e.target : undefined
|
|
120
132
|
const nested = target?.closest("[data-scrollable]")
|
|
133
|
+
|
|
121
134
|
if (el && nested && nested !== el) return
|
|
122
135
|
stop()
|
|
123
136
|
}
|
|
124
137
|
|
|
125
138
|
const handleScroll = () => {
|
|
126
139
|
const el = store.scrollRef
|
|
140
|
+
|
|
127
141
|
if (!el) return
|
|
128
142
|
|
|
129
143
|
if (!canScroll(el)) {
|
|
130
144
|
if (store.userScrolled) setStore("userScrolled", false)
|
|
145
|
+
|
|
131
146
|
return
|
|
132
147
|
}
|
|
133
148
|
|
|
134
149
|
if (distanceFromBottom(el) < threshold()) {
|
|
135
150
|
if (store.userScrolled) setStore("userScrolled", false)
|
|
151
|
+
|
|
136
152
|
return
|
|
137
153
|
}
|
|
138
154
|
|
|
139
155
|
// Ignore scroll events triggered by our own scrollToBottom calls.
|
|
140
156
|
if (!store.userScrolled && isAuto(el)) {
|
|
141
157
|
scrollToBottom(false)
|
|
158
|
+
|
|
142
159
|
return
|
|
143
160
|
}
|
|
144
161
|
|
|
@@ -148,6 +165,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
148
165
|
const handleInteraction = () => {
|
|
149
166
|
if (!active()) return
|
|
150
167
|
const selection = window.getSelection()
|
|
168
|
+
|
|
151
169
|
if (selection && selection.toString().length > 0) {
|
|
152
170
|
stop()
|
|
153
171
|
}
|
|
@@ -158,11 +176,13 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
158
176
|
|
|
159
177
|
if (mode === "none") {
|
|
160
178
|
el.style.overflowAnchor = "none"
|
|
179
|
+
|
|
161
180
|
return
|
|
162
181
|
}
|
|
163
182
|
|
|
164
183
|
if (mode === "auto") {
|
|
165
184
|
el.style.overflowAnchor = "auto"
|
|
185
|
+
|
|
166
186
|
return
|
|
167
187
|
}
|
|
168
188
|
|
|
@@ -173,11 +193,15 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
173
193
|
() => store.contentRef,
|
|
174
194
|
() => {
|
|
175
195
|
const el = store.scrollRef
|
|
196
|
+
|
|
176
197
|
if (el && !canScroll(el)) {
|
|
177
198
|
if (store.userScrolled) setStore("userScrolled", false)
|
|
199
|
+
|
|
178
200
|
return
|
|
179
201
|
}
|
|
202
|
+
|
|
180
203
|
if (!active()) return
|
|
204
|
+
|
|
181
205
|
if (store.userScrolled) return
|
|
182
206
|
// ResizeObserver fires after layout, before paint.
|
|
183
207
|
// Keep the bottom locked in the same frame to avoid visible
|
|
@@ -188,20 +212,26 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
188
212
|
|
|
189
213
|
createEffect(() => {
|
|
190
214
|
const el = store.scrollRef
|
|
215
|
+
|
|
191
216
|
if (!el) return
|
|
192
217
|
const root = el.closest("main")
|
|
218
|
+
|
|
193
219
|
if (!root) return
|
|
194
220
|
let connected = el.isConnected
|
|
195
221
|
const contains = (node: Node) => node === el || (node instanceof Element && node.contains(el))
|
|
222
|
+
|
|
196
223
|
const observer = new MutationObserver((records) => {
|
|
197
224
|
records.forEach((record) => {
|
|
198
225
|
if ([...record.removedNodes].some(contains)) connected = false
|
|
226
|
+
|
|
199
227
|
if (!record.addedNodes.length || ![...record.addedNodes].some(contains)) return
|
|
200
228
|
const reattached = !connected
|
|
201
229
|
connected = true
|
|
230
|
+
|
|
202
231
|
if (reattached) scrollToBottom(false)
|
|
203
232
|
})
|
|
204
233
|
})
|
|
234
|
+
|
|
205
235
|
observer.observe(root, { childList: true })
|
|
206
236
|
onCleanup(() => observer.disconnect())
|
|
207
237
|
})
|
|
@@ -209,11 +239,13 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
209
239
|
createEffect(
|
|
210
240
|
on(options.working, (working: boolean) => {
|
|
211
241
|
settling = false
|
|
242
|
+
|
|
212
243
|
if (settleTimer) clearTimeout(settleTimer)
|
|
213
244
|
settleTimer = undefined
|
|
214
245
|
|
|
215
246
|
if (working) {
|
|
216
247
|
if (!store.userScrolled) scrollToBottom(true)
|
|
248
|
+
|
|
217
249
|
return
|
|
218
250
|
}
|
|
219
251
|
|
|
@@ -229,6 +261,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
229
261
|
// update overflow anchoring once the element exists.
|
|
230
262
|
store.userScrolled
|
|
231
263
|
const el = store.scrollRef
|
|
264
|
+
|
|
232
265
|
if (!el) return
|
|
233
266
|
updateOverflowAnchor(el)
|
|
234
267
|
})
|
|
@@ -237,6 +270,7 @@ export function createAutoScroll(options: AutoScrollOptions) {
|
|
|
237
270
|
|
|
238
271
|
onCleanup(() => {
|
|
239
272
|
if (settleTimer) clearTimeout(settleTimer)
|
|
273
|
+
|
|
240
274
|
if (autoTimer) clearTimeout(autoTimer)
|
|
241
275
|
})
|
|
242
276
|
|
package/src/hooks/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
21
21
|
const [store, setStore] = createStore<{ filter: string }>({ filter: "" })
|
|
22
22
|
|
|
23
23
|
type Group = { category: string; items: [T, ...T[]] }
|
|
24
|
+
|
|
24
25
|
const empty: Group[] = []
|
|
25
26
|
|
|
26
27
|
const [grouped, { refetch }] = createResource(
|
|
@@ -32,6 +33,7 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
32
33
|
const query = filter ?? ""
|
|
33
34
|
const needle = query.toLowerCase()
|
|
34
35
|
const all = (await Promise.resolve(items)) || []
|
|
36
|
+
|
|
35
37
|
const result = pipe(
|
|
36
38
|
all,
|
|
37
39
|
(x) => {
|
|
@@ -39,10 +41,12 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
39
41
|
const skipFilter = props.skipFilter
|
|
40
42
|
const filterable = skipFilter ? x.filter((item) => !skipFilter(item)) : x
|
|
41
43
|
const skipped = skipFilter ? x.filter(skipFilter) : []
|
|
44
|
+
|
|
42
45
|
const filtered =
|
|
43
46
|
!props.filterKeys && Array.isArray(filterable) && filterable.every((e) => typeof e === "string")
|
|
44
47
|
? (fuzzysort.go(needle, filterable).map((x) => x.target) as T[])
|
|
45
48
|
: fuzzysort.go(needle, filterable, { keys: props.filterKeys! }).map((x) => x.obj)
|
|
49
|
+
|
|
46
50
|
return skipped.length ? [...filtered, ...skipped] : filtered
|
|
47
51
|
},
|
|
48
52
|
groupBy((x) => (props.groupBy ? props.groupBy(x) : "")),
|
|
@@ -50,6 +54,7 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
50
54
|
map(([k, v]) => ({ category: k, items: props.sortBy ? v.sort(props.sortBy) : v })),
|
|
51
55
|
(groups) => (props.sortGroupsBy ? groups.sort(props.sortGroupsBy) : groups),
|
|
52
56
|
)
|
|
57
|
+
|
|
53
58
|
return result
|
|
54
59
|
},
|
|
55
60
|
{ initialValue: empty },
|
|
@@ -64,10 +69,13 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
64
69
|
|
|
65
70
|
function initialActive() {
|
|
66
71
|
if (props.noInitialSelection) return ""
|
|
72
|
+
|
|
67
73
|
if (props.current) return props.key(props.current)
|
|
68
74
|
|
|
69
75
|
const items = flat()
|
|
76
|
+
|
|
70
77
|
if (items.length === 0) return ""
|
|
78
|
+
|
|
71
79
|
return props.key(items[0])
|
|
72
80
|
}
|
|
73
81
|
|
|
@@ -80,9 +88,12 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
80
88
|
const reset = () => {
|
|
81
89
|
if (props.noInitialSelection) {
|
|
82
90
|
list.setActive("")
|
|
91
|
+
|
|
83
92
|
return
|
|
84
93
|
}
|
|
94
|
+
|
|
85
95
|
const all = flat()
|
|
96
|
+
|
|
86
97
|
if (all.length === 0) return
|
|
87
98
|
list.setActive(props.key(all[0]))
|
|
88
99
|
}
|
|
@@ -92,14 +103,17 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
|
|
|
92
103
|
event.preventDefault()
|
|
93
104
|
const selectedIndex = flat().findIndex((x) => props.key(x) === list.active())
|
|
94
105
|
const selected = flat()[selectedIndex]
|
|
106
|
+
|
|
95
107
|
if (selected) props.onSelect?.(selected, selectedIndex)
|
|
96
108
|
} else if (event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
|
|
97
109
|
if (event.key === "n" || event.key === "p") {
|
|
98
110
|
event.preventDefault()
|
|
111
|
+
|
|
99
112
|
const navEvent = new KeyboardEvent("keydown", {
|
|
100
113
|
key: event.key === "n" ? "ArrowDown" : "ArrowUp",
|
|
101
114
|
bubbles: true,
|
|
102
115
|
})
|
|
116
|
+
|
|
103
117
|
list.onKeyDown(navEvent)
|
|
104
118
|
}
|
|
105
119
|
} else {
|
package/src/i18n/en.ts
CHANGED
|
@@ -108,6 +108,7 @@ const source = {
|
|
|
108
108
|
"ui.messagePart.questions.dismissed": "Questions dismissed",
|
|
109
109
|
"ui.messagePart.compaction": "Session compacted",
|
|
110
110
|
"ui.messagePart.compaction.started": "Session compaction started",
|
|
111
|
+
"ui.messagePart.compaction.queued": "Session compaction queued",
|
|
111
112
|
"ui.messagePart.compaction.running": "Compacting",
|
|
112
113
|
"ui.messagePart.compaction.failed": "Session compaction failed",
|
|
113
114
|
"ui.messagePart.compaction.cancelled": "Session compaction cancelled",
|
|
@@ -242,12 +243,16 @@ const source = {
|
|
|
242
243
|
"ui.message.copyMessage": "Copy message",
|
|
243
244
|
"ui.message.forkMessage": "Fork to new session",
|
|
244
245
|
"ui.message.revertMessage": "Revert message",
|
|
246
|
+
"ui.message.moveToQueue": "Move to queue",
|
|
247
|
+
"ui.message.pending": "Pending",
|
|
248
|
+
"ui.message.deletePending": "Delete",
|
|
245
249
|
"ui.message.copyResponse": "Copy response",
|
|
246
250
|
"ui.message.copied": "Copied",
|
|
247
251
|
"ui.message.thought": "Thought",
|
|
248
252
|
"ui.message.duration.seconds": "{{count}}s",
|
|
249
253
|
"ui.message.duration.minutesSeconds": "{{minutes}}m {{seconds}}s",
|
|
250
254
|
"ui.message.interrupted": "Interrupted",
|
|
255
|
+
"ui.message.modelVariant": "{{model}} ({{variant}})",
|
|
251
256
|
"ui.sessionTimeline.notice.model": "Model",
|
|
252
257
|
"ui.sessionTimeline.notice.modelSwitched": "Switched to {{model}}",
|
|
253
258
|
"ui.sessionTimeline.notice.agentChanged": "Agent changed",
|
|
@@ -275,10 +280,15 @@ const source = {
|
|
|
275
280
|
} satisfies Record<string, string>
|
|
276
281
|
|
|
277
282
|
export type Key = keyof typeof source
|
|
283
|
+
|
|
278
284
|
export type PluralCategory = "zero" | "one" | "two" | "few" | "many" | "other"
|
|
285
|
+
|
|
279
286
|
export type PluralKey = {
|
|
280
287
|
[Entry in Key]: Entry extends `${infer Base}.other` ? (`${Base}.one` extends Key ? Base : never) : never
|
|
281
288
|
}[Key]
|
|
289
|
+
|
|
282
290
|
export type PluralLookupKey = `${PluralKey}.${PluralCategory}`
|
|
291
|
+
|
|
283
292
|
export type LocaleKey = Key | PluralLookupKey
|
|
293
|
+
|
|
284
294
|
export const dict: typeof source & Record<string, string> = source
|