@meith/theme-default 0.6.0 → 0.8.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.
Files changed (41) hide show
  1. package/package.json +3 -3
  2. package/src/copy.ts +316 -0
  3. package/src/index.ts +6 -6
  4. package/src/messages/en.json +189 -0
  5. package/src/messages/index.ts +5 -0
  6. package/src/panel-icons.tsx +1 -2
  7. package/src/shared.tsx +4 -6
  8. package/src/slots/announcement.tsx +15 -9
  9. package/src/slots/auth-page.tsx +3 -1
  10. package/src/slots/board-index.tsx +11 -4
  11. package/src/slots/board-stats.tsx +14 -12
  12. package/src/slots/category-block.tsx +1 -1
  13. package/src/slots/discovery-view.tsx +16 -15
  14. package/src/slots/error-notice.tsx +17 -9
  15. package/src/slots/footer.tsx +15 -4
  16. package/src/slots/forum-display.tsx +25 -20
  17. package/src/slots/forum-jump.tsx +1 -1
  18. package/src/slots/forum-row.tsx +16 -15
  19. package/src/slots/header.tsx +13 -3
  20. package/src/slots/latest-posts.tsx +21 -12
  21. package/src/slots/latest-threads.tsx +27 -16
  22. package/src/slots/member-profile.tsx +19 -15
  23. package/src/slots/navigation.tsx +7 -4
  24. package/src/slots/notice.tsx +12 -9
  25. package/src/slots/pagination.tsx +16 -10
  26. package/src/slots/panel-nav.tsx +37 -16
  27. package/src/slots/panel-page.tsx +15 -3
  28. package/src/slots/panel-shell.tsx +1 -7
  29. package/src/slots/post-actions.tsx +18 -11
  30. package/src/slots/post-bit.tsx +42 -20
  31. package/src/slots/post-form.tsx +7 -3
  32. package/src/slots/redirect-notice.tsx +16 -7
  33. package/src/slots/search-form.tsx +13 -9
  34. package/src/slots/search-results.tsx +27 -26
  35. package/src/slots/shell.tsx +4 -3
  36. package/src/slots/subforum-list.tsx +12 -9
  37. package/src/slots/thread-row.tsx +19 -18
  38. package/src/slots/thread-view.tsx +23 -13
  39. package/src/slots/user-panel.tsx +19 -14
  40. package/src/slots/who-is-online.tsx +35 -19
  41. package/src/theme.ts +90 -19
@@ -1,22 +1,31 @@
1
- import { Card, CardContent, buttonVariants } from '@meith/ui'
2
- import type { RedirectNoticeModel } from '@meith/theme-kit'
1
+ import type { RedirectNoticeModel, SlotCopy } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
3
+ import { buttonVariants, Card, CardContent } from '@meith/ui'
4
+
5
+ export function RedirectNotice({
6
+ message,
7
+ targetHref,
8
+ delaySeconds,
9
+ copy,
10
+ }: RedirectNoticeModel & { copy: SlotCopy }) {
11
+ const c = (key: string) => fromSlotCopy(copy, `default.redirectNotice.${key}`)
3
12
 
4
- export function RedirectNotice({ message, targetHref, delaySeconds }: RedirectNoticeModel) {
5
13
  return (
6
14
  <Card className="w-full max-w-lg">
7
15
  <CardContent className="p-6">
8
16
  <p className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
9
- Redirecting
17
+ {c('redirecting')}
10
18
  </p>
11
- <h1 className="mt-1 text-2xl font-semibold tracking-tight">Please wait</h1>
19
+ <h1 className="mt-1 text-2xl font-semibold tracking-tight">{c('pleaseWait')}</h1>
12
20
  <p className="mt-2 text-sm text-muted-foreground">{message}</p>
13
21
 
14
22
  <div className="mt-5 flex flex-wrap items-center gap-3">
15
23
  <a href={targetHref} className={buttonVariants({ variant: 'primary' })}>
16
- Continue now
24
+ {c('continueNow')}
17
25
  </a>
18
26
  <span className="text-xs text-muted-foreground">
19
- Continuing on its own in {delaySeconds} {delaySeconds === 1 ? 'second' : 'seconds'}.
27
+ {c('continuingOnItsOwnIn')} {delaySeconds}{' '}
28
+ {delaySeconds === 1 ? c('second.one') : c('second.other')}.
20
29
  </span>
21
30
  </div>
22
31
  </CardContent>
@@ -1,16 +1,17 @@
1
+ import type { OptionModel, SearchAdvancedModel, SearchFormModel, SlotCopy } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
1
3
  import {
2
4
  Alert,
3
5
  AlertDescription,
4
6
  AlertTitle,
7
+ buttonVariants,
5
8
  Card,
6
9
  CardContent,
7
10
  Disclosure,
8
11
  Field,
9
12
  Input,
10
13
  NativeSelect,
11
- buttonVariants,
12
14
  } from '@meith/ui'
13
- import type { OptionModel, SearchAdvancedModel, SearchFormModel } from '@meith/theme-kit'
14
15
 
15
16
  export function SearchForm({
16
17
  action,
@@ -22,14 +23,17 @@ export function SearchForm({
22
23
  hint,
23
24
  errorMessage,
24
25
  advanced,
25
- }: SearchFormModel) {
26
+ copy,
27
+ }: SearchFormModel & { copy: SlotCopy }) {
28
+ const c = (key: string) => fromSlotCopy(copy, `default.searchForm.${key}`)
29
+
26
30
  return (
27
31
  <Card>
28
32
  <CardContent className="p-4 sm:p-5">
29
33
  <form method="get" action={action} className="flex flex-col gap-4">
30
34
  <Field
31
35
  name={fields.query}
32
- label="Search for"
36
+ label={c('searchFor')}
33
37
  error={errorMessage}
34
38
  {...(hint === null ? {} : { description: hint })}
35
39
  >
@@ -40,21 +44,21 @@ export function SearchForm({
40
44
  maxLength={maxQueryLength}
41
45
  type="search"
42
46
  autoComplete="off"
43
- placeholder="Words in a subject or a post"
47
+ placeholder={c('placeholder')}
44
48
  />
45
49
  )}
46
50
  </Field>
47
51
 
48
52
  <div className="grid gap-4 sm:grid-cols-2">
49
- <Choice label="In" name={fields.forum} options={forums} />
50
- <Choice label="Sort by" name={fields.sort} options={sorts} />
53
+ <Choice label={c('in')} name={fields.forum} options={forums} />
54
+ <Choice label={c('sortBy')} name={fields.sort} options={sorts} />
51
55
  </div>
52
56
 
53
57
  {advanced !== undefined && <Advanced {...advanced} />}
54
58
 
55
59
  <div>
56
60
  <button type="submit" className={buttonVariants({ variant: 'primary' })}>
57
- Search
61
+ {c('search')}
58
62
  </button>
59
63
  </div>
60
64
  </form>
@@ -62,7 +66,7 @@ export function SearchForm({
62
66
  {errorMessage !== null && (
63
67
  <Alert tone="error" className="mt-4">
64
68
  <AlertDescription>
65
- <AlertTitle>No results.</AlertTitle> {errorMessage}
69
+ <AlertTitle>{c('noResults')}</AlertTitle> {errorMessage}
66
70
  </AlertDescription>
67
71
  </Alert>
68
72
  )}
@@ -1,20 +1,21 @@
1
+ import type { OptionModel, SearchRefineModel, SearchResultsModel, SlotCopy } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
1
3
  import {
4
+ buttonVariants,
2
5
  Card,
3
6
  CardContent,
4
7
  CardFooter,
5
8
  CardRows,
9
+ cn,
6
10
  Empty,
7
11
  EmptyDescription,
8
12
  EmptyTitle,
9
13
  Field,
10
14
  Input,
11
15
  NativeSelect,
12
- buttonVariants,
13
- cn,
14
16
  } from '@meith/ui'
15
- import type { OptionModel, SearchRefineModel, SearchResultsModel } from '@meith/theme-kit'
16
17
 
17
- import { LINK, Stamp, pageAt } from '../shared'
18
+ import { LINK, pageAt, Stamp } from '../shared'
18
19
 
19
20
  export function SearchResults({
20
21
  terms,
@@ -26,7 +27,10 @@ export function SearchResults({
26
27
  within,
27
28
  refine,
28
29
  regions,
29
- }: SearchResultsModel) {
30
+ copy,
31
+ }: SearchResultsModel & { copy: SlotCopy }) {
32
+ const c = (key: string) => fromSlotCopy(copy, `default.searchResults.${key}`)
33
+
30
34
  return (
31
35
  <main
32
36
  id="board-content"
@@ -35,23 +39,22 @@ export function SearchResults({
35
39
  >
36
40
  <div className="flex flex-col gap-1">
37
41
  <h1 className="font-heading text-2xl font-semibold">
38
- Results for &ldquo;{terms}&rdquo;
42
+ {c('resultsFor')} {c('openQuote')}
43
+ {terms}
44
+ {c('closeQuote')}
39
45
  </h1>
40
46
  <p className="text-sm text-muted-foreground">
41
- Searched <Stamp at={searchedAt} />. Results are checked against your access
42
- every time this page is opened, so they can change.
47
+ {c('searched')} <Stamp at={searchedAt} />. {c('searchedNote')}
43
48
  </p>
44
49
  </div>
45
50
 
46
- {refine !== undefined && <Refine {...refine} />}
51
+ {refine !== undefined && <Refine {...refine} copy={copy} />}
47
52
 
48
53
  <Card>
49
54
  {hits.length === 0 ? (
50
55
  <Empty>
51
- <EmptyTitle>Nothing matched.</EmptyTitle>
52
- <EmptyDescription>
53
- Or nothing you can see does. Try fewer words, or a different spelling.
54
- </EmptyDescription>
56
+ <EmptyTitle>{c('nothingMatched')}</EmptyTitle>
57
+ <EmptyDescription>{c('tryFewerWords')}</EmptyDescription>
55
58
  </Empty>
56
59
  ) : (
57
60
  <CardRows>
@@ -65,7 +68,7 @@ export function SearchResults({
65
68
  dangerouslySetInnerHTML={{ __html: hit.excerptHtml }}
66
69
  />
67
70
  <p className="text-xs text-muted-foreground">
68
- {hit.authorUsername} · <Stamp at={hit.postedAt} />
71
+ {hit.authorUsername} {c('dot')} <Stamp at={hit.postedAt} />
69
72
  </p>
70
73
  </li>
71
74
  ))}
@@ -78,7 +81,7 @@ export function SearchResults({
78
81
  nextHref !== null && (
79
82
  <CardFooter>
80
83
  <a href={nextHref} className={`font-medium text-foreground ${LINK}`}>
81
- {nextLabel}
84
+ {nextLabel} {c('nextArrow')}
82
85
  </a>
83
86
  </CardFooter>
84
87
  )
@@ -99,12 +102,7 @@ export function SearchResults({
99
102
 
100
103
  <Field name={within.field} label={within.label} description={within.hint}>
101
104
  {(control) => (
102
- <Input
103
- {...control}
104
- defaultValue={within.value}
105
- type="search"
106
- autoComplete="off"
107
- />
105
+ <Input {...control} defaultValue={within.value} type="search" autoComplete="off" />
108
106
  )}
109
107
  </Field>
110
108
 
@@ -118,7 +116,7 @@ export function SearchResults({
118
116
  </Card>
119
117
 
120
118
  <a href={newSearchHref} className={`text-sm font-medium text-foreground ${LINK}`}>
121
- Start a new search
119
+ {c('startNewSearch')}
122
120
  </a>
123
121
  </main>
124
122
  )
@@ -135,7 +133,10 @@ function Refine({
135
133
  submitLabel,
136
134
  applied,
137
135
  clearHref,
138
- }: SearchRefineModel) {
136
+ copy,
137
+ }: SearchRefineModel & { copy: SlotCopy }) {
138
+ const c = (key: string) => fromSlotCopy(copy, `default.searchResults.${key}`)
139
+
139
140
  return (
140
141
  <section
141
142
  aria-label={label}
@@ -171,8 +172,8 @@ function Refine({
171
172
  className="inline-flex items-center gap-1.5 rounded-full border border-primary/40 bg-primary/10 px-2.5 py-1 text-xs font-medium text-foreground hover:bg-primary/20"
172
173
  >
173
174
  {chip.label}
174
- <span aria-hidden="true">×</span>
175
- <span className="sr-only">— remove this filter</span>
175
+ <span aria-hidden="true">{c('removeFilterX')}</span>
176
+ <span className="sr-only">{c('removeFilter')}</span>
176
177
  </a>
177
178
  ))}
178
179
  </div>
@@ -200,7 +201,7 @@ function Refine({
200
201
 
201
202
  {clearHref !== null && (
202
203
  <a href={clearHref} className={`text-xs font-medium text-foreground ${LINK}`}>
203
- Clear filters
204
+ {c('clearFilters')}
204
205
  </a>
205
206
  )}
206
207
  </form>
@@ -1,6 +1,7 @@
1
- import type { ShellModel } from '@meith/theme-kit'
1
+ import type { ShellModel, SlotCopy } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
2
3
 
3
- export function Shell({ viewer, children }: ShellModel) {
4
+ export function Shell({ viewer, children, copy }: ShellModel & { copy: SlotCopy }) {
4
5
  return (
5
6
  <div
6
7
  className="flex min-h-dvh flex-col bg-background text-foreground"
@@ -10,7 +11,7 @@ export function Shell({ viewer, children }: ShellModel) {
10
11
  href="#board-content"
11
12
  className="sr-only focus-visible:not-sr-only focus-visible:absolute focus-visible:left-4 focus-visible:top-4 focus-visible:z-50 focus-visible:inline-flex focus-visible:h-9 focus-visible:items-center focus-visible:rounded-md focus-visible:border focus-visible:border-border focus-visible:bg-card focus-visible:px-3 focus-visible:text-sm focus-visible:font-medium focus-visible:text-foreground focus-visible:shadow-lg"
12
13
  >
13
- Skip to content
14
+ {fromSlotCopy(copy, 'default.shell.skipToContent')}
14
15
  </a>
15
16
  {children}
16
17
  </div>
@@ -1,16 +1,19 @@
1
+ import type { SlotCopy, SubforumListModel } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
1
3
  import { Card, CardContent, CardHeader, CardTitle } from '@meith/ui'
2
- import type { SubforumListModel } from '@meith/theme-kit'
3
4
 
4
5
  import { Counts, LINK } from '../shared'
5
6
 
6
- export function SubforumList({ forums }: SubforumListModel) {
7
+ export function SubforumList({ forums, copy }: SubforumListModel & { copy: SlotCopy }) {
7
8
  if (forums.length === 0) return null
8
9
 
10
+ const c = (key: string) => fromSlotCopy(copy, `default.subforumList.${key}`)
11
+
9
12
  return (
10
13
  <Card aria-labelledby="subforums-heading">
11
14
  <CardHeader>
12
15
  <CardTitle id="subforums-heading" className="text-sm">
13
- Subforums
16
+ {c('heading')}
14
17
  </CardTitle>
15
18
  </CardHeader>
16
19
 
@@ -25,16 +28,16 @@ export function SubforumList({ forums }: SubforumListModel) {
25
28
  <Counts
26
29
  items={[
27
30
  {
28
- label: 'Threads',
31
+ label: c('threadsLabel'),
29
32
  value: forum.threadCount,
30
- one: 'thread',
31
- many: 'threads',
33
+ one: c('thread.one'),
34
+ many: c('thread.other'),
32
35
  },
33
36
  {
34
- label: 'Posts',
37
+ label: c('postsLabel'),
35
38
  value: forum.postCount,
36
- one: 'post',
37
- many: 'posts',
39
+ one: c('post.one'),
40
+ many: c('post.other'),
38
41
  },
39
42
  ]}
40
43
  />
@@ -1,9 +1,12 @@
1
+ import type { SlotCopy, ThreadRowSlotModel } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
1
3
  import { Badge } from '@meith/ui'
2
- import type { ThreadRowSlotModel } from '@meith/theme-kit'
3
4
 
4
5
  import { Counts, LINK, Prefix, ReadSpacer, Stamp, UnreadDot, UserRef } from '../shared'
5
6
 
6
- export function ThreadRow({ thread, select }: ThreadRowSlotModel) {
7
+ export function ThreadRow({ thread, select, copy }: ThreadRowSlotModel & { copy: SlotCopy }) {
8
+ const c = (key: string) => fromSlotCopy(copy, `default.threadRow.${key}`)
9
+
7
10
  return (
8
11
  <li
9
12
  data-unread={thread.isUnread ? '' : undefined}
@@ -29,9 +32,9 @@ export function ThreadRow({ thread, select }: ThreadRowSlotModel) {
29
32
  <div className="min-w-0">
30
33
  <div className="flex flex-wrap items-baseline gap-x-2 gap-y-1">
31
34
  {thread.prefix !== null && <Prefix prefix={thread.prefix} />}
32
- {thread.isSticky && <Badge tone="pinned">Pinned</Badge>}
33
- {thread.isLocked && <Badge tone="locked">Locked</Badge>}
34
- {thread.isMoved && <Badge tone="moved">Moved</Badge>}
35
+ {thread.isSticky && <Badge tone="pinned">{c('pinned')}</Badge>}
36
+ {thread.isLocked && <Badge tone="locked">{c('locked')}</Badge>}
37
+ {thread.isMoved && <Badge tone="moved">{c('moved')}</Badge>}
35
38
 
36
39
  <a
37
40
  href={thread.href}
@@ -42,11 +45,11 @@ export function ThreadRow({ thread, select }: ThreadRowSlotModel) {
42
45
  >
43
46
  {thread.title}
44
47
  </a>
45
- {thread.isUnread && <span className="sr-only"> (new posts)</span>}
48
+ {thread.isUnread && <span className="sr-only"> {c('newPosts')}</span>}
46
49
  </div>
47
50
 
48
51
  <p className="mt-0.5 text-xs text-muted-foreground">
49
- Started by <UserRef user={thread.author} className="font-normal" />
52
+ {c('startedBy')} <UserRef user={thread.author} className="font-normal" />
50
53
  </p>
51
54
  </div>
52
55
 
@@ -54,32 +57,30 @@ export function ThreadRow({ thread, select }: ThreadRowSlotModel) {
54
57
  className="col-start-2 md:col-start-3 md:justify-end"
55
58
  items={[
56
59
  {
57
- label: 'Replies',
60
+ label: c('repliesLabel'),
58
61
  value: thread.replyCount,
59
- one: 'reply',
60
- many: 'replies',
62
+ one: c('reply.one'),
63
+ many: c('reply.other'),
61
64
  },
62
65
  {
63
- label: 'Views',
66
+ label: c('viewsLabel'),
64
67
  value: thread.viewCount,
65
- one: 'view',
66
- many: 'views',
68
+ one: c('view.one'),
69
+ many: c('view.other'),
67
70
  },
68
71
  ]}
69
72
  />
70
73
 
71
74
  <div className="col-start-2 min-w-0 text-xs text-muted-foreground md:col-start-4">
72
75
  {thread.lastPost === null ? (
73
- <span className="text-thread-moved">No replies yet</span>
76
+ <span className="text-thread-moved">{c('noRepliesYet')}</span>
74
77
  ) : (
75
78
  <>
76
79
  <a href={thread.lastPost.href} className={`block font-medium text-foreground ${LINK}`}>
77
- Latest reply
80
+ {c('latestReply')}
78
81
  </a>
79
82
  <span className="mt-0.5 block truncate">
80
- {'by '}
81
- <UserRef user={thread.lastPost.author} className="font-normal" />
82
- {' · '}
83
+ {c('by')} <UserRef user={thread.lastPost.author} className="font-normal" /> {c('dot')}{' '}
83
84
  <Stamp at={thread.lastPost.at} />
84
85
  </span>
85
86
  </>
@@ -1,9 +1,19 @@
1
+ import type { SlotCopy, ThreadViewModel } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
1
3
  import { Badge, buttonVariants } from '@meith/ui'
2
- import type { ThreadViewModel } from '@meith/theme-kit'
3
4
 
4
5
  import { Counts, MUTED_LINK, PAGE_BODY, Prefix } from '../shared'
5
6
 
6
- export function ThreadView({ thread, forum, replyHref, markReadAction, regions }: ThreadViewModel) {
7
+ export function ThreadView({
8
+ thread,
9
+ forum,
10
+ replyHref,
11
+ markReadAction,
12
+ regions,
13
+ copy,
14
+ }: ThreadViewModel & { copy: SlotCopy }) {
15
+ const c = (key: string) => fromSlotCopy(copy, `default.threadView.${key}`)
16
+
7
17
  return (
8
18
  <div className={PAGE_BODY}>
9
19
  <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between sm:gap-6">
@@ -18,23 +28,23 @@ export function ThreadView({ thread, forum, replyHref, markReadAction, regions }
18
28
 
19
29
  <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-2">
20
30
  {thread.prefix !== null && <Prefix prefix={thread.prefix} />}
21
- {thread.isSticky && <Badge tone="pinned">Pinned</Badge>}
22
- {thread.isLocked && <Badge tone="locked">Locked — no new replies</Badge>}
23
- {thread.isMoved && <Badge tone="moved">Moved</Badge>}
31
+ {thread.isSticky && <Badge tone="pinned">{c('pinned')}</Badge>}
32
+ {thread.isLocked && <Badge tone="locked">{c('locked')}</Badge>}
33
+ {thread.isMoved && <Badge tone="moved">{c('moved')}</Badge>}
24
34
 
25
35
  <Counts
26
36
  items={[
27
37
  {
28
- label: 'Replies',
38
+ label: c('repliesLabel'),
29
39
  value: thread.replyCount,
30
- one: 'reply',
31
- many: 'replies',
40
+ one: c('reply.one'),
41
+ many: c('reply.other'),
32
42
  },
33
43
  {
34
- label: 'Views',
44
+ label: c('viewsLabel'),
35
45
  value: thread.viewCount,
36
- one: 'view',
37
- many: 'views',
46
+ one: c('view.one'),
47
+ many: c('view.other'),
38
48
  },
39
49
  ]}
40
50
  />
@@ -45,13 +55,13 @@ export function ThreadView({ thread, forum, replyHref, markReadAction, regions }
45
55
  {markReadAction !== null && (
46
56
  <form action={markReadAction} method="post">
47
57
  <button type="submit" className={buttonVariants({ variant: 'ghost' })}>
48
- Mark read
58
+ {c('markRead')}
49
59
  </button>
50
60
  </form>
51
61
  )}
52
62
  {replyHref !== null && (
53
63
  <a href={replyHref} className={buttonVariants({ variant: 'primary' })}>
54
- Reply
64
+ {c('replyAction')}
55
65
  </a>
56
66
  )}
57
67
  </div>
@@ -1,10 +1,12 @@
1
+ import type { SlotCopy, UserPanelModel } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
1
3
  import { Avatar, Badge, buttonVariants } from '@meith/ui'
2
4
  import { Menu } from '@meith/ui/menu'
3
- import type { UserPanelModel } from '@meith/theme-kit'
4
5
 
5
6
  import { MUTED_LINK } from '../shared'
6
7
 
7
- const COUNT_LINK = 'rounded-full outline-offset-2 focus-visible:outline-2 focus-visible:outline-ring'
8
+ const COUNT_LINK =
9
+ 'rounded-full outline-offset-2 focus-visible:outline-2 focus-visible:outline-ring'
8
10
 
9
11
  export function UserPanel({
10
12
  viewer,
@@ -14,7 +16,10 @@ export function UserPanel({
14
16
  notificationsHref,
15
17
  messagesHref,
16
18
  children,
17
- }: UserPanelModel) {
19
+ copy,
20
+ }: UserPanelModel & { copy: SlotCopy }) {
21
+ const c = (key: string) => fromSlotCopy(copy, `default.userPanel.${key}`)
22
+
18
23
  if (viewer.isGuest) {
19
24
  return (
20
25
  <div className="flex items-center gap-2">
@@ -34,7 +39,7 @@ export function UserPanel({
34
39
  )
35
40
  }
36
41
 
37
- const name = viewer.username ?? 'Signed in'
42
+ const name = viewer.username ?? c('signedIn')
38
43
 
39
44
  return (
40
45
  <div className="flex min-w-0 flex-col items-start gap-1 sm:items-end">
@@ -46,28 +51,28 @@ export function UserPanel({
46
51
  />
47
52
 
48
53
  <div className="flex min-w-0 items-center gap-2">
49
- {unreadNotifications > 0 && (
54
+ {unreadNotifications.value > 0 && (
50
55
  <a href={notificationsHref} className={COUNT_LINK}>
51
56
  <Badge tone="solid">
52
- {unreadNotifications}
53
- <span className="sr-only"> unread notifications</span>
54
- <span aria-hidden="true"> new</span>
57
+ {unreadNotifications.label}
58
+ <span className="sr-only"> {c('unreadNotifications')}</span>
59
+ <span aria-hidden="true"> {c('new')}</span>
55
60
  </Badge>
56
61
  </a>
57
62
  )}
58
- {unreadMessages > 0 && (
63
+ {unreadMessages.value > 0 && (
59
64
  <a href={messagesHref} className={COUNT_LINK}>
60
65
  <Badge tone="outline">
61
- {unreadMessages}
62
- <span className="sr-only"> unread messages</span>
63
- <span aria-hidden="true"> unread</span>
66
+ {unreadMessages.label}
67
+ <span className="sr-only"> {c('unreadMessages')}</span>
68
+ <span aria-hidden="true"> {c('unread')}</span>
64
69
  </Badge>
65
70
  </a>
66
71
  )}
67
72
 
68
73
  <span data-account="menu" className="flex min-w-0 items-center">
69
74
  <Menu
70
- label="Your account"
75
+ label={c('yourAccount')}
71
76
  items={links}
72
77
  trigger={
73
78
  <>
@@ -96,7 +101,7 @@ export function UserPanel({
96
101
  <nav
97
102
  data-account="plain"
98
103
  style={{ display: 'none' }}
99
- aria-label="Your account"
104
+ aria-label={c('yourAccount')}
100
105
  className="flex-wrap items-center gap-x-3 gap-y-1 text-xs sm:justify-end"
101
106
  >
102
107
  <span className="font-medium text-foreground">{name}</span>