@meith/theme-clubhouse 0.7.0 → 0.9.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meith/theme-clubhouse",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "A sports club theme for Meith — GAA, soccer, basketball — painted from the club's own two colours.",
5
5
  "license": "LGPL-3.0-or-later",
6
6
  "repository": {
@@ -20,9 +20,9 @@
20
20
  "access": "public"
21
21
  },
22
22
  "dependencies": {
23
- "@meith/theme-default": "^0.7.0",
24
- "@meith/theme-kit": "^0.7.0",
25
- "@meith/ui": "^0.7.0"
23
+ "@meith/theme-default": "^0.9.0",
24
+ "@meith/theme-kit": "^0.9.0",
25
+ "@meith/ui": "^0.9.0"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": "^19.2.0"
package/src/copy.ts ADDED
@@ -0,0 +1,229 @@
1
+ import type { SlotCopy, Translator } from '@meith/theme-kit'
2
+
3
+ function copyFor(t: Translator, keys: readonly string[]): SlotCopy {
4
+ const copy: Record<string, string> = {}
5
+ for (const key of keys) copy[key] = t.t(key)
6
+ return copy
7
+ }
8
+
9
+ export function announcementCopy(t: Translator): SlotCopy {
10
+ return copyFor(t, [
11
+ 'clubhouse.announcement.clubNotice',
12
+ 'clubhouse.announcement.posted',
13
+ 'clubhouse.announcement.postedBy',
14
+ ])
15
+ }
16
+
17
+ export function boardIndexCopy(t: Translator): SlotCopy {
18
+ return copyFor(t, ['clubhouse.boardIndex.boardActivity', 'clubhouse.boardIndex.markAllRead'])
19
+ }
20
+
21
+ export function boardStatsCopy(t: Translator): SlotCopy {
22
+ return copyFor(t, [
23
+ 'clubhouse.boardStats.counted',
24
+ 'clubhouse.boardStats.heading',
25
+ 'clubhouse.boardStats.members',
26
+ 'clubhouse.boardStats.newest',
27
+ 'clubhouse.boardStats.notComputed',
28
+ 'clubhouse.boardStats.posts',
29
+ 'clubhouse.boardStats.threads',
30
+ ])
31
+ }
32
+
33
+ export function categoryBlockCopy(t: Translator): SlotCopy {
34
+ return copyFor(t, [
35
+ 'clubhouse.categoryBlock.forum',
36
+ 'clubhouse.categoryBlock.lastPost',
37
+ 'clubhouse.categoryBlock.posts',
38
+ 'clubhouse.categoryBlock.threads',
39
+ ])
40
+ }
41
+
42
+ export function footerCopy(t: Translator): SlotCopy {
43
+ return copyFor(t, ['clubhouse.footer.footerNav', 'clubhouse.footer.timesShownIn'])
44
+ }
45
+
46
+ export function forumDisplayCopy(t: Translator): SlotCopy {
47
+ return copyFor(t, [
48
+ 'clubhouse.forumDisplay.emptyNoThread',
49
+ 'clubhouse.forumDisplay.emptyNoThreadFirst',
50
+ 'clubhouse.forumDisplay.latest',
51
+ 'clubhouse.forumDisplay.markRead',
52
+ 'clubhouse.forumDisplay.newThread',
53
+ 'clubhouse.forumDisplay.noThreadsYet',
54
+ 'clubhouse.forumDisplay.posts',
55
+ 'clubhouse.forumDisplay.replies',
56
+ 'clubhouse.forumDisplay.startFirstThread',
57
+ 'clubhouse.forumDisplay.thread',
58
+ 'clubhouse.forumDisplay.threads',
59
+ 'clubhouse.forumDisplay.views',
60
+ ])
61
+ }
62
+
63
+ export function forumRowCopy(t: Translator): SlotCopy {
64
+ return copyFor(t, [
65
+ 'clubhouse.forumRow.link',
66
+ 'clubhouse.forumRow.newPosts',
67
+ 'clubhouse.forumRow.noPostsYet',
68
+ 'clubhouse.forumRow.posts',
69
+ 'clubhouse.forumRow.threads',
70
+ ])
71
+ }
72
+
73
+ export function headerCopy(t: Translator): SlotCopy {
74
+ return copyFor(t, ['clubhouse.header.boardSections'])
75
+ }
76
+
77
+ export function latestPostsCopy(t: Translator): SlotCopy {
78
+ return copyFor(t, [
79
+ 'clubhouse.latestPosts.asOf',
80
+ 'clubhouse.latestPosts.in',
81
+ 'clubhouse.latestPosts.newestPostsEmpty',
82
+ 'clubhouse.latestPosts.nothingSaidYet',
83
+ 'clubhouse.latestPosts.title',
84
+ ])
85
+ }
86
+
87
+ export function latestThreadsCopy(t: Translator): SlotCopy {
88
+ return copyFor(t, [
89
+ 'clubhouse.latestThreads.asOf',
90
+ 'clubhouse.latestThreads.in',
91
+ 'clubhouse.latestThreads.newestThreadsEmpty',
92
+ 'clubhouse.latestThreads.nothingStartedYet',
93
+ 'clubhouse.latestThreads.title',
94
+ ])
95
+ }
96
+
97
+ export function memberProfileCopy(t: Translator): SlotCopy {
98
+ return copyFor(t, [
99
+ 'clubhouse.memberProfile.about',
100
+ 'clubhouse.memberProfile.joined',
101
+ 'clubhouse.memberProfile.lastVisit',
102
+ 'clubhouse.memberProfile.memberActions',
103
+ 'clubhouse.memberProfile.never',
104
+ 'clubhouse.memberProfile.posts',
105
+ 'clubhouse.memberProfile.signature',
106
+ ])
107
+ }
108
+
109
+ export function navigationCopy(t: Translator): SlotCopy {
110
+ return copyFor(t, ['clubhouse.navigation.breadcrumb'])
111
+ }
112
+
113
+ export function noticeCopy(t: Translator): SlotCopy {
114
+ return copyFor(t, [
115
+ 'clubhouse.notice.dismiss',
116
+ 'clubhouse.notice.kindError',
117
+ 'clubhouse.notice.kindInfo',
118
+ 'clubhouse.notice.kindSuccess',
119
+ 'clubhouse.notice.kindWarning',
120
+ ])
121
+ }
122
+
123
+ export function paginationCopy(t: Translator): SlotCopy {
124
+ return copyFor(t, [
125
+ 'clubhouse.pagination.next',
126
+ 'clubhouse.pagination.of',
127
+ 'clubhouse.pagination.page',
128
+ 'clubhouse.pagination.pagination',
129
+ 'clubhouse.pagination.previous',
130
+ ])
131
+ }
132
+
133
+ export function postActionsCopy(t: Translator): SlotCopy {
134
+ return copyFor(t, [
135
+ 'clubhouse.postActions.edit',
136
+ 'clubhouse.postActions.moderate',
137
+ 'clubhouse.postActions.postActions',
138
+ 'clubhouse.postActions.quote',
139
+ 'clubhouse.postActions.rate',
140
+ 'clubhouse.postActions.report',
141
+ 'clubhouse.postActions.restore',
142
+ 'clubhouse.postActions.warn',
143
+ ])
144
+ }
145
+
146
+ export function postBitCopy(t: Translator): SlotCopy {
147
+ return copyFor(t, [
148
+ 'clubhouse.postBit.attachment.one',
149
+ 'clubhouse.postBit.attachment.other',
150
+ 'clubhouse.postBit.deletedPost',
151
+ 'clubhouse.postBit.ignoring',
152
+ 'clubhouse.postBit.joined',
153
+ 'clubhouse.postBit.online',
154
+ 'clubhouse.postBit.openingPost',
155
+ 'clubhouse.postBit.postHidden',
156
+ 'clubhouse.postBit.posts',
157
+ 'clubhouse.postBit.rep',
158
+ 'clubhouse.postBit.showAnyway',
159
+ 'clubhouse.postBit.staffOnly',
160
+ 'clubhouse.postBit.waitingApproval',
161
+ ])
162
+ }
163
+
164
+ export function shellCopy(t: Translator): SlotCopy {
165
+ return copyFor(t, ['clubhouse.shell.skipToContent'])
166
+ }
167
+
168
+ export function subforumListCopy(t: Translator): SlotCopy {
169
+ return copyFor(t, [
170
+ 'clubhouse.subforumList.posts',
171
+ 'clubhouse.subforumList.threads',
172
+ 'clubhouse.subforumList.title',
173
+ ])
174
+ }
175
+
176
+ export function threadRowCopy(t: Translator): SlotCopy {
177
+ return copyFor(t, [
178
+ 'clubhouse.threadRow.latestReply',
179
+ 'clubhouse.threadRow.locked',
180
+ 'clubhouse.threadRow.moved',
181
+ 'clubhouse.threadRow.newPosts',
182
+ 'clubhouse.threadRow.noRepliesYet',
183
+ 'clubhouse.threadRow.pinned',
184
+ 'clubhouse.threadRow.replies',
185
+ 'clubhouse.threadRow.startedBy',
186
+ 'clubhouse.threadRow.views',
187
+ ])
188
+ }
189
+
190
+ export function threadViewCopy(t: Translator): SlotCopy {
191
+ return copyFor(t, [
192
+ 'clubhouse.threadView.lockedNoReplies',
193
+ 'clubhouse.threadView.markRead',
194
+ 'clubhouse.threadView.moved',
195
+ 'clubhouse.threadView.pinned',
196
+ 'clubhouse.threadView.replies',
197
+ 'clubhouse.threadView.reply',
198
+ 'clubhouse.threadView.views',
199
+ ])
200
+ }
201
+
202
+ export function userPanelCopy(t: Translator): SlotCopy {
203
+ return copyFor(t, [
204
+ 'clubhouse.userPanel.new',
205
+ 'clubhouse.userPanel.signedIn',
206
+ 'clubhouse.userPanel.unread',
207
+ 'clubhouse.userPanel.unreadMessages',
208
+ 'clubhouse.userPanel.unreadNotifications',
209
+ 'clubhouse.userPanel.yourAccount',
210
+ ])
211
+ }
212
+
213
+ export function whoIsOnlineCopy(t: Translator): SlotCopy {
214
+ return copyFor(t, [
215
+ 'clubhouse.whoIsOnline.and',
216
+ 'clubhouse.whoIsOnline.guest.one',
217
+ 'clubhouse.whoIsOnline.guest.other',
218
+ 'clubhouse.whoIsOnline.heading',
219
+ 'clubhouse.whoIsOnline.invisible',
220
+ 'clubhouse.whoIsOnline.member.one',
221
+ 'clubhouse.whoIsOnline.member.other',
222
+ 'clubhouse.whoIsOnline.more',
223
+ 'clubhouse.whoIsOnline.nobody',
224
+ 'clubhouse.whoIsOnline.online',
225
+ 'clubhouse.whoIsOnline.onlyGuests',
226
+ 'clubhouse.whoIsOnline.record',
227
+ 'clubhouse.whoIsOnline.seeEveryone',
228
+ ])
229
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { LIGHT_TOKENS, DARK_TOKENS, BROWSER_THEME_COLOR } from './tokens'
2
-
1
+ export { clubhouseMessages } from './messages'
3
2
  export { clubhouseTheme } from './theme'
3
+ export { BROWSER_THEME_COLOR, DARK_TOKENS, LIGHT_TOKENS } from './tokens'
@@ -0,0 +1,126 @@
1
+ {
2
+ "clubhouse.announcement.clubNotice": "Club notice",
3
+ "clubhouse.announcement.posted": "Posted",
4
+ "clubhouse.announcement.postedBy": "Posted by",
5
+ "clubhouse.boardIndex.boardActivity": "Board activity",
6
+ "clubhouse.boardIndex.markAllRead": "Mark all forums read",
7
+ "clubhouse.boardStats.counted": "Counted",
8
+ "clubhouse.boardStats.heading": "Club record",
9
+ "clubhouse.boardStats.members": "members",
10
+ "clubhouse.boardStats.newest": "newest",
11
+ "clubhouse.boardStats.notComputed": "The board’s totals are rolled up on a schedule, and the first run has not happened.",
12
+ "clubhouse.boardStats.posts": "posts",
13
+ "clubhouse.boardStats.threads": "threads",
14
+ "clubhouse.categoryBlock.forum": "Forum",
15
+ "clubhouse.categoryBlock.lastPost": "Last post",
16
+ "clubhouse.categoryBlock.posts": "Posts",
17
+ "clubhouse.categoryBlock.threads": "Threads",
18
+ "clubhouse.footer.footerNav": "Footer",
19
+ "clubhouse.footer.timesShownIn": "Times are shown in",
20
+ "clubhouse.forumDisplay.emptyNoThread": "Nothing has been posted in this forum.",
21
+ "clubhouse.forumDisplay.emptyNoThreadFirst": "Nothing has been posted in this forum. Yours would be the first.",
22
+ "clubhouse.forumDisplay.latest": "Latest",
23
+ "clubhouse.forumDisplay.markRead": "Mark read",
24
+ "clubhouse.forumDisplay.newThread": "New thread",
25
+ "clubhouse.forumDisplay.noThreadsYet": "No threads here yet",
26
+ "clubhouse.forumDisplay.posts": "posts",
27
+ "clubhouse.forumDisplay.replies": "Replies",
28
+ "clubhouse.forumDisplay.startFirstThread": "Start the first thread",
29
+ "clubhouse.forumDisplay.thread": "Thread",
30
+ "clubhouse.forumDisplay.threads": "threads",
31
+ "clubhouse.forumDisplay.views": "Views",
32
+ "clubhouse.forumRow.link": "Link",
33
+ "clubhouse.forumRow.newPosts": "(new posts)",
34
+ "clubhouse.forumRow.noPostsYet": "No posts yet",
35
+ "clubhouse.forumRow.posts": "posts",
36
+ "clubhouse.forumRow.threads": "threads",
37
+ "clubhouse.header.boardSections": "Board sections",
38
+ "clubhouse.latestPosts.asOf": "As of",
39
+ "clubhouse.latestPosts.in": "in",
40
+ "clubhouse.latestPosts.newestPostsEmpty": "The newest posts you can see will appear here.",
41
+ "clubhouse.latestPosts.nothingSaidYet": "Nothing said yet",
42
+ "clubhouse.latestPosts.title": "Latest posts",
43
+ "clubhouse.latestThreads.asOf": "As of",
44
+ "clubhouse.latestThreads.in": "in",
45
+ "clubhouse.latestThreads.newestThreadsEmpty": "The newest threads you can see will appear here.",
46
+ "clubhouse.latestThreads.nothingStartedYet": "Nothing started yet",
47
+ "clubhouse.latestThreads.title": "Latest threads",
48
+ "clubhouse.memberProfile.about": "About",
49
+ "clubhouse.memberProfile.joined": "Joined",
50
+ "clubhouse.memberProfile.lastVisit": "Last visit",
51
+ "clubhouse.memberProfile.memberActions": "Member actions",
52
+ "clubhouse.memberProfile.never": "Never",
53
+ "clubhouse.memberProfile.posts": "Posts",
54
+ "clubhouse.memberProfile.signature": "Signature",
55
+ "clubhouse.navigation.breadcrumb": "Breadcrumb",
56
+ "clubhouse.notice.dismiss": "Dismiss",
57
+ "clubhouse.notice.kindError": "Error",
58
+ "clubhouse.notice.kindInfo": "Notice",
59
+ "clubhouse.notice.kindSuccess": "Done",
60
+ "clubhouse.notice.kindWarning": "Warning",
61
+ "clubhouse.pagination.next": "Next",
62
+ "clubhouse.pagination.of": "of",
63
+ "clubhouse.pagination.page": "Page",
64
+ "clubhouse.pagination.pagination": "Pagination",
65
+ "clubhouse.pagination.previous": "Previous",
66
+ "clubhouse.postActions.edit": "Edit",
67
+ "clubhouse.postActions.moderate": "Moderate",
68
+ "clubhouse.postActions.postActions": "Post actions",
69
+ "clubhouse.postActions.quote": "Quote",
70
+ "clubhouse.postActions.rate": "Rate",
71
+ "clubhouse.postActions.report": "Report",
72
+ "clubhouse.postActions.restore": "Restore",
73
+ "clubhouse.postActions.warn": "Warn",
74
+ "clubhouse.postBit.attachment.one": "attachment",
75
+ "clubhouse.postBit.attachment.other": "attachments",
76
+ "clubhouse.postBit.deletedPost": "Deleted post.",
77
+ "clubhouse.postBit.ignoring": "You are ignoring",
78
+ "clubhouse.postBit.joined": "Joined",
79
+ "clubhouse.postBit.online": "Online",
80
+ "clubhouse.postBit.openingPost": "Opening post",
81
+ "clubhouse.postBit.postHidden": "This post is hidden.",
82
+ "clubhouse.postBit.posts": "Posts",
83
+ "clubhouse.postBit.rep": "Rep",
84
+ "clubhouse.postBit.showAnyway": "Show it anyway",
85
+ "clubhouse.postBit.staffOnly": "Only moderators can see this.",
86
+ "clubhouse.postBit.waitingApproval": "Waiting for approval.",
87
+ "clubhouse.shell.skipToContent": "Skip to content",
88
+ "clubhouse.subforumList.posts": "posts",
89
+ "clubhouse.subforumList.threads": "threads",
90
+ "clubhouse.subforumList.title": "Subforums",
91
+ "clubhouse.threadRow.latestReply": "Latest reply",
92
+ "clubhouse.threadRow.locked": "Locked",
93
+ "clubhouse.threadRow.moved": "Moved",
94
+ "clubhouse.threadRow.newPosts": "(new posts)",
95
+ "clubhouse.threadRow.noRepliesYet": "No replies yet",
96
+ "clubhouse.threadRow.pinned": "Pinned",
97
+ "clubhouse.threadRow.replies": "replies",
98
+ "clubhouse.threadRow.startedBy": "Started by",
99
+ "clubhouse.threadRow.views": "views",
100
+ "clubhouse.threadView.lockedNoReplies": "Locked — no new replies",
101
+ "clubhouse.threadView.markRead": "Mark read",
102
+ "clubhouse.threadView.moved": "Moved",
103
+ "clubhouse.threadView.pinned": "Pinned",
104
+ "clubhouse.threadView.replies": "replies",
105
+ "clubhouse.threadView.reply": "Reply",
106
+ "clubhouse.threadView.views": "views",
107
+ "clubhouse.userPanel.new": "new",
108
+ "clubhouse.userPanel.signedIn": "Signed in",
109
+ "clubhouse.userPanel.unread": "unread",
110
+ "clubhouse.userPanel.unreadMessages": "unread messages",
111
+ "clubhouse.userPanel.unreadNotifications": "unread notifications",
112
+ "clubhouse.userPanel.yourAccount": "Your account",
113
+ "clubhouse.whoIsOnline.and": "and",
114
+ "clubhouse.whoIsOnline.guest.one": "guest",
115
+ "clubhouse.whoIsOnline.guest.other": "guests",
116
+ "clubhouse.whoIsOnline.heading": "In the clubhouse",
117
+ "clubhouse.whoIsOnline.invisible": "Invisible",
118
+ "clubhouse.whoIsOnline.member.one": "member",
119
+ "clubhouse.whoIsOnline.member.other": "members",
120
+ "clubhouse.whoIsOnline.more": "more",
121
+ "clubhouse.whoIsOnline.nobody": "Nobody is reading the board right now.",
122
+ "clubhouse.whoIsOnline.online": "online —",
123
+ "clubhouse.whoIsOnline.onlyGuests": "Only guests are reading the board right now.",
124
+ "clubhouse.whoIsOnline.record": "Record:",
125
+ "clubhouse.whoIsOnline.seeEveryone": "See everyone online"
126
+ }
@@ -0,0 +1,5 @@
1
+ import type { CatalogBundle } from '@meith/theme-kit'
2
+
3
+ import en from './en.json'
4
+
5
+ export const clubhouseMessages: CatalogBundle = { en }
package/src/shared.tsx CHANGED
@@ -1,5 +1,5 @@
1
+ import type { CountModel, PrefixModel, TimeModel, UserRefModel } from '@meith/theme-kit'
1
2
  import { Badge, cn } from '@meith/ui'
2
- import type { PrefixModel, TimeModel, UserRefModel } from '@meith/theme-kit'
3
3
 
4
4
  export const PAGE = 'mx-auto w-full max-w-6xl px-4 sm:px-6'
5
5
 
@@ -80,10 +80,10 @@ export function ColumnHeads({
80
80
  )
81
81
  }
82
82
 
83
- export function Tally({ value, label }: { value: number; label: string }) {
83
+ export function Tally({ value, label }: { value: CountModel; label: string }) {
84
84
  return (
85
85
  <span className={`${NUMERIC} text-sm font-semibold text-foreground md:text-right`}>
86
- {value.toLocaleString('en')}
86
+ {value.label}
87
87
  <span className={`${MICRO} ms-1 md:hidden`}>{label}</span>
88
88
  </span>
89
89
  )
@@ -1,16 +1,26 @@
1
+ import type { AnnouncementModel, SlotCopy } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
1
3
  import { Card, CardContent, CardFooter } from '@meith/ui'
2
- import type { AnnouncementModel } from '@meith/theme-kit'
3
4
 
4
5
  import { ClubBar, HEADING, MICRO, MUTED_LINK, Stamp, UserRef } from '../shared'
5
6
 
6
- export function Announcement({ title, bodyHtml, postedBy, postedAt, forum }: AnnouncementModel) {
7
+ export function Announcement({
8
+ title,
9
+ bodyHtml,
10
+ postedBy,
11
+ postedAt,
12
+ forum,
13
+ copy,
14
+ }: AnnouncementModel & { copy: SlotCopy }) {
15
+ const c = (key: string) => fromSlotCopy(copy, `clubhouse.announcement.${key}`)
16
+
7
17
  return (
8
18
  <Card as="article" className="flex items-stretch">
9
19
  <ClubBar />
10
20
 
11
21
  <div className="min-w-0 flex-1">
12
22
  <CardContent className="flex flex-col gap-1.5 px-4 py-3">
13
- <p className={MICRO}>Club notice</p>
23
+ <p className={MICRO}>{c('clubNotice')}</p>
14
24
 
15
25
  <h2 className={`${HEADING} text-base text-balance`}>{title}</h2>
16
26
 
@@ -20,11 +30,10 @@ export function Announcement({ title, bodyHtml, postedBy, postedAt, forum }: Ann
20
30
  <CardFooter>
21
31
  <span>
22
32
  {postedBy === null ? (
23
- 'Posted'
33
+ c('posted')
24
34
  ) : (
25
35
  <>
26
- {'Posted by '}
27
- <UserRef user={postedBy} className="text-foreground" />
36
+ {c('postedBy')} <UserRef user={postedBy} className="text-foreground" />
28
37
  </>
29
38
  )}{' '}
30
39
  <Stamp at={postedAt} />
@@ -1,9 +1,16 @@
1
+ import type { BoardIndexModel, SlotCopy } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
1
3
  import { Card, cn } from '@meith/ui'
2
- import type { BoardIndexModel } from '@meith/theme-kit'
3
4
 
4
5
  import { BUTTON, PAGE_BODY } from '../shared'
5
6
 
6
- export function BoardIndex({ markAllReadAction, regions }: BoardIndexModel) {
7
+ export function BoardIndex({
8
+ markAllReadAction,
9
+ regions,
10
+ copy,
11
+ }: BoardIndexModel & { copy: SlotCopy }) {
12
+ const c = (key: string) => fromSlotCopy(copy, `clubhouse.boardIndex.${key}`)
13
+
7
14
  const rail = (regions.latest ?? null) !== null
8
15
  const footer = regions.stats !== null || regions.online !== null
9
16
 
@@ -19,7 +26,7 @@ export function BoardIndex({ markAllReadAction, regions }: BoardIndexModel) {
19
26
  type="submit"
20
27
  className={cn(BUTTON, 'bg-secondary text-secondary-foreground hover:bg-secondary/80')}
21
28
  >
22
- Mark all forums read
29
+ {c('markAllRead')}
23
30
  </button>
24
31
  </form>
25
32
  )}
@@ -34,7 +41,7 @@ export function BoardIndex({ markAllReadAction, regions }: BoardIndexModel) {
34
41
  <div className="flex min-w-0 flex-col gap-4">{regions.categories}</div>
35
42
 
36
43
  {rail && (
37
- <aside aria-label="Board activity" className="flex min-w-0 flex-col gap-4">
44
+ <aside aria-label={c('boardActivity')} className="flex min-w-0 flex-col gap-4">
38
45
  {regions.latest}
39
46
  </aside>
40
47
  )}
@@ -1,4 +1,5 @@
1
- import type { BoardStatsModel } from '@meith/theme-kit'
1
+ import type { BoardStatsModel, SlotCopy } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
2
3
 
3
4
  import { MICRO, NUMERIC, Stamp, UserRef } from '../shared'
4
5
 
@@ -8,20 +9,21 @@ export function BoardStats({
8
9
  memberCount,
9
10
  newestMember,
10
11
  computedAt,
11
- }: BoardStatsModel) {
12
+ copy,
13
+ }: BoardStatsModel & { copy: SlotCopy }) {
14
+ const c = (key: string) => fromSlotCopy(copy, `clubhouse.boardStats.${key}`)
15
+
12
16
  return (
13
17
  <section
14
18
  aria-labelledby="board-stats-heading"
15
19
  className="flex flex-wrap items-baseline gap-x-4 gap-y-1 text-xs text-muted-foreground"
16
20
  >
17
21
  <h2 id="board-stats-heading" className={MICRO}>
18
- Club record
22
+ {c('heading')}
19
23
  </h2>
20
24
 
21
25
  {computedAt === null ? (
22
- <span>
23
- The board&rsquo;s totals are rolled up on a schedule, and the first run has not happened.
24
- </span>
26
+ <span>{c('notComputed')}</span>
25
27
  ) : (
26
28
  <>
27
29
  {[
@@ -31,20 +33,20 @@ export function BoardStats({
31
33
  ].map((figure) => (
32
34
  <span key={figure.label}>
33
35
  <span className={`font-semibold text-foreground ${NUMERIC}`}>
34
- {figure.value.toLocaleString('en')}
36
+ {figure.value.label}
35
37
  </span>{' '}
36
- {figure.label}
38
+ {c(figure.label)}
37
39
  </span>
38
40
  ))}
39
41
 
40
42
  {newestMember !== null && (
41
43
  <span>
42
- newest <UserRef user={newestMember} className="text-foreground" />
44
+ {c('newest')} <UserRef user={newestMember} className="text-foreground" />
43
45
  </span>
44
46
  )}
45
47
 
46
48
  <span className={`ms-auto ${NUMERIC}`}>
47
- Counted <Stamp at={computedAt} />
49
+ {c('counted')} <Stamp at={computedAt} />
48
50
  </span>
49
51
  </>
50
52
  )}
@@ -1,9 +1,15 @@
1
+ import type { CategoryBlockModel, SlotCopy } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
1
3
  import { Card, CardRows } from '@meith/ui'
2
- import type { CategoryBlockModel } from '@meith/theme-kit'
3
4
 
4
5
  import { ColumnHeads, PanelHead } from '../shared'
5
6
 
6
- export function CategoryBlock({ category, children }: CategoryBlockModel) {
7
+ export function CategoryBlock({
8
+ category,
9
+ children,
10
+ copy,
11
+ }: CategoryBlockModel & { copy: SlotCopy }) {
12
+ const c = (key: string) => fromSlotCopy(copy, `clubhouse.categoryBlock.${key}`)
7
13
  const headingId = `category-${category.id}`
8
14
 
9
15
  return (
@@ -19,7 +25,7 @@ export function CategoryBlock({ category, children }: CategoryBlockModel) {
19
25
  })}
20
26
  />
21
27
 
22
- <ColumnHeads first="Forum" counts={['Threads', 'Posts']} last="Last post" />
28
+ <ColumnHeads first={c('forum')} counts={[c('threads'), c('posts')]} last={c('lastPost')} />
23
29
 
24
30
  <CardRows>{children}</CardRows>
25
31
  </Card>
@@ -1,8 +1,17 @@
1
- import type { FooterModel } from '@meith/theme-kit'
1
+ import type { FooterModel, SlotCopy } from '@meith/theme-kit'
2
+ import { fromSlotCopy } from '@meith/theme-kit'
2
3
 
3
4
  import { ClubRule, HEADING, MICRO, MUTED_LINK, PAGE } from '../shared'
4
5
 
5
- export function Footer({ boardTitle, links, timezoneLabel, poweredBy }: FooterModel) {
6
+ export function Footer({
7
+ boardTitle,
8
+ links,
9
+ timezoneLabel,
10
+ poweredBy,
11
+ copy,
12
+ }: FooterModel & { copy: SlotCopy }) {
13
+ const c = (key: string) => fromSlotCopy(copy, `clubhouse.footer.${key}`)
14
+
6
15
  return (
7
16
  <footer className="mt-auto">
8
17
  <ClubRule />
@@ -13,7 +22,7 @@ export function Footer({ boardTitle, links, timezoneLabel, poweredBy }: FooterMo
13
22
  <span className={`${HEADING} text-sm text-foreground`}>{boardTitle}</span>
14
23
 
15
24
  {links.length > 0 && (
16
- <nav aria-label="Footer" className={`flex flex-wrap gap-x-4 gap-y-1 ${MICRO}`}>
25
+ <nav aria-label={c('footerNav')} className={`flex flex-wrap gap-x-4 gap-y-1 ${MICRO}`}>
17
26
  {links.map((link) => (
18
27
  <a key={link.href} href={link.href} className={MUTED_LINK}>
19
28
  {link.label}
@@ -23,7 +32,9 @@ export function Footer({ boardTitle, links, timezoneLabel, poweredBy }: FooterMo
23
32
  )}
24
33
 
25
34
  <span className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground sm:justify-end">
26
- <span>Times are shown in {timezoneLabel}</span>
35
+ <span>
36
+ {c('timesShownIn')} {timezoneLabel}
37
+ </span>
27
38
  {poweredBy && (
28
39
  <a href={poweredBy.href} className={MUTED_LINK}>
29
40
  {poweredBy.label}