@cavuno/board 1.33.0 → 1.33.1
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/dist/bin.mjs +7 -8
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
- package/skills/cavuno-board-i18n/SKILL.md +82 -0
- package/skills/manifest.json +8 -1
package/dist/bin.mjs
CHANGED
|
@@ -40,6 +40,9 @@ function record(id, tier) {
|
|
|
40
40
|
var ENV_API_URL = record("env.api-url", 1);
|
|
41
41
|
var ENV_BOARD_KEY = record("env.board-key", 1);
|
|
42
42
|
var PK_RE = /^pk_[0-9a-f]{32}$/;
|
|
43
|
+
function apiBase(apiUrl) {
|
|
44
|
+
return apiUrl.replace(/\/+$/, "");
|
|
45
|
+
}
|
|
43
46
|
function checkEnv(env) {
|
|
44
47
|
const results = [];
|
|
45
48
|
if (!env.apiUrl) {
|
|
@@ -307,8 +310,7 @@ async function observeVerificationEmail(fetchImpl, apiKey, recipient, pollMs, ti
|
|
|
307
310
|
}
|
|
308
311
|
async function runWriteProbes(options) {
|
|
309
312
|
const { fetchImpl } = options;
|
|
310
|
-
const
|
|
311
|
-
const base = `${origin}/v1/boards/${encodeURIComponent(options.env.boardKey)}`;
|
|
313
|
+
const base = `${apiBase(options.env.apiUrl)}/v1/boards/${encodeURIComponent(options.env.boardKey)}`;
|
|
312
314
|
const nonce = options.nonce ?? `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
313
315
|
const email = `doctor+${nonce}@cavuno.com`;
|
|
314
316
|
const password = `doctor-probe-${nonce}-A1!`;
|
|
@@ -429,14 +431,12 @@ var STATIC_API = record("static.api", 1);
|
|
|
429
431
|
var STATIC_BOARD = record("static.board", 1);
|
|
430
432
|
var STATIC_SKILLS = record("static.skills", 1);
|
|
431
433
|
async function checkApiReachable(fetchImpl, apiUrl) {
|
|
432
|
-
let origin;
|
|
433
434
|
try {
|
|
434
|
-
|
|
435
|
+
new URL(apiUrl);
|
|
435
436
|
} catch {
|
|
436
437
|
return null;
|
|
437
438
|
}
|
|
438
|
-
const
|
|
439
|
-
const result = spec.ok ? spec : await probe(fetchImpl, `${origin}/v1/openapi.json`);
|
|
439
|
+
const result = await probe(fetchImpl, `${apiBase(apiUrl)}/v1/openapi.json`);
|
|
440
440
|
if (!result.ok) {
|
|
441
441
|
return STATIC_API(
|
|
442
442
|
"fail",
|
|
@@ -455,10 +455,9 @@ async function checkApiReachable(fetchImpl, apiUrl) {
|
|
|
455
455
|
return STATIC_API("pass", "OpenAPI spec reachable");
|
|
456
456
|
}
|
|
457
457
|
async function checkBoardResolves(fetchImpl, env) {
|
|
458
|
-
const origin = new URL(env.apiUrl).origin;
|
|
459
458
|
const result = await probe(
|
|
460
459
|
fetchImpl,
|
|
461
|
-
`${
|
|
460
|
+
`${apiBase(env.apiUrl)}/v1/boards/${encodeURIComponent(env.boardKey)}`
|
|
462
461
|
);
|
|
463
462
|
if (!result.ok) {
|
|
464
463
|
return {
|
package/dist/index.d.mts
CHANGED
|
@@ -204,7 +204,7 @@ declare function paginate<Q extends Record<string, unknown>, P extends PageShape
|
|
|
204
204
|
* constant because the package is platform-neutral and cannot read
|
|
205
205
|
* package.json at runtime.
|
|
206
206
|
*/
|
|
207
|
-
declare const SDK_VERSION = "1.33.
|
|
207
|
+
declare const SDK_VERSION = "1.33.1";
|
|
208
208
|
|
|
209
209
|
type SavedJob = Schemas['SavedJob'];
|
|
210
210
|
type SavedJobsListQuery = {
|
package/dist/index.d.ts
CHANGED
|
@@ -204,7 +204,7 @@ declare function paginate<Q extends Record<string, unknown>, P extends PageShape
|
|
|
204
204
|
* constant because the package is platform-neutral and cannot read
|
|
205
205
|
* package.json at runtime.
|
|
206
206
|
*/
|
|
207
|
-
declare const SDK_VERSION = "1.33.
|
|
207
|
+
declare const SDK_VERSION = "1.33.1";
|
|
208
208
|
|
|
209
209
|
type SavedJob = Schemas['SavedJob'];
|
|
210
210
|
type SavedJobsListQuery = {
|
package/dist/index.js
CHANGED
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cavuno-board-i18n
|
|
3
|
+
description: Multi-language chrome for a board frontend — the single-language board model, the uiCopy catalog ⊕ operator labels ⊕ generated copy layering, the one copy seam, and how to add path-prefixed locales (/de/, /fr/) with Paraglide JS on TanStack Start. Use when localizing a starter's chrome, adding a language, or wiring locale routing. Do NOT use to translate job content — that stays the board's language.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Board i18n: localize the chrome, never the content
|
|
7
|
+
|
|
8
|
+
A Cavuno board has **one content language** (`board.context().language`).
|
|
9
|
+
Job titles, descriptions, and company text are that single language and
|
|
10
|
+
never translated by the frontend. What you localize is the **chrome** —
|
|
11
|
+
labels, headings, FAQ scaffolding, filter vocabulary — plus the entity
|
|
12
|
+
data the API already translates (taxonomy, places, salary names).
|
|
13
|
+
|
|
14
|
+
So there are two separate things:
|
|
15
|
+
- **Content** (jobs, companies, blog): board-language, API-served, single.
|
|
16
|
+
- **Chrome** (your authored UI strings): can be multi-locale, path-routed.
|
|
17
|
+
|
|
18
|
+
## The copy layers (resolve in order)
|
|
19
|
+
|
|
20
|
+
1. **`uiCopy(language)`** — the versioned SDK catalog (`@cavuno/board/format`,
|
|
21
|
+
`en`/`de`/`fr`, functions-per-key for plurals; no runtime, no provider).
|
|
22
|
+
The floor.
|
|
23
|
+
2. **`⊕ board.context().labels`** — the operator's per-board overrides,
|
|
24
|
+
API-served (e.g. `featuredLabel` → "Top Job").
|
|
25
|
+
3. **`⊕ generated code`** — per-board copy the AI builder ejects to native
|
|
26
|
+
code (ADR-0059 layer 3). Supersedes the catalog for that board.
|
|
27
|
+
|
|
28
|
+
Every surface resolves copy through **one seam module** (`src/copy.ts`),
|
|
29
|
+
never by calling the catalog inline:
|
|
30
|
+
|
|
31
|
+
```ts snippet
|
|
32
|
+
import { uiCopy, type BoardLabelOverrides } from '@cavuno/board/format'
|
|
33
|
+
export function boardCopy(language: string | undefined, labels?: BoardLabelOverrides) {
|
|
34
|
+
return uiCopy(language, labels)
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The seam is the **single swap point** for the whole copy lifecycle: catalog
|
|
39
|
+
today → Paraglide messages (below) → builder-generated code later. Components
|
|
40
|
+
never change; only the seam's backing source does.
|
|
41
|
+
|
|
42
|
+
## Add path-prefixed locales with Paraglide JS (TanStack Start)
|
|
43
|
+
|
|
44
|
+
The starters are TanStack Start, and the TanStack-recommended i18n is
|
|
45
|
+
**Paraglide JS** — compile-time (messages become tree-shakeable functions),
|
|
46
|
+
**no runtime provider** (matches the catalog's no-provider stance), with
|
|
47
|
+
built-in locale routing. The board's language is served unprefixed at `/`;
|
|
48
|
+
extra locales are prefixed (`/de/`, `/fr/`).
|
|
49
|
+
|
|
50
|
+
1. **Init.** `npx @inlang/paraglide-js@latest init` — creates the inlang
|
|
51
|
+
project, `messages/{en,de,fr}.json`, the `paraglide/` output, and the Vite
|
|
52
|
+
plugin. Add the plugin to `vite.config.ts`.
|
|
53
|
+
2. **Generate messages FROM `uiCopy` — never hand-translate.** The catalog is
|
|
54
|
+
the source of truth. Emit `messages/{locale}.json` from
|
|
55
|
+
`uiCopy(locale)` so `de`/`fr` come from the reviewed SDK catalog, not a
|
|
56
|
+
second copy. (This generator is the `uiCopy → Paraglide` adapter.)
|
|
57
|
+
3. **Route.** Wire the router's `rewrite` with Paraglide's `deLocalizeUrl` /
|
|
58
|
+
`localizeUrl` so `/de/jobs` routes internally as `/jobs` and every `Link`
|
|
59
|
+
auto-prefixes the active locale. Default-locale-no-prefix: the board
|
|
60
|
+
language stays at `/`.
|
|
61
|
+
4. **SSR.** Add `paraglideMiddleware` in the server entry; set `<html lang>`
|
|
62
|
+
from `getLocale()` (it replaces the static `board.language` in the root).
|
|
63
|
+
5. **Seam.** Point `boardCopy` at the URL locale (`getLocale()`) instead of
|
|
64
|
+
only `board.language`, so `/de/` renders German chrome even on an English
|
|
65
|
+
board. This is the opt-in that ADR-0063 adds over the single-language model.
|
|
66
|
+
6. **SEO slice.** Per-locale `hreflang`, canonical, and sitemap entries are
|
|
67
|
+
part of completing multi-language — do them when you add real locales, not
|
|
68
|
+
in the foundation.
|
|
69
|
+
|
|
70
|
+
## Do not
|
|
71
|
+
|
|
72
|
+
- **Do not translate job content** (titles/descriptions). Fully multi-language
|
|
73
|
+
listings is a data-model change, out of scope.
|
|
74
|
+
- **Do not hand-author translations** in the starter — generate messages from
|
|
75
|
+
`uiCopy` so there is one reviewed source.
|
|
76
|
+
- **Do not add a runtime i18n provider** (e.g. use-intl's context). Paraglide
|
|
77
|
+
is compile-time; keep the no-provider property.
|
|
78
|
+
- **Do not scatter catalog calls** through markup — everything imports the one
|
|
79
|
+
seam, so the catalog→code migration is a single-file change.
|
|
80
|
+
|
|
81
|
+
Reference: ADR-0063 (starter i18n), ADR-0059 (copy catalog), ADR-0010 (entity
|
|
82
|
+
translation). Wiring flavor: `cavuno-board-tanstack-start`.
|
package/skills/manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.33.
|
|
2
|
+
"version": "1.33.1",
|
|
3
3
|
"skills": [
|
|
4
4
|
{
|
|
5
5
|
"name": "cavuno-board-account",
|
|
@@ -64,6 +64,13 @@
|
|
|
64
64
|
"framework": null,
|
|
65
65
|
"category": "core"
|
|
66
66
|
},
|
|
67
|
+
{
|
|
68
|
+
"name": "cavuno-board-i18n",
|
|
69
|
+
"description": "Multi-language chrome for a board frontend — the single-language board model, the uiCopy catalog ⊕ operator labels ⊕ generated copy layering, the one copy seam, and how to add path-prefixed locales (/de/, /fr/) with Paraglide JS on TanStack Start. Use when localizing a starter's chrome, adding a language, or wiring locale routing. Do NOT use to translate job content — that stays the board's language.",
|
|
70
|
+
"path": "skills/cavuno-board-i18n/SKILL.md",
|
|
71
|
+
"framework": null,
|
|
72
|
+
"category": "core"
|
|
73
|
+
},
|
|
67
74
|
{
|
|
68
75
|
"name": "cavuno-board-job-alerts",
|
|
69
76
|
"description": "Build job-alert flows with the @cavuno/board SDK — the anonymous double-opt-in surface (jobAlerts.subscribe/confirm/resendConfirmation + HMAC-token manage/unsubscribe/resubscribe/updatePreference/deletePreference) and the authenticated board.me.alerts CRUD. Covers which surface to use when, how the manage token rides, which filters actually scope delivery, and the full-replace update trap.",
|