@ossy/package-catalog 3.0.6 → 3.0.8

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,7 +1,7 @@
1
1
  {
2
2
  "name": "@ossy/package-catalog",
3
3
  "description": "Capability catalog UI — browse manifest packages, actions, and integration examples",
4
- "version": "3.0.6",
4
+ "version": "3.0.8",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "module": "./src/index.js",
@@ -29,5 +29,5 @@
29
29
  "@ossy/workspaces": ">=1.0.0",
30
30
  "react": ">=19.0.0 <20.0.0"
31
31
  },
32
- "gitHead": "3e1c558069c7738dd751e429c69b10e52d247d8c"
32
+ "gitHead": "980b380c9dee0de78ef3ebabe86efcec1c223a9a"
33
33
  }
package/src/Definition.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export const Definition = {
2
2
  id: 'package-catalog',
3
3
  title: 'Capabilities',
4
+ titleKey: 'package-catalog.home.title',
4
5
  description: 'Browse documented, invokable platform capabilities for your workspace.',
5
6
  icon: 'layout-grid',
6
7
  status: ['beta'],
@@ -1,5 +1,5 @@
1
1
  import React, { useMemo, useState } from 'react'
2
- import { Button, Icon, Input, Page, ContentHeader, SdkInvokeExample, Text, View } from '@ossy/design-system'
2
+ import { Button, Icon, Input, Page, ContentHeader, SdkInvokeExample, Text, View, useLocale } from '@ossy/design-system'
3
3
  import { useApp } from '@ossy/app/shell'
4
4
  import { useRouter } from '@ossy/router-react'
5
5
  import { PackageHero } from './PackageHero.jsx'
@@ -10,21 +10,22 @@ import {
10
10
  } from './ActionCapabilityCard.jsx'
11
11
  import {
12
12
  getPackageIcon,
13
- getPackagePitch,
14
13
  humanizeCapabilityId,
15
- slugToDisplayName,
14
+ resolvePackageDisplayName,
15
+ resolvePackagePitch,
16
16
  } from './packageCopy.js'
17
17
 
18
18
  /**
19
19
  * @param {object} pkg
20
+ * @param {{ t: (key: string) => string, definition?: object | null }} options
20
21
  * @returns {string}
21
22
  */
22
- function packageSearchHaystack (pkg) {
23
+ function packageSearchHaystack (pkg, { t, definition }) {
23
24
  const parts = [
24
25
  pkg.package,
25
26
  pkg.slug,
26
- slugToDisplayName(pkg.slug),
27
- getPackagePitch(pkg),
27
+ resolvePackageDisplayName(pkg, { t, definition }),
28
+ resolvePackagePitch(pkg, { t }),
28
29
  ]
29
30
 
30
31
  for (const page of pkg.pages || []) {
@@ -47,12 +48,16 @@ function packageSearchHaystack (pkg) {
47
48
  /**
48
49
  * @param {object[]} packages
49
50
  * @param {string} query
51
+ * @param {{ t: (key: string) => string, definitions?: Record<string, object> }} options
50
52
  * @returns {object[]}
51
53
  */
52
- function filterPackages (packages, query) {
54
+ function filterPackages (packages, query, { t, definitions }) {
53
55
  const normalized = query.trim().toLowerCase()
54
56
  if (!normalized) return packages
55
- return packages.filter((pkg) => packageSearchHaystack(pkg).includes(normalized))
57
+ return packages.filter((pkg) => packageSearchHaystack(pkg, {
58
+ t,
59
+ definition: definitions?.[pkg.slug],
60
+ }).includes(normalized))
56
61
  }
57
62
 
58
63
  /**
@@ -61,8 +66,10 @@ function filterPackages (packages, query) {
61
66
  export function PackagesBrowser () {
62
67
  const app = useApp()
63
68
  const router = useRouter()
69
+ const { t } = useLocale()
64
70
  const [query, setQuery] = useState('')
65
71
  const packages = app?.manifestSummary?.packages || []
72
+ const definitions = app?.manifestSummary?.definitions || {}
66
73
  const showcaseActions = useMemo(
67
74
  () => (app?.capabilities?.tools || []).slice(0, 6).map(tool => ({
68
75
  id: tool.actionId,
@@ -75,13 +82,14 @@ export function PackagesBrowser () {
75
82
  )
76
83
  const sorted = useMemo(
77
84
  () => [...packages].sort((a, b) =>
78
- slugToDisplayName(a.slug).localeCompare(slugToDisplayName(b.slug)),
85
+ resolvePackageDisplayName(a, { t, definition: definitions[a.slug] })
86
+ .localeCompare(resolvePackageDisplayName(b, { t, definition: definitions[b.slug] })),
79
87
  ),
80
- [packages],
88
+ [packages, definitions, t],
81
89
  )
82
90
  const filtered = useMemo(
83
- () => filterPackages(sorted, query),
84
- [sorted, query],
91
+ () => filterPackages(sorted, query, { t, definitions }),
92
+ [sorted, query, t, definitions],
85
93
  )
86
94
  const totalCount = sorted.length
87
95
  const visibleCount = filtered.length
@@ -240,7 +248,12 @@ export function PackagesBrowser () {
240
248
  ) : (
241
249
  <View gap="m" layout="row-wrap">
242
250
  {filtered.map((pkg) => (
243
- <PackageStoreCard key={pkg.package} pkg={pkg} router={router} />
251
+ <PackageStoreCard
252
+ key={pkg.package}
253
+ pkg={pkg}
254
+ definition={definitions[pkg.slug]}
255
+ router={router}
256
+ />
244
257
  ))}
245
258
  </View>
246
259
  )}
@@ -252,11 +265,12 @@ export function PackagesBrowser () {
252
265
  }
253
266
 
254
267
  /**
255
- * @param {{ pkg: object, router: ReturnType<typeof useRouter> }} props
268
+ * @param {{ pkg: object, definition?: object | null, router: ReturnType<typeof useRouter> }} props
256
269
  */
257
- function PackageStoreCard ({ pkg, router }) {
258
- const displayName = slugToDisplayName(pkg.slug)
259
- const pitch = getPackagePitch(pkg)
270
+ function PackageStoreCard ({ pkg, definition, router }) {
271
+ const { t } = useLocale()
272
+ const displayName = resolvePackageDisplayName(pkg, { t, definition })
273
+ const pitch = resolvePackagePitch(pkg, { t })
260
274
  const icon = getPackageIcon(pkg)
261
275
  const actionCount = pkg.actions?.length ?? 0
262
276
  const detailHref = router.getHref({
@@ -285,9 +299,14 @@ function PackageStoreCard ({ pkg, router }) {
285
299
  {pitch}
286
300
  </Text>
287
301
  {actionCount > 0 && (
288
- <Text variant="small" className="pkg-store-text-center pkg-store-text-subtle">
289
- {actionCount} action{actionCount === 1 ? '' : 's'}
290
- </Text>
302
+ <Text
303
+ variant="small"
304
+ className="pkg-store-text-center pkg-store-text-subtle"
305
+ text={actionCount === 1
306
+ ? 'package-catalog.card.actionOne'
307
+ : 'package-catalog.card.actionMany'}
308
+ params={{ count: actionCount }}
309
+ />
291
310
  )}
292
311
  </View>
293
312
  )
@@ -1,4 +1,5 @@
1
1
  {
2
+ "package-catalog.home.title": "Capabilities",
2
3
  "package-catalog.home.hero.title": "A digital toolbox for your business",
3
4
  "package-catalog.browser.title": "Capabilities",
4
5
  "package-catalog.browser.description": "Browse documented, invokable capabilities for your workspace.",
@@ -32,6 +33,8 @@
32
33
  "package-catalog.section.matching": "Matching capabilities",
33
34
  "package-catalog.section.description": "Each card opens showcase and documentation — actions, widgets, and optional pages.",
34
35
  "package-catalog.gridMeta": "Showing {visible} of {total} capabilities",
36
+ "package-catalog.card.actionOne": "{count} action",
37
+ "package-catalog.card.actionMany": "{count} actions",
35
38
  "package-catalog.emptyManifest": "Capability catalog unavailable. Rebuild the app after upgrading @ossy/platform.",
36
39
  "package-catalog.detail.notFound.title": "Capability not found",
37
40
  "package-catalog.detail.notFound.body": "No manifest entries for slug: {slug}",
@@ -80,6 +83,8 @@
80
83
  "package-catalog.detail.service.off": "disabled",
81
84
  "package-catalog.detail.service.enable": "Enable service",
82
85
  "package-catalog.detail.service.disable": "Disable service",
83
- "package-catalog.detail.meta.capabilities": "{count} capabilit{countSuffix} included",
86
+ "package-catalog.detail.meta.capabilities": "{count} capabilities included",
87
+ "package-catalog.detail.meta.capabilitiesOne": "{count} capability included",
88
+ "package-catalog.detail.meta.capabilitiesMany": "{count} capabilities included",
84
89
  "package-catalog.detail.openAPage": "Open a page"
85
90
  }
package/src/index.js CHANGED
@@ -4,6 +4,8 @@ export { CatalogPageStyles } from './CatalogPageStyles.jsx'
4
4
  export {
5
5
  slugToDisplayName,
6
6
  getPackagePitch,
7
+ resolvePackageDisplayName,
8
+ resolvePackagePitch,
7
9
  getPackageIcon,
8
10
  humanizeCapabilityId,
9
11
  } from './packageCopy.js'
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import { Icon, Page, ContentHeader, Text, View } from '@ossy/design-system'
2
+ import { Icon, Page, ContentHeader, Text, View, useLocale } from '@ossy/design-system'
3
3
  import { useApp } from '@ossy/app/shell'
4
4
  import { useRouter } from '@ossy/router-react'
5
5
  import { countPackageCapabilities } from './formatPaths.js'
@@ -19,8 +19,8 @@ import { CatalogPageStyles } from './CatalogPageStyles.jsx'
19
19
  import {
20
20
  filterBackgroundTasks,
21
21
  getPackageIcon,
22
- getPackagePitch,
23
- slugToDisplayName,
22
+ resolvePackageDisplayName,
23
+ resolvePackagePitch,
24
24
  } from './packageCopy.js'
25
25
 
26
26
  export const metadata = {
@@ -35,6 +35,7 @@ export const metadata = {
35
35
  export default function PackageDetailPage () {
36
36
  const app = useApp()
37
37
  const router = useRouter()
38
+ const { t } = useLocale()
38
39
  const slug = router.params.packageSlug
39
40
  const pkg = app?.manifestSummary?.packages?.find((p) => p.slug === slug)
40
41
 
@@ -47,10 +48,10 @@ export default function PackageDetailPage () {
47
48
  )
48
49
  }
49
50
 
50
- const displayName = slugToDisplayName(pkg.slug)
51
- const pitch = getPackagePitch(pkg)
52
- const packageIcon = getPackageIcon(pkg)
53
51
  const packageDefinition = app?.manifestSummary?.definitions?.[pkg.slug]
52
+ const displayName = resolvePackageDisplayName(pkg, { t, definition: packageDefinition })
53
+ const pitch = resolvePackagePitch(pkg, { t })
54
+ const packageIcon = getPackageIcon(pkg)
54
55
  const totalCapabilities = countPackageCapabilities(pkg)
55
56
  const backgroundTasks = filterBackgroundTasks(pkg.tasks)
56
57
 
@@ -75,10 +76,11 @@ export default function PackageDetailPage () {
75
76
  eyebrow={pkg.package}
76
77
  title={displayName}
77
78
  text={pitch}
78
- meta="package-catalog.detail.meta.capabilities"
79
+ meta={totalCapabilities === 1
80
+ ? 'package-catalog.detail.meta.capabilitiesOne'
81
+ : 'package-catalog.detail.meta.capabilitiesMany'}
79
82
  metaParams={{
80
83
  count: totalCapabilities,
81
- countSuffix: totalCapabilities === 1 ? 'y' : 'ies',
82
84
  }}
83
85
  maxWidth="l"
84
86
  />
@@ -119,7 +121,7 @@ export default function PackageDetailPage () {
119
121
  )}
120
122
 
121
123
  {documentedActions.length > 0 && (
122
- <PackageActionsSection far={documentedActions} />
124
+ <PackageActionsSection actions={documentedActions} />
123
125
  )}
124
126
 
125
127
  {backgroundTasks.length > 0 && (
@@ -65,7 +65,7 @@ const PITCH_BY_SLUG = {
65
65
  storage: 'Files, folders, and workspace storage',
66
66
  tasks: 'Tasks, checklists, and async work',
67
67
  themes: 'Themes, branding, and appearance',
68
- timesheets: 'Time tracking and billable hours',
68
+ timesheets: 'Generate, review, save, and export monthly work hours',
69
69
  tokens: 'Design tokens and theme variables',
70
70
  tools: 'Developer tools and utilities',
71
71
  users: 'Users, accounts, and membership',
@@ -107,6 +107,54 @@ export function getPackagePitch (pkg) {
107
107
  return `${name} — Ossy feature package`
108
108
  }
109
109
 
110
+ /**
111
+ * Translate a key, or return null when the catalog has no entry (key echoes back).
112
+ *
113
+ * @param {(key: string, params?: Record<string, string | number>) => string} t
114
+ * @param {string | null | undefined} key
115
+ * @returns {string | null}
116
+ */
117
+ function translateOrNull (t, key) {
118
+ if (!key || typeof t !== 'function') return null
119
+ const translated = t(key)
120
+ return translated && translated !== key ? translated : null
121
+ }
122
+
123
+ /**
124
+ * Localized package title for catalog cards / detail hero.
125
+ * Prefer Definition.titleKey, then `{slug}.home.title`, then English fallbacks.
126
+ *
127
+ * @param {{ slug?: string }} pkg
128
+ * @param {{ t?: (key: string) => string, definition?: { title?: string, titleKey?: string } | null }} [options]
129
+ * @returns {string}
130
+ */
131
+ export function resolvePackageDisplayName (pkg, options = {}) {
132
+ const { t, definition } = options
133
+ const slug = pkg?.slug
134
+ const fromKey = translateOrNull(t, definition?.titleKey)
135
+ || translateOrNull(t, slug ? `${slug}.home.title` : null)
136
+ if (fromKey) return fromKey
137
+ if (definition?.title) return definition.title
138
+ return slugToDisplayName(slug || '')
139
+ }
140
+
141
+ /**
142
+ * Localized package pitch for catalog cards / detail hero.
143
+ * Prefer `{slug}.catalog.pitch`, then `{slug}.home.description`, then English PITCH_BY_SLUG.
144
+ *
145
+ * @param {{ slug?: string, package?: string }} pkg
146
+ * @param {{ t?: (key: string) => string }} [options]
147
+ * @returns {string}
148
+ */
149
+ export function resolvePackagePitch (pkg, options = {}) {
150
+ const { t } = options
151
+ const slug = pkg?.slug
152
+ const fromKey = translateOrNull(t, slug ? `${slug}.catalog.pitch` : null)
153
+ || translateOrNull(t, slug ? `${slug}.home.description` : null)
154
+ if (fromKey) return fromKey
155
+ return getPackagePitch(pkg)
156
+ }
157
+
110
158
  /** Known background task descriptions for package detail showcase. */
111
159
  export const SHOWCASE_TASK_DESCRIPTIONS = {
112
160
  '@ossy/booking/tasks/send-reminder': 'Hourly cron — emails clients ~24h before their booking.',
@@ -1,4 +1,5 @@
1
1
  {
2
+ "package-catalog.home.title": "Funktioner",
2
3
  "package-catalog.home.hero.title": "En digital verktygslåda för ditt företag",
3
4
  "package-catalog.browser.title": "Funktioner",
4
5
  "package-catalog.browser.description": "Bläddra bland dokumenterade, anropbara capabilities för din workspace.",
@@ -32,6 +33,8 @@
32
33
  "package-catalog.section.matching": "Matchande funktioner",
33
34
  "package-catalog.section.description": "Varje kort öppnar showcase och dokumentation — actions, widgets och valfria sidor.",
34
35
  "package-catalog.gridMeta": "Visar {visible} av {total} funktioner",
36
+ "package-catalog.card.actionOne": "{count} action",
37
+ "package-catalog.card.actionMany": "{count} actions",
35
38
  "package-catalog.emptyManifest": "Funktionskatalogen är inte tillgänglig. Bygg om appen efter uppgradering av @ossy/platform.",
36
39
  "package-catalog.detail.notFound.title": "Funktionen hittades inte",
37
40
  "package-catalog.detail.notFound.body": "Inga manifestposter för slug: {slug}",
@@ -80,6 +83,8 @@
80
83
  "package-catalog.detail.service.off": "inaktiverad",
81
84
  "package-catalog.detail.service.enable": "Aktivera tjänst",
82
85
  "package-catalog.detail.service.disable": "Inaktivera tjänst",
83
- "package-catalog.detail.meta.capabilities": "{count} funktion{countSuffix} ingår",
86
+ "package-catalog.detail.meta.capabilities": "{count} funktioner ingår",
87
+ "package-catalog.detail.meta.capabilitiesOne": "{count} funktion ingår",
88
+ "package-catalog.detail.meta.capabilitiesMany": "{count} funktioner ingår",
84
89
  "package-catalog.detail.openAPage": "Öppna en sida"
85
90
  }