@anchrd/intel-ui 0.48.0 → 0.50.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/package.json +2 -2
- package/src/board/board-assignee/board-assignee-picker.tsx +33 -16
- package/src/board/board-kanban/board-kanban.tsx +32 -1
- package/src/board/board-settings/board-settings.tsx +22 -1
- package/src/board/board-task/board-task.ts +10 -0
- package/src/board/board-task/board-task.tsx +27 -13
- package/src/feed/feed.tsx +26 -3
- package/src/i18n/de.json +2 -0
- package/src/i18n/en.json +2 -0
- package/src/i18n/es.json +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anchrd/intel-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"typecheck": "tsc --noEmit"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@anchrd/intel-contract": "^0.
|
|
36
|
+
"@anchrd/intel-contract": "^0.28.0",
|
|
37
37
|
"@blocknote/core": "^0.52.1",
|
|
38
38
|
"@blocknote/react": "^0.52.1",
|
|
39
39
|
"@blocknote/shadcn": "^0.52.1",
|
|
@@ -25,17 +25,11 @@ export function AssigneePicker({
|
|
|
25
25
|
boardId,
|
|
26
26
|
current,
|
|
27
27
|
onPick,
|
|
28
|
-
open,
|
|
29
|
-
onOpenChange,
|
|
30
28
|
children,
|
|
31
29
|
}: {
|
|
32
30
|
boardId: string;
|
|
33
31
|
current: string | null;
|
|
34
32
|
onPick(assigneeId: string | null): void;
|
|
35
|
-
// Controlled only where somebody else decides when it opens — the plus menu does, the chip does
|
|
36
|
-
// not. Left out, the trigger below governs it on its own.
|
|
37
|
-
open?: boolean;
|
|
38
|
-
onOpenChange?(next: boolean): void;
|
|
39
33
|
// ⚠️ THE ANCHOR, and it is not optional decoration (#723). Radix positions a menu against its
|
|
40
34
|
// trigger; the first version of this component had none, and the menu never appeared. Every test
|
|
41
35
|
// stayed green, because jsdom has no layout and Testing Library finds a portal wherever it sits.
|
|
@@ -44,19 +38,34 @@ export function AssigneePicker({
|
|
|
44
38
|
const i18n = useI18n();
|
|
45
39
|
const { data } = useIntelRouterContext();
|
|
46
40
|
const [query, setQuery] = useState("");
|
|
41
|
+
/**
|
|
42
|
+
* ⚠️ **The query is gated on being OPEN, and that is not an optimisation** (#757). Since the empty
|
|
43
|
+
* query became a real question, an ungated `useQuery` fires the moment the component mounts — and
|
|
44
|
+
* this component wraps every assignee circle on the screen. A board with forty cards would open
|
|
45
|
+
* forty requests for a menu nobody has touched.
|
|
46
|
+
*
|
|
47
|
+
* ⚠️ **Uncontrolled, since #750.** This used to accept an `open` prop for the plus menu, which
|
|
48
|
+
* opened the picker without a trigger of its own. That entry is gone — the circle is always on the
|
|
49
|
+
* row and opens the same menu — and the prop went with it rather than staying as a second way in
|
|
50
|
+
* that nothing drives and nothing keeps in step.
|
|
51
|
+
*/
|
|
52
|
+
const [shown, setShown] = useState(false);
|
|
47
53
|
const trimmed = query.trim();
|
|
54
|
+
/**
|
|
55
|
+
* ⚠️ **Enabled for an EMPTY query too, since #757.** Nothing typed asks a different question than
|
|
56
|
+
* one letter: it names the people who reach this board, a short set this installation already
|
|
57
|
+
* knows. One letter stays refused, because that IS a search and a bad one.
|
|
58
|
+
*/
|
|
48
59
|
const search = useQuery({
|
|
49
60
|
queryKey: ["board-assignee-search", boardId, trimmed],
|
|
50
|
-
enabled: trimmed.length >= 2,
|
|
61
|
+
enabled: shown && (trimmed.length === 0 || trimmed.length >= 2),
|
|
51
62
|
queryFn: async () => await data.searchBoardAssignees(boardId, trimmed),
|
|
52
63
|
});
|
|
53
64
|
const found = search.data?.items ?? [];
|
|
65
|
+
const more = search.data?.more ?? 0;
|
|
54
66
|
|
|
55
67
|
return (
|
|
56
|
-
<DropdownMenu
|
|
57
|
-
{...(open === undefined ? {} : { open })}
|
|
58
|
-
{...(onOpenChange === undefined ? {} : { onOpenChange })}
|
|
59
|
-
>
|
|
68
|
+
<DropdownMenu onOpenChange={setShown}>
|
|
60
69
|
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
|
61
70
|
<DropdownMenuContent align="start" className="w-64">
|
|
62
71
|
<div className="p-1">
|
|
@@ -67,7 +76,7 @@ export function AssigneePicker({
|
|
|
67
76
|
aria-label={i18n.t("board.assignee.search")}
|
|
68
77
|
placeholder={i18n.t("board.assignee.search")}
|
|
69
78
|
onChange={(event) => setQuery(event.target.value)}
|
|
70
|
-
onKeyDown={(event) => event.key === "Escape" &&
|
|
79
|
+
onKeyDown={(event) => event.key === "Escape" && setShown(false)}
|
|
71
80
|
className="w-full rounded-md border bg-background px-2 py-1 text-xs outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
72
81
|
/>
|
|
73
82
|
</div>
|
|
@@ -75,7 +84,6 @@ export function AssigneePicker({
|
|
|
75
84
|
<>
|
|
76
85
|
<DropdownMenuItem
|
|
77
86
|
onSelect={() => {
|
|
78
|
-
onOpenChange?.(false);
|
|
79
87
|
onPick(null);
|
|
80
88
|
}}
|
|
81
89
|
>
|
|
@@ -84,12 +92,15 @@ export function AssigneePicker({
|
|
|
84
92
|
<DropdownMenuSeparator />
|
|
85
93
|
</>
|
|
86
94
|
)}
|
|
87
|
-
{
|
|
95
|
+
{/* ⚠️ Only for the ONE-character case. An empty query now answers with suggestions, and
|
|
96
|
+
telling somebody to keep typing while showing them names would be a hint that contradicts
|
|
97
|
+
the list under it. */}
|
|
98
|
+
{trimmed.length === 1 ? (
|
|
88
99
|
<p className="px-2 py-1.5 text-muted-foreground text-xs">
|
|
89
100
|
{i18n.t("board.assignee.keepTyping")}
|
|
90
101
|
</p>
|
|
91
102
|
) : null}
|
|
92
|
-
{trimmed.length
|
|
103
|
+
{trimmed.length !== 1 && !search.isPending && found.length === 0 ? (
|
|
93
104
|
<p className="px-2 py-1.5 text-muted-foreground text-xs">
|
|
94
105
|
{i18n.t("board.assignee.nobody")}
|
|
95
106
|
</p>
|
|
@@ -98,7 +109,6 @@ export function AssigneePicker({
|
|
|
98
109
|
<DropdownMenuItem
|
|
99
110
|
key={person.id}
|
|
100
111
|
onSelect={() => {
|
|
101
|
-
onOpenChange?.(false);
|
|
102
112
|
onPick(person.id);
|
|
103
113
|
}}
|
|
104
114
|
>
|
|
@@ -109,6 +119,13 @@ export function AssigneePicker({
|
|
|
109
119
|
<span className="ml-auto truncate text-muted-foreground text-xs">{person.email}</span>
|
|
110
120
|
</DropdownMenuItem>
|
|
111
121
|
))}
|
|
122
|
+
{/* ⚠️ The cap says so rather than hiding. Three names that look like the whole list teach
|
|
123
|
+
the reader that nobody else exists, which is the one thing this list must not say. */}
|
|
124
|
+
{more === 0 ? null : (
|
|
125
|
+
<p className="px-2 py-1.5 text-muted-foreground text-xs">
|
|
126
|
+
{i18n.t("board.assignee.more", { count: more })}
|
|
127
|
+
</p>
|
|
128
|
+
)}
|
|
112
129
|
</DropdownMenuContent>
|
|
113
130
|
</DropdownMenu>
|
|
114
131
|
);
|
|
@@ -16,6 +16,7 @@ import { useState } from "react";
|
|
|
16
16
|
import type { BoardHandle, ColumnWithCards } from "@/board/board-data/board-data.types.ts";
|
|
17
17
|
import { type Family, familiesOf } from "@/board/board-stripes/board-stripes.ts";
|
|
18
18
|
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
19
|
+
import { cn } from "@/lib/utils";
|
|
19
20
|
import {
|
|
20
21
|
type Drop,
|
|
21
22
|
dragEndToDrop,
|
|
@@ -326,7 +327,13 @@ function Column({
|
|
|
326
327
|
aria-label={entry.column.title}
|
|
327
328
|
className="group/column flex w-64 shrink-0 flex-col gap-2"
|
|
328
329
|
>
|
|
329
|
-
<h3 className="flex items-
|
|
330
|
+
<h3 className="flex items-center gap-2 px-1 text-sm font-medium">
|
|
331
|
+
{/* ⚠️ The dot is DECORATION and says so (#725). The column's name is right beside it in
|
|
332
|
+
words, so announcing the colour would repeat the name in a form nobody can act on — and
|
|
333
|
+
for somebody who cannot see it, a second announcement of "Backlog" is noise, not help.
|
|
334
|
+
⚠️ An inline style, not a class: the value is chosen by a person at runtime, and Tailwind
|
|
335
|
+
only ships classes its scanner saw in the source. */}
|
|
336
|
+
<ColumnDot color={entry.column.color} />
|
|
330
337
|
{entry.column.title}
|
|
331
338
|
<span className="text-xs text-muted-foreground tabular-nums">{entry.cards.length}</span>
|
|
332
339
|
{entry.unknown === true ? (
|
|
@@ -647,3 +654,27 @@ function depthOf(
|
|
|
647
654
|
? i18n.t("board.stripes.root", { level: family.level })
|
|
648
655
|
: i18n.t("board.stripes.under", { level: family.level, column: parentColumn });
|
|
649
656
|
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* The colour of a column, as a dot (#725).
|
|
660
|
+
*
|
|
661
|
+
* ⚠️ **One component for two places.** The kanban head draws it and so does a linked task in the
|
|
662
|
+
* open card; written twice they drift, and a dot that means one thing here and another there is
|
|
663
|
+
* worse than no dot.
|
|
664
|
+
*
|
|
665
|
+
* ⚠️ **Absent is a real state, not a missing value.** A board configured before colours existed has
|
|
666
|
+
* none, and painting it grey-by-default would claim somebody chose grey. The reader's own muted
|
|
667
|
+
* tone says "no colour set" without pretending otherwise.
|
|
668
|
+
*/
|
|
669
|
+
export function ColumnDot({ color }: { color?: string | undefined }) {
|
|
670
|
+
return (
|
|
671
|
+
<span
|
|
672
|
+
aria-hidden="true"
|
|
673
|
+
className={cn(
|
|
674
|
+
"size-2 shrink-0 rounded-full",
|
|
675
|
+
color === undefined && "bg-muted-foreground/40",
|
|
676
|
+
)}
|
|
677
|
+
{...(color === undefined ? {} : { style: { backgroundColor: color } })}
|
|
678
|
+
/>
|
|
679
|
+
);
|
|
680
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ARCHIVE_COLUMN_ID,
|
|
3
|
+
type BoardColumn,
|
|
4
|
+
DEFAULT_COLUMN_COLOR,
|
|
5
|
+
} from "@anchrd/intel-contract/board";
|
|
2
6
|
import { ChevronDown, ChevronUp, X } from "lucide-react";
|
|
3
7
|
import { useState } from "react";
|
|
4
8
|
import type { BoardHandle } from "@/board/board-data/board-data.types.ts";
|
|
@@ -94,6 +98,23 @@ export function BoardSettings({ board, onClose }: { board: BoardHandle; onClose(
|
|
|
94
98
|
<ul className="space-y-2">
|
|
95
99
|
{columns.map((column, index) => (
|
|
96
100
|
<li key={column.id} className="flex items-center gap-2">
|
|
101
|
+
{/* ⚠️ A free colour, not a palette (Jack's decision 2026-08-22) — see the contract
|
|
102
|
+
for why that is affordable here: the value only ever fills a dot.
|
|
103
|
+
⚠️ The native `input type="color"`, not a built one. It is the platform's own
|
|
104
|
+
picker, it works with a keyboard and a screen reader without anybody writing that
|
|
105
|
+
part, and "kaufen statt bauen" is the rule this repository states for exactly this
|
|
106
|
+
case. */}
|
|
107
|
+
<input
|
|
108
|
+
type="color"
|
|
109
|
+
value={column.color ?? DEFAULT_COLUMN_COLOR}
|
|
110
|
+
aria-label={i18n.t("board.settings.columnColor", { column: column.title })}
|
|
111
|
+
onChange={(event) => {
|
|
112
|
+
const next = [...columns];
|
|
113
|
+
next[index] = { ...column, color: event.target.value };
|
|
114
|
+
setColumns(next);
|
|
115
|
+
}}
|
|
116
|
+
className="size-7 shrink-0 cursor-pointer rounded-md border bg-background outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
117
|
+
/>
|
|
97
118
|
<input
|
|
98
119
|
value={column.title}
|
|
99
120
|
aria-label={i18n.t("board.settings.columnName", { column: column.title })}
|
|
@@ -18,6 +18,13 @@ export interface TaskLink {
|
|
|
18
18
|
title: string | null;
|
|
19
19
|
/** The column the linked task sits in, so its state is readable without opening it. */
|
|
20
20
|
columnTitle: string | null;
|
|
21
|
+
/**
|
|
22
|
+
* The colour of that column (#725), so the row can show the state as a dot as well as a word.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ `undefined` is a real answer, not a gap: a board configured before colours existed has none,
|
|
25
|
+
* and a blocker outside this board has no column at all.
|
|
26
|
+
*/
|
|
27
|
+
columnColor: string | undefined;
|
|
21
28
|
/** Set on a subtask only: whether it sits in a column the board calls terminal. */
|
|
22
29
|
done: boolean;
|
|
23
30
|
}
|
|
@@ -57,6 +64,7 @@ export function linksOf(task: BoardTask, columns: ColumnWithCards[]): TaskLink[]
|
|
|
57
64
|
id: card.id,
|
|
58
65
|
title: card.title,
|
|
59
66
|
columnTitle: column.title,
|
|
67
|
+
columnColor: column.color,
|
|
60
68
|
done: column.terminal,
|
|
61
69
|
});
|
|
62
70
|
}
|
|
@@ -69,6 +77,7 @@ export function linksOf(task: BoardTask, columns: ColumnWithCards[]): TaskLink[]
|
|
|
69
77
|
id: card.id,
|
|
70
78
|
title: card.title,
|
|
71
79
|
columnTitle: column.title,
|
|
80
|
+
columnColor: column.color,
|
|
72
81
|
done: false,
|
|
73
82
|
});
|
|
74
83
|
}
|
|
@@ -85,6 +94,7 @@ export function linksOf(task: BoardTask, columns: ColumnWithCards[]): TaskLink[]
|
|
|
85
94
|
// says "something outside this board"; the id says nothing at all.
|
|
86
95
|
title: blocking?.card.title ?? null,
|
|
87
96
|
columnTitle: blocking?.column.title ?? null,
|
|
97
|
+
columnColor: blocking?.column.color,
|
|
88
98
|
done: false,
|
|
89
99
|
});
|
|
90
100
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { BoardTask } from "@anchrd/intel-contract/board";
|
|
2
|
-
import { Lock, Plus
|
|
2
|
+
import { Lock, Plus } from "lucide-react";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { AssigneeChip } from "@/board/board-assignee/board-assignee.tsx";
|
|
5
5
|
import { BoardChip } from "@/board/board-chip/board-chip.tsx";
|
|
6
6
|
import type { BoardHandle } from "@/board/board-data/board-data.types.ts";
|
|
7
7
|
import { DatesChip } from "@/board/board-dates/board-dates.tsx";
|
|
8
|
+
import { ColumnDot } from "@/board/board-kanban/board-kanban.tsx";
|
|
8
9
|
import { StatusChip } from "@/board/board-status/board-status.tsx";
|
|
9
10
|
import {
|
|
10
11
|
DropdownMenu,
|
|
@@ -13,24 +14,37 @@ import {
|
|
|
13
14
|
DropdownMenuTrigger,
|
|
14
15
|
} from "@/components/ui/dropdown-menu.tsx";
|
|
15
16
|
import { useI18n } from "@/i18n/i18n-context.tsx";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
const linkIcons: Record<LinkKind, typeof Lock> = {
|
|
19
|
-
subtask: Square,
|
|
20
|
-
blocker: Lock,
|
|
21
|
-
blocked: Lock,
|
|
22
|
-
};
|
|
17
|
+
import { linksOf, type TaskLink } from "./board-task.ts";
|
|
23
18
|
|
|
24
19
|
/** One entry of the link list. The icon carries the kind; nothing writes the word out. */
|
|
25
20
|
function LinkRow({ link, onOpen }: { link: TaskLink; onOpen(id: string): void }) {
|
|
26
21
|
const i18n = useI18n();
|
|
27
|
-
|
|
22
|
+
/**
|
|
23
|
+
* ⚠️ **A subtask shows its column as a DOT, not a tick** (#725). Jack's report of 2026-08-21 was
|
|
24
|
+
* that the open subtasks carry a checkmark in front of them that cannot be ticked. A checkbox
|
|
25
|
+
* promises an action this list does not have, and a card is finished by being moved, not by being
|
|
26
|
+
* ticked here.
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ **The lock stays.** A blocker outside this board is not a state on a scale, it is something
|
|
29
|
+
* the reader may not open, and a colour cannot say that.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* ⚠️ **The lock is about READABILITY, not about the kind**, and keying it on the kind was wrong in
|
|
33
|
+
* both directions: `blocked` is always a card of this board and always readable, while `blocker`
|
|
34
|
+
* may be any node anywhere — the one case where this answer holds no title and no column at all.
|
|
35
|
+
*
|
|
36
|
+
* `title === null` is exactly that case, and it is the same condition the row already uses one
|
|
37
|
+
* line down to say "outside this board". A dot there would be a colour for a column that does not
|
|
38
|
+
* exist.
|
|
39
|
+
*/
|
|
40
|
+
const unreadable = link.title === null;
|
|
28
41
|
return (
|
|
29
42
|
<li className="flex items-center gap-2 border-b py-2 last:border-0">
|
|
30
|
-
|
|
31
|
-
aria-hidden="true"
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
{unreadable ? (
|
|
44
|
+
<Lock aria-hidden="true" className="size-4 shrink-0 text-destructive" />
|
|
45
|
+
) : (
|
|
46
|
+
<ColumnDot color={link.columnColor} />
|
|
47
|
+
)}
|
|
34
48
|
<button
|
|
35
49
|
type="button"
|
|
36
50
|
onClick={() => onOpen(link.id)}
|
package/src/feed/feed.tsx
CHANGED
|
@@ -18,6 +18,21 @@ import { initials, useSessionUser } from "@/user-name/user-name.ts";
|
|
|
18
18
|
|
|
19
19
|
const PageSize = 30;
|
|
20
20
|
|
|
21
|
+
// How many steps of the trail a card shows.
|
|
22
|
+
//
|
|
23
|
+
// ⚠️ The LAST ones, not the first, and that is the whole decision (#758). A path answers "where does
|
|
24
|
+
// this sit", and the answer is at its end: `… / system / Kundenwissen` says where `Kunde A` lives,
|
|
25
|
+
// `Agenten / Anton Anchrd / sys…` says where the tree begins, which every card shares. Cutting from
|
|
26
|
+
// the front is what `truncate` does on its own, so this has to be decided here rather than left to
|
|
27
|
+
// CSS.
|
|
28
|
+
const TrailSteps = 2;
|
|
29
|
+
|
|
30
|
+
/** The tail of a trail, with a leading ellipsis where something was cut. */
|
|
31
|
+
export function shortTrail(titles: readonly string[]): string {
|
|
32
|
+
if (titles.length <= TrailSteps) return titles.join(" / ");
|
|
33
|
+
return `… / ${titles.slice(-TrailSteps).join(" / ")}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
// Who to show. Only the first two reach the server; `people` is applied here because whether an
|
|
22
37
|
// actor is a machine is Gate's answer and arrives with the names, not with the events.
|
|
23
38
|
type Audience = "all" | "mine" | "people";
|
|
@@ -280,9 +295,17 @@ function FeedCard({
|
|
|
280
295
|
<span className="text-sm leading-snug">
|
|
281
296
|
{i18n.t(`feed.action.${event.action}`, { who, title: event.nodeTitle })}
|
|
282
297
|
</span>
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
298
|
+
{/* ⚠️ `min-w-0` is not decoration. A grid item defaults to `min-width: auto`, so without it
|
|
299
|
+
this row grows to whatever the path needs and pushes the button PAST the card's edge —
|
|
300
|
+
which is what a four-step path did on a phone. `truncate` below cannot save it either: it
|
|
301
|
+
shortens against the width it is given, and its parent was already too wide. */}
|
|
302
|
+
<span className="flex min-w-0 items-center justify-between gap-3">
|
|
303
|
+
<span
|
|
304
|
+
className="truncate text-xs text-muted-foreground"
|
|
305
|
+
// The whole path stays reachable where there is a pointer to rest.
|
|
306
|
+
title={event.path.map((step) => step.title).join(" / ")}
|
|
307
|
+
>
|
|
308
|
+
{shortTrail(event.path.map((step) => step.title))}
|
|
286
309
|
</span>
|
|
287
310
|
{/* ⚠️ A button, not a label. The whole point of the mark is getting there, and a span
|
|
288
311
|
carries no focus — so the keyboard requirement could not be met by a surface that had
|
package/src/i18n/de.json
CHANGED
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
"board.assignSomebody": "Jemandem zuweisen",
|
|
88
88
|
"board.assignee.clear": "Zuständigkeit entfernen",
|
|
89
89
|
"board.assignee.keepTyping": "Mindestens zwei Zeichen tippen",
|
|
90
|
+
"board.assignee.more": "Hier nicht aufgeführt: {count}. Tippe, um zu suchen.",
|
|
90
91
|
"board.assignee.nobody": "Niemand mit Zugriff auf dieses Board passt dazu",
|
|
91
92
|
"board.assignee.search": "Person suchen",
|
|
92
93
|
"board.backTo": "Zurück zu {board}",
|
|
@@ -131,6 +132,7 @@
|
|
|
131
132
|
"board.moveTo": "in eine andere Spalte verschieben",
|
|
132
133
|
"board.removeLabel": "dieses Label entfernen",
|
|
133
134
|
"board.settings.addColumn": "+ Spalte",
|
|
135
|
+
"board.settings.columnColor": "Farbe der Spalte {column}",
|
|
134
136
|
"board.settings.columnName": "Name der Spalte {column}",
|
|
135
137
|
"board.settings.description": "Spalten und was das Board zeigt.",
|
|
136
138
|
"board.settings.duplicateName": "Zwei Spalten heißen {column}. Namen müssen sie unterscheidbar machen.",
|
package/src/i18n/en.json
CHANGED
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
"board.assignSomebody": "Assign to somebody",
|
|
88
88
|
"board.assignee.clear": "Remove the assignee",
|
|
89
89
|
"board.assignee.keepTyping": "Type at least two characters",
|
|
90
|
+
"board.assignee.more": "Not listed here: {count}. Type to search.",
|
|
90
91
|
"board.assignee.nobody": "Nobody with access to this board matches",
|
|
91
92
|
"board.assignee.search": "Find a person",
|
|
92
93
|
"board.backTo": "Back to {board}",
|
|
@@ -131,6 +132,7 @@
|
|
|
131
132
|
"board.moveTo": "move it to another column",
|
|
132
133
|
"board.removeLabel": "remove this label",
|
|
133
134
|
"board.settings.addColumn": "+ Column",
|
|
135
|
+
"board.settings.columnColor": "Colour of the {column} column",
|
|
134
136
|
"board.settings.columnName": "Name of the column {column}",
|
|
135
137
|
"board.settings.description": "Columns and what the board shows.",
|
|
136
138
|
"board.settings.duplicateName": "Two columns are called {column}. Names have to tell them apart.",
|
package/src/i18n/es.json
CHANGED
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
"board.assignSomebody": "Asignar a alguien",
|
|
88
88
|
"board.assignee.clear": "Quitar el responsable",
|
|
89
89
|
"board.assignee.keepTyping": "Escribe al menos dos caracteres",
|
|
90
|
+
"board.assignee.more": "No listados aquí: {count}. Escribe para buscar.",
|
|
90
91
|
"board.assignee.nobody": "Nadie con acceso a este tablero coincide",
|
|
91
92
|
"board.assignee.search": "Buscar una persona",
|
|
92
93
|
"board.backTo": "Volver a {board}",
|
|
@@ -131,6 +132,7 @@
|
|
|
131
132
|
"board.moveTo": "moverla a otra columna",
|
|
132
133
|
"board.removeLabel": "quitar esta etiqueta",
|
|
133
134
|
"board.settings.addColumn": "+ Columna",
|
|
135
|
+
"board.settings.columnColor": "Color de la columna {column}",
|
|
134
136
|
"board.settings.columnName": "Nombre de la columna {column}",
|
|
135
137
|
"board.settings.description": "Columnas y lo que muestra el tablero.",
|
|
136
138
|
"board.settings.duplicateName": "Dos columnas se llaman {column}. Los nombres deben distinguirlas.",
|