@spaethtech/svelte-ui 0.17.1-dev.84.05ecf9b → 0.18.0
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/.claude/skills/svelte-ui/SKILL.md +34 -0
- package/CLAUDE.md +11 -0
- package/dist/components/Badge/Badge.svelte +3 -1
- package/dist/components/Banner.svelte +3 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.svelte +4 -2
- package/dist/components/ButtonGroup/ButtonGroup.svelte +3 -1
- package/dist/components/Calendar.svelte +5 -3
- package/dist/components/Carousel/Carousel.svelte +4 -2
- package/dist/components/CommandPalette/CommandPalette.svelte +3 -1
- package/dist/components/DataTable.svelte +19 -15
- package/dist/components/DatePicker.svelte +8 -6
- package/dist/components/FileUpload/FileUpload.svelte +5 -3
- package/dist/components/List.svelte +4 -2
- package/dist/components/NumberInput.svelte +6 -4
- package/dist/components/Pagination/Pagination.svelte +10 -6
- package/dist/components/Pagination/Pagination.svelte.d.ts +1 -1
- package/dist/components/Query.svelte +11 -9
- package/dist/components/Stepper/Stepper.svelte +3 -1
- package/dist/components/TextArea.svelte +5 -3
- package/dist/components/ThemeSelector.svelte +3 -1
- package/dist/components/TimeRangeInput.svelte +4 -2
- package/dist/components/Toast/Toaster.svelte +3 -1
- package/dist/i18n.d.ts +88 -0
- package/dist/i18n.js +84 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
|
@@ -55,6 +55,40 @@ the components use:
|
|
|
55
55
|
<Button text="Save" variant="primary" onclick={() => save(name)} />
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
## Localization (i18n) — opt-in
|
|
59
|
+
|
|
60
|
+
Components render their own microcopy (aria-labels, titles, "per page", "No results", month names…)
|
|
61
|
+
through a message registry with **English defaults**. Do nothing → identical English output. To
|
|
62
|
+
localize, call **`setUiI18n`** ONCE near the app root (a layout); partial `messages` merge over the
|
|
63
|
+
defaults, so translate a subset and the rest stays English. `locale` also feeds the date components'
|
|
64
|
+
`Intl.*` formatting (falls back to `navigator.language` when unset; a per-instance `locale` prop still
|
|
65
|
+
overrides).
|
|
66
|
+
|
|
67
|
+
```svelte
|
|
68
|
+
<script>
|
|
69
|
+
import { setUiI18n } from "@spaethtech/svelte-ui";
|
|
70
|
+
import { getLocale } from "$lib/paraglide/runtime";
|
|
71
|
+
import { m } from "$lib/paraglide/messages";
|
|
72
|
+
setUiI18n({
|
|
73
|
+
locale: () => getLocale(), // getter ⇒ reactive; or a plain string
|
|
74
|
+
messages: () => ({
|
|
75
|
+
dismiss: m.ui_dismiss(),
|
|
76
|
+
selectAll: m.ui_select_all(),
|
|
77
|
+
rowRange: ({ start, end, total }) => m.ui_row_range({ start, end, total }), // "1–10 of 247"
|
|
78
|
+
removeFile: ({ name }) => m.ui_remove_file({ name }),
|
|
79
|
+
// …any subset of `UiMessages`
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
</script>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Parameterized entries are functions (`rowRange`, `removeFile`) — 1:1 with Paraglide's `m.key(params)`.
|
|
86
|
+
Values may be plain or getters (getters make components re-render on locale change; plain is fine for
|
|
87
|
+
per-navigation locale). Full key list: the `UiMessages` type; `defaultUiMessages` is the English source.
|
|
88
|
+
Strings already exposed as PROPS (`Select placeholder`, `EmptyState title`, `CommandPalette empty`…) you
|
|
89
|
+
localize directly on the component. **Authoring rule (this repo):** every new user-facing string a
|
|
90
|
+
component renders must go through `uiI18n().messages` + `defaultUiMessages`, never a bare literal.
|
|
91
|
+
|
|
58
92
|
## The two shared axes (mirror each other)
|
|
59
93
|
|
|
60
94
|
- **`variant`** — color/style: `primary` `secondary` `danger` `info` `success` `warning` `ghost`
|
package/CLAUDE.md
CHANGED
|
@@ -172,6 +172,17 @@ Two shared **cross-cutting axes** every sizeable/themeable component consumes (m
|
|
|
172
172
|
|
|
173
173
|
## Component Guidelines
|
|
174
174
|
|
|
175
|
+
### Localization (i18n)
|
|
176
|
+
|
|
177
|
+
Every user-facing string a component renders itself (aria-label, title, placeholder, fixed visible text,
|
|
178
|
+
"per page"/"No results"/month names…) MUST go through the message registry, never a bare literal:
|
|
179
|
+
add a key to `UiMessages` + `defaultUiMessages` in `src/lib/i18n.ts` (English default = the exact old
|
|
180
|
+
literal, so output stays byte-identical), then render `i18n.messages.<key>` (`const i18n = uiI18n()`).
|
|
181
|
+
Parameterized strings are functions (`rowRange({start,end,total})`) — never concatenate. Date/number
|
|
182
|
+
formatting reads `i18n.locale` (fallback `navigator.language`; a per-instance `locale` prop overrides).
|
|
183
|
+
Strings already exposed as a component PROP are localized by the consumer — leave those. A hard-coded
|
|
184
|
+
user-facing literal in a component is a bug (it's invisible to `svelte-check` and to i18n consumers).
|
|
185
|
+
|
|
175
186
|
### Accessibility
|
|
176
187
|
|
|
177
188
|
- All interactive components need keyboard support
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { uiI18n } from "../../i18n.js";
|
|
3
|
+
const i18n = uiI18n();
|
|
2
4
|
import type { Snippet } from "svelte";
|
|
3
5
|
import Close from "~icons/mdi/close";
|
|
4
6
|
import type { Size } from "../../types/sizes.js";
|
|
@@ -143,7 +145,7 @@
|
|
|
143
145
|
type="button"
|
|
144
146
|
onclick={handleDismiss}
|
|
145
147
|
onkeydown={handleKeydown}
|
|
146
|
-
aria-label=
|
|
148
|
+
aria-label={i18n.messages.dismiss}
|
|
147
149
|
class="inline-flex items-center justify-center rounded-full hover:[background-color:color-mix(in_srgb,currentColor_15%,transparent)] focus:outline-none transition-colors duration-150"
|
|
148
150
|
>
|
|
149
151
|
<Close class={responsiveClasses(size, iconSizeClass)} />
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { uiI18n } from "../i18n.js";
|
|
3
|
+
const i18n = uiI18n();
|
|
2
4
|
import type { Snippet } from "svelte";
|
|
3
5
|
import type { Size } from "../types/sizes.js";
|
|
4
6
|
import { responsiveClasses, type Responsive } from "../types/responsive.js";
|
|
@@ -119,7 +121,7 @@
|
|
|
119
121
|
{#if dismissible}
|
|
120
122
|
<button
|
|
121
123
|
type="button"
|
|
122
|
-
aria-label=
|
|
124
|
+
aria-label={i18n.messages.dismiss}
|
|
123
125
|
onclick={close}
|
|
124
126
|
class="inline-flex shrink-0 cursor-pointer items-center justify-center rounded p-0.5 opacity-80 transition-opacity hover:opacity-100 focus-visible:opacity-100 {responsiveClasses(
|
|
125
127
|
size,
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
</script>
|
|
18
18
|
|
|
19
19
|
<script lang="ts">
|
|
20
|
+
import { uiI18n } from "../../i18n.js";
|
|
21
|
+
const i18n = uiI18n();
|
|
20
22
|
import Button from "../Button.svelte";
|
|
21
23
|
import Menu from "../Menu.svelte";
|
|
22
24
|
import type { MenuItem } from "../../data/table/types.js";
|
|
@@ -71,7 +73,7 @@
|
|
|
71
73
|
}
|
|
72
74
|
</script>
|
|
73
75
|
|
|
74
|
-
<nav aria-label=
|
|
76
|
+
<nav aria-label={i18n.messages.breadcrumb} class={cls}>
|
|
75
77
|
<ol
|
|
76
78
|
class="flex flex-wrap items-center gap-1 [color:color-mix(in_srgb,var(--ui-color-text)_60%,transparent)]"
|
|
77
79
|
>
|
|
@@ -91,7 +93,7 @@
|
|
|
91
93
|
<li class="inline-flex items-center">
|
|
92
94
|
{#if node.kind === "ellipsis"}
|
|
93
95
|
<Button
|
|
94
|
-
title=
|
|
96
|
+
title={i18n.messages.showMore}
|
|
95
97
|
variant="ghost"
|
|
96
98
|
{size}
|
|
97
99
|
aria-haspopup="menu"
|
|
@@ -48,6 +48,8 @@
|
|
|
48
48
|
</script>
|
|
49
49
|
|
|
50
50
|
<script lang="ts">
|
|
51
|
+
import { uiI18n } from "../../i18n.js";
|
|
52
|
+
const i18n = uiI18n();
|
|
51
53
|
import Button from "../Button.svelte";
|
|
52
54
|
import Menu from "../Menu.svelte";
|
|
53
55
|
import type { Size } from "../../types/sizes.js";
|
|
@@ -225,7 +227,7 @@
|
|
|
225
227
|
disabled={disabled || item.disabled}
|
|
226
228
|
aria-haspopup="menu"
|
|
227
229
|
aria-expanded={!!menuOpen[i]}
|
|
228
|
-
aria-label=
|
|
230
|
+
aria-label={i18n.messages.more}
|
|
229
231
|
bind:element={caretRefs[i]}
|
|
230
232
|
onclick={() => (menuOpen[i] = !menuOpen[i])}
|
|
231
233
|
>
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
-->
|
|
12
12
|
<script lang="ts">
|
|
13
|
+
import { uiI18n } from "../i18n.js";
|
|
14
|
+
const i18n = uiI18n();
|
|
13
15
|
import type { Size } from "../types/sizes.js";
|
|
14
16
|
import { responsiveClasses, type Responsive } from "../types/responsive.js";
|
|
15
17
|
import { variantToken, type Variant } from "../types/variants.js";
|
|
@@ -43,7 +45,7 @@
|
|
|
43
45
|
min,
|
|
44
46
|
max,
|
|
45
47
|
weekStart,
|
|
46
|
-
locale = typeof navigator !== "undefined" ? navigator.language : "en-US",
|
|
48
|
+
locale = i18n.locale ?? (typeof navigator !== "undefined" ? navigator.language : "en-US"),
|
|
47
49
|
size = "md",
|
|
48
50
|
variant = "primary",
|
|
49
51
|
class: cls = "",
|
|
@@ -191,14 +193,14 @@
|
|
|
191
193
|
<div class="mb-1 flex items-center justify-between {sizing.head}">
|
|
192
194
|
<button
|
|
193
195
|
type="button"
|
|
194
|
-
aria-label=
|
|
196
|
+
aria-label={i18n.messages.prevMonth}
|
|
195
197
|
class="grid {sizing.nav} place-items-center rounded-[var(--ui-border-radius)] hover:[background-color:var(--ui-color-hover)] active:[background-color:var(--ui-color-active)]"
|
|
196
198
|
onclick={() => go(-1)}><IconLeft class="size-5" /></button
|
|
197
199
|
>
|
|
198
200
|
<span class="{sizing.label} font-medium">{monthLabel}</span>
|
|
199
201
|
<button
|
|
200
202
|
type="button"
|
|
201
|
-
aria-label=
|
|
203
|
+
aria-label={i18n.messages.nextMonth}
|
|
202
204
|
class="grid {sizing.nav} place-items-center rounded-[var(--ui-border-radius)] hover:[background-color:var(--ui-color-hover)] active:[background-color:var(--ui-color-active)]"
|
|
203
205
|
onclick={() => go(1)}><IconRight class="size-5" /></button
|
|
204
206
|
>
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
12
|
<script lang="ts" generics="T">
|
|
13
|
+
import { uiI18n } from "../../i18n.js";
|
|
14
|
+
const i18n = uiI18n();
|
|
13
15
|
import Button from "../Button.svelte";
|
|
14
16
|
import IconPrev from "~icons/mdi/chevron-left";
|
|
15
17
|
import IconNext from "~icons/mdi/chevron-right";
|
|
@@ -106,7 +108,7 @@
|
|
|
106
108
|
{#if controls && n > 1}
|
|
107
109
|
<div class="absolute inset-y-0 left-2 flex items-center">
|
|
108
110
|
<Button
|
|
109
|
-
title=
|
|
111
|
+
title={i18n.messages.previous}
|
|
110
112
|
variant="secondary"
|
|
111
113
|
onclick={prev}
|
|
112
114
|
disabled={!loop && index === 0}
|
|
@@ -117,7 +119,7 @@
|
|
|
117
119
|
</div>
|
|
118
120
|
<div class="absolute inset-y-0 right-2 flex items-center">
|
|
119
121
|
<Button
|
|
120
|
-
title=
|
|
122
|
+
title={i18n.messages.next}
|
|
121
123
|
variant="secondary"
|
|
122
124
|
onclick={next}
|
|
123
125
|
disabled={!loop && index === n - 1}
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
</script>
|
|
21
21
|
|
|
22
22
|
<script lang="ts">
|
|
23
|
+
import { uiI18n } from "../../i18n.js";
|
|
24
|
+
const i18n = uiI18n();
|
|
23
25
|
import Input from "../Input.svelte";
|
|
24
26
|
import Kbd from "../Kbd/Kbd.svelte";
|
|
25
27
|
import IconSearch from "~icons/mdi/magnify";
|
|
@@ -143,7 +145,7 @@
|
|
|
143
145
|
class="h-max w-full max-w-xl overflow-hidden rounded-[var(--ui-border-radius)] border shadow-2xl [background-color:var(--ui-color-background)] [border-color:var(--ui-border-color)]"
|
|
144
146
|
role="dialog"
|
|
145
147
|
aria-modal="true"
|
|
146
|
-
aria-label=
|
|
148
|
+
aria-label={i18n.messages.commandPalette}
|
|
147
149
|
>
|
|
148
150
|
<Input
|
|
149
151
|
bind:value={query}
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
-->
|
|
12
12
|
<script lang="ts" generics="T">
|
|
13
|
+
import { uiI18n } from "../i18n.js";
|
|
14
|
+
const i18n = uiI18n();
|
|
13
15
|
import type { Snippet } from "svelte";
|
|
14
16
|
import IconDots from "~icons/mdi/dots-vertical";
|
|
15
17
|
import IconBulk from "~icons/mdi/format-list-checks";
|
|
@@ -195,9 +197,11 @@
|
|
|
195
197
|
}
|
|
196
198
|
|
|
197
199
|
const rangeText = $derived(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
:
|
|
200
|
+
i18n.messages.rowRange({
|
|
201
|
+
start: (grid.page - 1) * grid.perPage + 1,
|
|
202
|
+
end: Math.min(grid.page * grid.perPage, total),
|
|
203
|
+
total,
|
|
204
|
+
}),
|
|
201
205
|
);
|
|
202
206
|
|
|
203
207
|
// Invisible icon-slot sizing for the empty-state spacer. Sized to
|
|
@@ -246,7 +250,7 @@
|
|
|
246
250
|
{#if selectable}
|
|
247
251
|
<Checkbox
|
|
248
252
|
{size}
|
|
249
|
-
aria-label=
|
|
253
|
+
aria-label={i18n.messages.selectAll}
|
|
250
254
|
checked={grid.allSelected}
|
|
251
255
|
indeterminate={grid.someSelected}
|
|
252
256
|
onchange={() => grid.toggleAll()}
|
|
@@ -317,8 +321,8 @@
|
|
|
317
321
|
<Button
|
|
318
322
|
variant="ghost"
|
|
319
323
|
{size}
|
|
320
|
-
aria-label=
|
|
321
|
-
title=
|
|
324
|
+
aria-label={i18n.messages.bulkActions}
|
|
325
|
+
title={i18n.messages.bulkActions}
|
|
322
326
|
disabled={grid.selected.size === 0}
|
|
323
327
|
onclick={(e) => openMenu(e, bulkActions(grid.selected))}
|
|
324
328
|
>
|
|
@@ -352,7 +356,7 @@
|
|
|
352
356
|
{/if}
|
|
353
357
|
<div class="grow self-center text-center {subtle}">
|
|
354
358
|
{#if settled}
|
|
355
|
-
|
|
359
|
+
{i18n.messages.noResults}.
|
|
356
360
|
{:else}
|
|
357
361
|
<span class="inline-flex items-center gap-2"
|
|
358
362
|
><IconLoading class="w-[1em] h-[1em] animate-spin" /> Loading…</span
|
|
@@ -381,7 +385,7 @@
|
|
|
381
385
|
{#if selectable}
|
|
382
386
|
<Checkbox
|
|
383
387
|
{size}
|
|
384
|
-
aria-label=
|
|
388
|
+
aria-label={i18n.messages.selectRow}
|
|
385
389
|
checked={grid.isSelected(id)}
|
|
386
390
|
onchange={() => grid.toggleRow(id)}
|
|
387
391
|
/>
|
|
@@ -402,8 +406,8 @@
|
|
|
402
406
|
<Button
|
|
403
407
|
variant="ghost"
|
|
404
408
|
{size}
|
|
405
|
-
aria-label=
|
|
406
|
-
title=
|
|
409
|
+
aria-label={i18n.messages.moveUp}
|
|
410
|
+
title={i18n.messages.moveUp}
|
|
407
411
|
disabled={rowIndex === 0}
|
|
408
412
|
onclick={() => onReorder?.(row, "up")}
|
|
409
413
|
>
|
|
@@ -412,8 +416,8 @@
|
|
|
412
416
|
<Button
|
|
413
417
|
variant="ghost"
|
|
414
418
|
{size}
|
|
415
|
-
aria-label=
|
|
416
|
-
title=
|
|
419
|
+
aria-label={i18n.messages.moveDown}
|
|
420
|
+
title={i18n.messages.moveDown}
|
|
417
421
|
disabled={rowIndex === rows.length - 1}
|
|
418
422
|
onclick={() => onReorder?.(row, "down")}
|
|
419
423
|
>
|
|
@@ -454,8 +458,8 @@
|
|
|
454
458
|
variant="ghost"
|
|
455
459
|
{size}
|
|
456
460
|
disabled={!actions || actions.length === 0}
|
|
457
|
-
aria-label=
|
|
458
|
-
title=
|
|
461
|
+
aria-label={i18n.messages.rowActions}
|
|
462
|
+
title={i18n.messages.rowActions}
|
|
459
463
|
onclick={(e) => openMenu(e, actions ?? [])}
|
|
460
464
|
>
|
|
461
465
|
{#snippet icon()}<IconDots />{/snippet}
|
|
@@ -490,7 +494,7 @@
|
|
|
490
494
|
options={grid.perPageOptions.map((n) => ({ value: String(n), label: String(n) }))}
|
|
491
495
|
onSelection={(o) => grid.setPerPage(Number(o.value))}
|
|
492
496
|
/>
|
|
493
|
-
<span class={subtle}>
|
|
497
|
+
<span class={subtle}>{i18n.messages.perPage}</span>
|
|
494
498
|
</div>
|
|
495
499
|
|
|
496
500
|
<div class="flex items-center gap-3">
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
-->
|
|
15
15
|
<script lang="ts">
|
|
16
|
+
import { uiI18n } from "../i18n.js";
|
|
17
|
+
const i18n = uiI18n();
|
|
16
18
|
import type { Size } from "../types/sizes.js";
|
|
17
19
|
import { responsiveClasses, type Responsive } from "../types/responsive.js";
|
|
18
20
|
import { variantToken, type Variant } from "../types/variants.js";
|
|
@@ -57,7 +59,7 @@
|
|
|
57
59
|
min,
|
|
58
60
|
max,
|
|
59
61
|
weekStart,
|
|
60
|
-
locale = typeof navigator !== "undefined" ? navigator.language : "en-US",
|
|
62
|
+
locale = i18n.locale ?? (typeof navigator !== "undefined" ? navigator.language : "en-US"),
|
|
61
63
|
placeholder = range ? "Select dates" : time ? "Select date & time" : "Select date",
|
|
62
64
|
disabled = false,
|
|
63
65
|
size = "md",
|
|
@@ -195,7 +197,7 @@
|
|
|
195
197
|
{#if hasValue && !disabled}
|
|
196
198
|
<button
|
|
197
199
|
type="button"
|
|
198
|
-
aria-label=
|
|
200
|
+
aria-label={i18n.messages.clear}
|
|
199
201
|
tabindex="-1"
|
|
200
202
|
class="grid size-5 place-items-center rounded {subtle} hover:[background-color:color-mix(in_srgb,var(--ui-color-text)_10%,transparent)]"
|
|
201
203
|
onclick={(e) => {
|
|
@@ -207,7 +209,7 @@
|
|
|
207
209
|
<button
|
|
208
210
|
type="button"
|
|
209
211
|
tabindex="-1"
|
|
210
|
-
aria-label=
|
|
212
|
+
aria-label={i18n.messages.openCalendar}
|
|
211
213
|
{disabled}
|
|
212
214
|
class="inline-flex items-center [color:var(--ui-color-secondary)] disabled:opacity-50"
|
|
213
215
|
onclick={toggle}><IconCalendar /></button
|
|
@@ -235,7 +237,7 @@
|
|
|
235
237
|
{#if time}
|
|
236
238
|
<div class="mt-2 flex flex-col gap-2 border-t pt-2 [border-color:var(--ui-border-color)]">
|
|
237
239
|
<div class="flex items-center gap-3">
|
|
238
|
-
<span class="w-10 shrink-0 text-xs {subtle}">
|
|
240
|
+
<span class="w-10 shrink-0 text-xs {subtle}">{i18n.messages.start}</span><TimeSpinner
|
|
239
241
|
value={timeOf(rv.start)}
|
|
240
242
|
{step}
|
|
241
243
|
{seconds}
|
|
@@ -294,7 +296,7 @@
|
|
|
294
296
|
<button
|
|
295
297
|
type="button"
|
|
296
298
|
class="rounded px-2 py-1 [color:var(--accent)] hover:underline"
|
|
297
|
-
onclick={goToday}>
|
|
299
|
+
onclick={goToday}>{i18n.messages.today}</button
|
|
298
300
|
>
|
|
299
301
|
{#if time}<span class={subtle}>/</span><button
|
|
300
302
|
type="button"
|
|
@@ -305,7 +307,7 @@
|
|
|
305
307
|
<button
|
|
306
308
|
type="button"
|
|
307
309
|
class="rounded px-2 py-1 {subtle} hover:underline"
|
|
308
|
-
onclick={() => (open = false)}>
|
|
310
|
+
onclick={() => (open = false)}>{i18n.messages.done}</button
|
|
309
311
|
>
|
|
310
312
|
</div>
|
|
311
313
|
</div>
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
-->
|
|
9
9
|
<script lang="ts">
|
|
10
|
+
import { uiI18n } from "../../i18n.js";
|
|
11
|
+
const i18n = uiI18n();
|
|
10
12
|
import { onDestroy, untrack } from "svelte";
|
|
11
13
|
import type { Snippet } from "svelte";
|
|
12
14
|
import FieldChrome, { nextFieldId } from "../FieldChrome.svelte";
|
|
@@ -121,7 +123,7 @@
|
|
|
121
123
|
<div
|
|
122
124
|
role="button"
|
|
123
125
|
tabindex={disabled ? -1 : 0}
|
|
124
|
-
aria-label=
|
|
126
|
+
aria-label={i18n.messages.uploadFiles}
|
|
125
127
|
aria-describedby={describedBy}
|
|
126
128
|
class="flex flex-col items-center justify-center gap-2 rounded-[var(--ui-border-radius)] border-2 border-dashed text-center transition-colors {responsiveClasses(
|
|
127
129
|
size,
|
|
@@ -194,8 +196,8 @@
|
|
|
194
196
|
{/if}
|
|
195
197
|
</div>
|
|
196
198
|
<Button
|
|
197
|
-
title=
|
|
198
|
-
aria-label=
|
|
199
|
+
title={i18n.messages.removeFile({ name: file.name })}
|
|
200
|
+
aria-label={i18n.messages.removeFile({ name: file.name })}
|
|
199
201
|
variant="ghost"
|
|
200
202
|
size="sm"
|
|
201
203
|
icon={removeIcon}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { uiI18n } from "../i18n.js";
|
|
3
|
+
const i18n = uiI18n();
|
|
2
4
|
import { untrack } from "svelte";
|
|
3
5
|
import type { Snippet } from "svelte";
|
|
4
6
|
import type { Size } from "../types/sizes.js";
|
|
@@ -511,7 +513,7 @@
|
|
|
511
513
|
onblur={handleListBlur}
|
|
512
514
|
tabindex={disabled ? -1 : 0}
|
|
513
515
|
role="listbox"
|
|
514
|
-
aria-label=
|
|
516
|
+
aria-label={i18n.messages.listOfOptions}
|
|
515
517
|
aria-busy={isLoading}
|
|
516
518
|
aria-disabled={disabled}
|
|
517
519
|
>
|
|
@@ -626,7 +628,7 @@
|
|
|
626
628
|
aria-valuemin="0"
|
|
627
629
|
aria-valuemax="100"
|
|
628
630
|
tabindex="-1"
|
|
629
|
-
aria-label=
|
|
631
|
+
aria-label={i18n.messages.scrollThumb}
|
|
630
632
|
></div>
|
|
631
633
|
{/if}
|
|
632
634
|
</div>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { uiI18n } from "../i18n.js";
|
|
3
|
+
const i18n = uiI18n();
|
|
2
4
|
import { tick } from "svelte";
|
|
3
5
|
import type { Snippet } from "svelte";
|
|
4
6
|
import Input from "./Input.svelte";
|
|
@@ -321,8 +323,8 @@
|
|
|
321
323
|
tabindex={-1}
|
|
322
324
|
disabled={atMin}
|
|
323
325
|
onclick={() => stepBy(-1)}
|
|
324
|
-
aria-label=
|
|
325
|
-
title=
|
|
326
|
+
aria-label={i18n.messages.decrease}
|
|
327
|
+
title={i18n.messages.decrease}
|
|
326
328
|
>
|
|
327
329
|
{#snippet icon()}<Minus />{/snippet}
|
|
328
330
|
</Button>
|
|
@@ -332,8 +334,8 @@
|
|
|
332
334
|
tabindex={-1}
|
|
333
335
|
disabled={atMax}
|
|
334
336
|
onclick={() => stepBy(1)}
|
|
335
|
-
aria-label=
|
|
336
|
-
title=
|
|
337
|
+
aria-label={i18n.messages.increase}
|
|
338
|
+
title={i18n.messages.increase}
|
|
337
339
|
>
|
|
338
340
|
{#snippet icon()}<Plus />{/snippet}
|
|
339
341
|
</Button>
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
-->
|
|
10
10
|
<script lang="ts">
|
|
11
|
+
import { uiI18n } from "../../i18n.js";
|
|
12
|
+
const i18n = uiI18n();
|
|
11
13
|
import Button from "../Button.svelte";
|
|
12
14
|
import Select from "../Select.svelte";
|
|
13
15
|
import NumberInput from "../NumberInput.svelte";
|
|
@@ -66,9 +68,11 @@
|
|
|
66
68
|
});
|
|
67
69
|
|
|
68
70
|
const rangeText = $derived(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
:
|
|
71
|
+
i18n.messages.rowRange({
|
|
72
|
+
start: (page - 1) * perPage + 1,
|
|
73
|
+
end: Math.min(page * perPage, total),
|
|
74
|
+
total,
|
|
75
|
+
}),
|
|
72
76
|
);
|
|
73
77
|
const subtle = $derived(
|
|
74
78
|
`${responsiveClasses(size, { sm: "text-xs", md: "text-sm", lg: "text-base" })} [color:color-mix(in_srgb,var(--ui-color-text)_60%,transparent)]`,
|
|
@@ -101,7 +105,7 @@
|
|
|
101
105
|
</script>
|
|
102
106
|
|
|
103
107
|
<nav
|
|
104
|
-
aria-label=
|
|
108
|
+
aria-label={i18n.messages.pagination}
|
|
105
109
|
class="ui-accent flex flex-wrap items-center justify-between gap-3 border rounded-[var(--ui-border-radius)] {bandPad} {cls}"
|
|
106
110
|
style="border-color: {bandBorder}; background-color: {bandBg}; {accentVar}"
|
|
107
111
|
>
|
|
@@ -115,7 +119,7 @@
|
|
|
115
119
|
options={perPageOptions.map((n) => ({ value: String(n), label: String(n) }))}
|
|
116
120
|
onSelection={(o) => setPerPage(Number(o.value))}
|
|
117
121
|
/>
|
|
118
|
-
<span class={subtle}>
|
|
122
|
+
<span class={subtle}>{i18n.messages.perPage}</span>
|
|
119
123
|
</div>
|
|
120
124
|
{/if}
|
|
121
125
|
|
|
@@ -148,7 +152,7 @@
|
|
|
148
152
|
{size}
|
|
149
153
|
{disabled}
|
|
150
154
|
inputClass="text-center px-1"
|
|
151
|
-
aria-label=
|
|
155
|
+
aria-label={i18n.messages.pageNumber}
|
|
152
156
|
onchange={commitJump}
|
|
153
157
|
/>
|
|
154
158
|
</span>
|
|
@@ -20,6 +20,6 @@ type $$ComponentProps = {
|
|
|
20
20
|
disabled?: boolean;
|
|
21
21
|
class?: string;
|
|
22
22
|
};
|
|
23
|
-
declare const Pagination: import("svelte").Component<$$ComponentProps, {}, "
|
|
23
|
+
declare const Pagination: import("svelte").Component<$$ComponentProps, {}, "perPage" | "page">;
|
|
24
24
|
type Pagination = ReturnType<typeof Pagination>;
|
|
25
25
|
export default Pagination;
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
-->
|
|
9
9
|
<script lang="ts">
|
|
10
|
+
import { uiI18n } from "../i18n.js";
|
|
11
|
+
const i18n = uiI18n();
|
|
10
12
|
import { tick } from "svelte";
|
|
11
13
|
import IconSearch from "~icons/mdi/magnify";
|
|
12
14
|
import IconClose from "~icons/mdi/close";
|
|
@@ -167,15 +169,15 @@
|
|
|
167
169
|
{#snippet icon()}<IconSearch class="opacity-60" />{/snippet}
|
|
168
170
|
{#snippet actions()}
|
|
169
171
|
{#if dataset.query}
|
|
170
|
-
<Button variant="ghost" size="sm" aria-label=
|
|
172
|
+
<Button variant="ghost" size="sm" aria-label={i18n.messages.clear} title={i18n.messages.clear} onclick={clear}
|
|
171
173
|
>{#snippet icon()}<IconClose />{/snippet}</Button
|
|
172
174
|
>
|
|
173
175
|
{/if}
|
|
174
176
|
<Button
|
|
175
177
|
variant="ghost"
|
|
176
178
|
size="sm"
|
|
177
|
-
aria-label=
|
|
178
|
-
title=
|
|
179
|
+
aria-label={i18n.messages.queryHelp}
|
|
180
|
+
title={i18n.messages.queryHelp}
|
|
179
181
|
onclick={() => (helpOpen = true)}>{#snippet icon()}<IconHelp />{/snippet}</Button
|
|
180
182
|
>
|
|
181
183
|
{/snippet}
|
|
@@ -186,7 +188,7 @@
|
|
|
186
188
|
<p class="mt-1 text-xs [color:var(--ui-color-error)]">{dataset.error}</p>
|
|
187
189
|
{:else if dataset.dirty}
|
|
188
190
|
<p class="mt-1 text-xs [color:color-mix(in_srgb,var(--ui-color-text)_50%,transparent)]">
|
|
189
|
-
Press <kbd class="rounded border px-1 {bc}">
|
|
191
|
+
Press <kbd class="rounded border px-1 {bc}">{i18n.messages.enter}</kbd> to apply
|
|
190
192
|
</p>
|
|
191
193
|
{/if}
|
|
192
194
|
</div>
|
|
@@ -225,7 +227,7 @@
|
|
|
225
227
|
</ul>
|
|
226
228
|
</Popup>
|
|
227
229
|
|
|
228
|
-
<ConfirmDialog bind:isOpen={helpOpen} title=
|
|
230
|
+
<ConfirmDialog bind:isOpen={helpOpen} title={i18n.messages.querySyntax} showConfirm={false} showCancel={false}>
|
|
229
231
|
<div class="max-h-[70vh] space-y-4 overflow-y-auto text-sm [color:var(--ui-color-text)]">
|
|
230
232
|
<p>
|
|
231
233
|
A filter is one or more conditions, <code class="font-mono">$column operator value</code>.
|
|
@@ -233,7 +235,7 @@
|
|
|
233
235
|
<code>()</code>.
|
|
234
236
|
</p>
|
|
235
237
|
<div>
|
|
236
|
-
<h4 class="mb-1 font-semibold">
|
|
238
|
+
<h4 class="mb-1 font-semibold">{i18n.messages.operators}</h4>
|
|
237
239
|
<table class="w-full text-left">
|
|
238
240
|
<tbody class="[&_td]:py-0.5 [&_td:first-child]:pr-4 [&_td:first-child]:font-mono">
|
|
239
241
|
<tr><td>== !=</td><td>equals / not equals</td></tr>
|
|
@@ -246,7 +248,7 @@
|
|
|
246
248
|
</table>
|
|
247
249
|
</div>
|
|
248
250
|
<div>
|
|
249
|
-
<h4 class="mb-1 font-semibold">
|
|
251
|
+
<h4 class="mb-1 font-semibold">{i18n.messages.casts}</h4>
|
|
250
252
|
<p class="mb-1 text-xs [color:color-mix(in_srgb,var(--ui-color-text)_70%,transparent)]">
|
|
251
253
|
A <code class="font-mono">|type</code> after a column or value forces its type for strict comparison
|
|
252
254
|
— one cast per clause; put it on the column and/or the literals.
|
|
@@ -282,7 +284,7 @@
|
|
|
282
284
|
</table>
|
|
283
285
|
</div>
|
|
284
286
|
<div>
|
|
285
|
-
<h4 class="mb-1 font-semibold">
|
|
287
|
+
<h4 class="mb-1 font-semibold">{i18n.messages.examples}</h4>
|
|
286
288
|
<table class="w-full text-left">
|
|
287
289
|
<tbody
|
|
288
290
|
class="[&_td]:py-0.5 [&_td:first-child]:whitespace-nowrap [&_td:first-child]:pr-4 [&_td:first-child]:font-mono [&_td:last-child]:text-xs [&_td:last-child]:[color:color-mix(in_srgb,var(--ui-color-text)_65%,transparent)]"
|
|
@@ -305,7 +307,7 @@
|
|
|
305
307
|
</div>
|
|
306
308
|
<p class="text-xs [color:color-mix(in_srgb,var(--ui-color-text)_60%,transparent)]">
|
|
307
309
|
Tip: type <kbd class="rounded border px-1 {bc}">$</kbd> for columns; enum columns suggest
|
|
308
|
-
values. Applies on <kbd class="rounded border px-1 {bc}">
|
|
310
|
+
values. Applies on <kbd class="rounded border px-1 {bc}">{i18n.messages.enter}</kbd>.
|
|
309
311
|
</p>
|
|
310
312
|
</div>
|
|
311
313
|
</ConfirmDialog>
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<script lang="ts">
|
|
14
|
+
import { uiI18n } from "../../i18n.js";
|
|
15
|
+
const i18n = uiI18n();
|
|
14
16
|
import IconCheck from "~icons/mdi/check";
|
|
15
17
|
import type { Variant } from "../../types/variants.js";
|
|
16
18
|
import { variantToken } from "../../types/variants.js";
|
|
@@ -61,7 +63,7 @@
|
|
|
61
63
|
|
|
62
64
|
<ol
|
|
63
65
|
class="flex {isH ? 'flex-row items-start' : 'flex-col'} {cls}"
|
|
64
|
-
aria-label=
|
|
66
|
+
aria-label={i18n.messages.progress}
|
|
65
67
|
>
|
|
66
68
|
{#each steps as step, i (i)}
|
|
67
69
|
{@const s = state(i)}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { uiI18n } from "../i18n.js";
|
|
3
|
+
const i18n = uiI18n();
|
|
2
4
|
import ContentCopy from "~icons/mdi/content-copy";
|
|
3
5
|
import type { HTMLTextareaAttributes } from "svelte/elements";
|
|
4
6
|
import type { Snippet } from "svelte";
|
|
@@ -705,7 +707,7 @@
|
|
|
705
707
|
updateScrollbar();
|
|
706
708
|
}}
|
|
707
709
|
role="group"
|
|
708
|
-
aria-label=
|
|
710
|
+
aria-label={i18n.messages.textAreaControls}
|
|
709
711
|
>
|
|
710
712
|
<textarea
|
|
711
713
|
bind:this={textareaElement}
|
|
@@ -747,7 +749,7 @@
|
|
|
747
749
|
role="button"
|
|
748
750
|
tabindex="0"
|
|
749
751
|
style="user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;"
|
|
750
|
-
title=
|
|
752
|
+
title={i18n.messages.copyToClipboard}
|
|
751
753
|
>
|
|
752
754
|
<ContentCopy
|
|
753
755
|
class="w-4 h-4 [color:var(--ui-color-secondary)!important] group-hover:[color:color-mix(in_srgb,var(--ui-color-secondary)_80%,black)!important] group-focus:[color:color-mix(in_srgb,currentColor_70%,transparent)!important] transition-colors"
|
|
@@ -774,7 +776,7 @@
|
|
|
774
776
|
aria-valuemin="0"
|
|
775
777
|
aria-valuemax="100"
|
|
776
778
|
tabindex="-1"
|
|
777
|
-
aria-label=
|
|
779
|
+
aria-label={i18n.messages.scrollThumb}
|
|
778
780
|
></div>
|
|
779
781
|
{/if}
|
|
780
782
|
</div>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { uiI18n } from "../i18n.js";
|
|
3
|
+
const i18n = uiI18n();
|
|
2
4
|
import Select from "./Select.svelte";
|
|
3
5
|
import type { Size } from "../types/sizes.js";
|
|
4
6
|
import { responsiveClasses, type Responsive } from "../types/responsive.js";
|
|
@@ -40,7 +42,7 @@
|
|
|
40
42
|
{options}
|
|
41
43
|
{size}
|
|
42
44
|
{variant}
|
|
43
|
-
placeholder=
|
|
45
|
+
placeholder={i18n.messages.selectTheme}
|
|
44
46
|
class="w-40"
|
|
45
47
|
/>
|
|
46
48
|
</div>
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
-->
|
|
11
11
|
<script lang="ts">
|
|
12
|
+
import { uiI18n } from "../i18n.js";
|
|
13
|
+
const i18n = uiI18n();
|
|
12
14
|
import type { Size } from "../types/sizes.js";
|
|
13
15
|
import { responsiveClasses, type Responsive } from "../types/responsive.js";
|
|
14
16
|
import { resolveStep, type StepSpec } from "../types/time.js";
|
|
@@ -128,7 +130,7 @@
|
|
|
128
130
|
{#if hasValue && !disabled}
|
|
129
131
|
<button
|
|
130
132
|
type="button"
|
|
131
|
-
aria-label=
|
|
133
|
+
aria-label={i18n.messages.clear}
|
|
132
134
|
tabindex="-1"
|
|
133
135
|
class="grid size-5 place-items-center rounded {subtle} hover:[background-color:color-mix(in_srgb,var(--ui-color-text)_10%,transparent)]"
|
|
134
136
|
onclick={(e) => {
|
|
@@ -140,7 +142,7 @@
|
|
|
140
142
|
<button
|
|
141
143
|
type="button"
|
|
142
144
|
tabindex="-1"
|
|
143
|
-
aria-label=
|
|
145
|
+
aria-label={i18n.messages.openTimeRange}
|
|
144
146
|
{disabled}
|
|
145
147
|
class="inline-flex items-center [color:var(--ui-color-secondary)] disabled:opacity-50"
|
|
146
148
|
onclick={toggle}><IconClock /></button
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { uiI18n } from "../../i18n.js";
|
|
3
|
+
const i18n = uiI18n();
|
|
2
4
|
/**
|
|
3
5
|
* Renders the toast store's items in a fixed, top-layer stack. Mount ONCE at the app root; push with
|
|
4
6
|
* `toast.*` from anywhere. Each toast is an Alert (filled) honouring its variant + size.
|
|
@@ -41,7 +43,7 @@
|
|
|
41
43
|
>
|
|
42
44
|
<button
|
|
43
45
|
type="button"
|
|
44
|
-
aria-label=
|
|
46
|
+
aria-label={i18n.messages.dismiss}
|
|
45
47
|
class="shrink-0 self-center pr-2 opacity-60 hover:opacity-100"
|
|
46
48
|
onclick={() => dismissToast(t.id)}><IconClose class="size-4" /></button
|
|
47
49
|
>
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** A message: a fixed string, or a function of named params (mirrors Paraglide's `m.key(params)`). */
|
|
2
|
+
type Msg = string;
|
|
3
|
+
type MsgFn<P> = (params: P) => string;
|
|
4
|
+
/**
|
|
5
|
+
* Every user-facing string svelte-ui renders itself. Parameterized entries are functions so word order
|
|
6
|
+
* stays translatable. Strings already exposed as component PROPS (Select `placeholder`, EmptyState
|
|
7
|
+
* `title`, CommandPalette `placeholder`/`empty`, …) are localized by the consumer directly and are NOT
|
|
8
|
+
* here.
|
|
9
|
+
*/
|
|
10
|
+
export type UiMessages = {
|
|
11
|
+
dismiss: Msg;
|
|
12
|
+
clear: Msg;
|
|
13
|
+
more: Msg;
|
|
14
|
+
showMore: Msg;
|
|
15
|
+
copyToClipboard: Msg;
|
|
16
|
+
done: Msg;
|
|
17
|
+
noResults: Msg;
|
|
18
|
+
previous: Msg;
|
|
19
|
+
next: Msg;
|
|
20
|
+
first: Msg;
|
|
21
|
+
last: Msg;
|
|
22
|
+
pagination: Msg;
|
|
23
|
+
pageNumber: Msg;
|
|
24
|
+
perPage: Msg;
|
|
25
|
+
rowRange: MsgFn<{
|
|
26
|
+
start: number;
|
|
27
|
+
end: number;
|
|
28
|
+
total: number;
|
|
29
|
+
}>;
|
|
30
|
+
breadcrumb: Msg;
|
|
31
|
+
scrollThumb: Msg;
|
|
32
|
+
progress: Msg;
|
|
33
|
+
commandPalette: Msg;
|
|
34
|
+
selectDate: Msg;
|
|
35
|
+
openCalendar: Msg;
|
|
36
|
+
prevMonth: Msg;
|
|
37
|
+
nextMonth: Msg;
|
|
38
|
+
today: Msg;
|
|
39
|
+
start: Msg;
|
|
40
|
+
dynamicDates: Msg;
|
|
41
|
+
openTimeRange: Msg;
|
|
42
|
+
bulkActions: Msg;
|
|
43
|
+
selectAll: Msg;
|
|
44
|
+
selectRow: Msg;
|
|
45
|
+
rowActions: Msg;
|
|
46
|
+
moveUp: Msg;
|
|
47
|
+
moveDown: Msg;
|
|
48
|
+
listOfOptions: Msg;
|
|
49
|
+
increase: Msg;
|
|
50
|
+
decrease: Msg;
|
|
51
|
+
textAreaControls: Msg;
|
|
52
|
+
uploadFiles: Msg;
|
|
53
|
+
removeFile: MsgFn<{
|
|
54
|
+
name: string;
|
|
55
|
+
}>;
|
|
56
|
+
queryHelp: Msg;
|
|
57
|
+
querySyntax: Msg;
|
|
58
|
+
casts: Msg;
|
|
59
|
+
operators: Msg;
|
|
60
|
+
examples: Msg;
|
|
61
|
+
notes: Msg;
|
|
62
|
+
enter: Msg;
|
|
63
|
+
selectTheme: Msg;
|
|
64
|
+
menu: Msg;
|
|
65
|
+
};
|
|
66
|
+
/** English defaults — the exact literals the components used to hard-code (no behaviour change). */
|
|
67
|
+
export declare const defaultUiMessages: UiMessages;
|
|
68
|
+
/** What a component reads. `locale` is `undefined` unless a provider sets one — Intl components then
|
|
69
|
+
* fall back to `navigator.language`, preserving current behaviour for no-op consumers. */
|
|
70
|
+
export type UiI18n = {
|
|
71
|
+
readonly locale: string | undefined;
|
|
72
|
+
readonly messages: UiMessages;
|
|
73
|
+
};
|
|
74
|
+
type Resolvable<T> = T | (() => T);
|
|
75
|
+
type UiI18nConfig = {
|
|
76
|
+
locale?: Resolvable<string | undefined>;
|
|
77
|
+
messages?: Resolvable<Partial<UiMessages>>;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Provide svelte-ui's localization. Call ONCE near the app root. Partial `messages` merge over the
|
|
81
|
+
* English defaults, so translating a subset leaves the rest English. Values may be getters for a
|
|
82
|
+
* runtime-switchable locale.
|
|
83
|
+
*/
|
|
84
|
+
export declare function setUiI18n(config: UiI18nConfig): void;
|
|
85
|
+
/** Read the active messages + locale. Falls back to English defaults / `undefined` locale when no
|
|
86
|
+
* provider is present. Reads lazily, so getters passed to `setUiI18n` make components reactive. */
|
|
87
|
+
export declare function uiI18n(): UiI18n;
|
|
88
|
+
export {};
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// svelte-ui internal localization. Components render their own microcopy (aria-labels, titles, fixed
|
|
2
|
+
// visible text) through a small message registry with English defaults, and read a display `locale`
|
|
3
|
+
// for `Intl.*` formatting. A consumer overrides once at the app root via `setUiI18n`; do nothing and
|
|
4
|
+
// every string is the English default (byte-identical output — i18n is fully opt-in).
|
|
5
|
+
//
|
|
6
|
+
// Reactivity: `setUiI18n` accepts concrete values OR getter functions. Pass getters that read reactive
|
|
7
|
+
// state (e.g. `() => getLocale()`) and components re-render on locale change; pass plain values for a
|
|
8
|
+
// static (per-navigation) locale. Reads happen lazily in each component's reactive scope.
|
|
9
|
+
import { getContext, setContext } from "svelte";
|
|
10
|
+
/** English defaults — the exact literals the components used to hard-code (no behaviour change). */
|
|
11
|
+
export const defaultUiMessages = {
|
|
12
|
+
dismiss: "Dismiss",
|
|
13
|
+
clear: "Clear",
|
|
14
|
+
more: "More",
|
|
15
|
+
showMore: "Show more",
|
|
16
|
+
copyToClipboard: "Copy to clipboard",
|
|
17
|
+
done: "Done",
|
|
18
|
+
noResults: "No results",
|
|
19
|
+
previous: "Previous",
|
|
20
|
+
next: "Next",
|
|
21
|
+
first: "First",
|
|
22
|
+
last: "Last",
|
|
23
|
+
pagination: "Pagination",
|
|
24
|
+
pageNumber: "Page number",
|
|
25
|
+
perPage: "per page",
|
|
26
|
+
rowRange: ({ start, end, total }) => (total <= 0 ? "0 of 0" : `${start}–${end} of ${total}`),
|
|
27
|
+
breadcrumb: "Breadcrumb",
|
|
28
|
+
scrollThumb: "Scroll thumb",
|
|
29
|
+
progress: "Progress",
|
|
30
|
+
commandPalette: "Command palette",
|
|
31
|
+
selectDate: "Select date",
|
|
32
|
+
openCalendar: "Open calendar",
|
|
33
|
+
prevMonth: "Previous month",
|
|
34
|
+
nextMonth: "Next month",
|
|
35
|
+
today: "Today",
|
|
36
|
+
start: "Start",
|
|
37
|
+
dynamicDates: "Dynamic dates",
|
|
38
|
+
openTimeRange: "Open time range",
|
|
39
|
+
bulkActions: "Bulk actions",
|
|
40
|
+
selectAll: "Select all",
|
|
41
|
+
selectRow: "Select row",
|
|
42
|
+
rowActions: "Row actions",
|
|
43
|
+
moveUp: "Move up",
|
|
44
|
+
moveDown: "Move down",
|
|
45
|
+
listOfOptions: "List of options",
|
|
46
|
+
increase: "Increase",
|
|
47
|
+
decrease: "Decrease",
|
|
48
|
+
textAreaControls: "Text area with controls",
|
|
49
|
+
uploadFiles: "Upload files: drag and drop or activate to browse",
|
|
50
|
+
removeFile: ({ name }) => `Remove ${name}`,
|
|
51
|
+
queryHelp: "Query help",
|
|
52
|
+
querySyntax: "Query syntax",
|
|
53
|
+
casts: "Casts",
|
|
54
|
+
operators: "Operators",
|
|
55
|
+
examples: "Examples",
|
|
56
|
+
notes: "Notes",
|
|
57
|
+
enter: "Enter",
|
|
58
|
+
selectTheme: "Select theme...",
|
|
59
|
+
menu: "Menu",
|
|
60
|
+
};
|
|
61
|
+
const KEY = Symbol("svelte-ui-i18n");
|
|
62
|
+
const resolve = (v) => typeof v === "function" ? v() : v;
|
|
63
|
+
/**
|
|
64
|
+
* Provide svelte-ui's localization. Call ONCE near the app root. Partial `messages` merge over the
|
|
65
|
+
* English defaults, so translating a subset leaves the rest English. Values may be getters for a
|
|
66
|
+
* runtime-switchable locale.
|
|
67
|
+
*/
|
|
68
|
+
export function setUiI18n(config) {
|
|
69
|
+
setContext(KEY, config);
|
|
70
|
+
}
|
|
71
|
+
/** Read the active messages + locale. Falls back to English defaults / `undefined` locale when no
|
|
72
|
+
* provider is present. Reads lazily, so getters passed to `setUiI18n` make components reactive. */
|
|
73
|
+
export function uiI18n() {
|
|
74
|
+
const cfg = getContext(KEY);
|
|
75
|
+
return {
|
|
76
|
+
get locale() {
|
|
77
|
+
return resolve(cfg?.locale);
|
|
78
|
+
},
|
|
79
|
+
get messages() {
|
|
80
|
+
const overrides = resolve(cfg?.messages);
|
|
81
|
+
return overrides ? { ...defaultUiMessages, ...overrides } : defaultUiMessages;
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -82,6 +82,8 @@ export { anchored, computePlacement, resolveBoundary } from "./positioning/ancho
|
|
|
82
82
|
export type { Side, Align, Boundary, PlaceOptions, Placement, AnchoredParams, } from "./positioning/anchored.js";
|
|
83
83
|
export { tooltip } from "./positioning/tooltip.js";
|
|
84
84
|
export type { TooltipOptions } from "./positioning/tooltip.js";
|
|
85
|
+
export { setUiI18n, uiI18n, defaultUiMessages } from "./i18n.js";
|
|
86
|
+
export type { UiMessages, UiI18n } from "./i18n.js";
|
|
85
87
|
export type { Variant } from "./types/variants.js";
|
|
86
88
|
export type { Size } from "./types/sizes.js";
|
|
87
89
|
export type { StepSpec } from "./types/time.js";
|
package/dist/index.js
CHANGED
|
@@ -78,5 +78,7 @@ export { default as Query } from "./components/Query.svelte";
|
|
|
78
78
|
export { anchored, computePlacement, resolveBoundary } from "./positioning/anchored.js";
|
|
79
79
|
// Tooltip action (use:tooltip) — themed tooltips on the `anchored` engine, no global handler.
|
|
80
80
|
export { tooltip } from "./positioning/tooltip.js";
|
|
81
|
+
// Internal localization (opt-in) — override svelte-ui's own microcopy + Intl locale at the app root.
|
|
82
|
+
export { setUiI18n, uiI18n, defaultUiMessages } from "./i18n.js";
|
|
81
83
|
export { BREAKPOINT_PX } from "./types/breakpoints.js";
|
|
82
84
|
export { responsiveClasses, resolveScalar } from "./types/responsive.js";
|