@cavuno/board 1.33.0 → 1.34.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/dist/bin.mjs +73 -19
- package/dist/{board-BTVapQiL.d.mts → board-DgDC0T4g.d.mts} +1 -1
- package/dist/{board-0yI5ZRJw.d.ts → board-Id19Yg8-.d.ts} +1 -1
- package/dist/filters.d.mts +1 -1
- package/dist/filters.d.ts +1 -1
- package/dist/format.d.mts +2 -2
- package/dist/format.d.ts +2 -2
- package/dist/index.d.mts +22 -6
- package/dist/index.d.ts +22 -6
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{jobs-Di4AV-02.d.mts → jobs-IIJtDgzX.d.mts} +138 -2
- package/dist/{jobs-Di4AV-02.d.ts → jobs-IIJtDgzX.d.ts} +138 -2
- package/dist/paths.d.mts +72 -0
- package/dist/paths.d.ts +72 -0
- package/dist/paths.js +99 -0
- package/dist/paths.mjs +78 -0
- package/dist/{salaries-D-BGZpDC.d.mts → salaries-BfEEEOHj.d.mts} +1 -1
- package/dist/{salaries-B-zJKjkk.d.ts → salaries-CL_00fNX.d.ts} +1 -1
- package/dist/seo.d.mts +3 -3
- package/dist/seo.d.ts +3 -3
- package/dist/server.d.mts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/sitemap.d.mts +3 -3
- package/dist/sitemap.d.ts +3 -3
- package/dist/sitemap.js +89 -29
- package/dist/sitemap.mjs +89 -29
- package/package.json +11 -1
- package/skills/cavuno-board-i18n/SKILL.md +82 -0
- package/skills/manifest.json +8 -1
package/dist/sitemap.js
CHANGED
|
@@ -164,6 +164,65 @@ function paginate(listFn, query, options) {
|
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
// src/paths/index.ts
|
|
168
|
+
function jobDetailPath(companySlug, jobSlug) {
|
|
169
|
+
return `/companies/${companySlug}/jobs/${jobSlug}`;
|
|
170
|
+
}
|
|
171
|
+
function jobsCategoryPath(categorySlug) {
|
|
172
|
+
return `/jobs/${categorySlug}`;
|
|
173
|
+
}
|
|
174
|
+
function jobsSkillPath(skillSlug) {
|
|
175
|
+
return `/jobs/skills/${skillSlug}`;
|
|
176
|
+
}
|
|
177
|
+
function jobsLocationPath(placeSlug) {
|
|
178
|
+
return `/jobs/locations/${placeSlug}`;
|
|
179
|
+
}
|
|
180
|
+
function companyPath(companySlug) {
|
|
181
|
+
return `/companies/${companySlug}`;
|
|
182
|
+
}
|
|
183
|
+
function companyMarketPath(marketSlug) {
|
|
184
|
+
return `/companies/markets/${marketSlug}`;
|
|
185
|
+
}
|
|
186
|
+
function companySalaryPath(companySlug) {
|
|
187
|
+
return `/companies/${companySlug}/salaries`;
|
|
188
|
+
}
|
|
189
|
+
function salaryTitlePath(titleSlug) {
|
|
190
|
+
return `/salaries/titles/${titleSlug}`;
|
|
191
|
+
}
|
|
192
|
+
function salarySkillPath(skillSlug) {
|
|
193
|
+
return `/salaries/skills/${skillSlug}`;
|
|
194
|
+
}
|
|
195
|
+
function salaryLocationPath(placeSlug) {
|
|
196
|
+
return `/salaries/locations/${placeSlug}`;
|
|
197
|
+
}
|
|
198
|
+
function blogPostPath(postSlug) {
|
|
199
|
+
return `/blog/${postSlug}`;
|
|
200
|
+
}
|
|
201
|
+
function blogTagPath(tagSlug) {
|
|
202
|
+
return `/blog/tag/${tagSlug}`;
|
|
203
|
+
}
|
|
204
|
+
function blogAuthorPath(authorSlug) {
|
|
205
|
+
return `/blog/author/${authorSlug}`;
|
|
206
|
+
}
|
|
207
|
+
var BOARD_PATHS = {
|
|
208
|
+
home: "/",
|
|
209
|
+
jobs: "/jobs",
|
|
210
|
+
companies: "/companies",
|
|
211
|
+
salaries: "/salaries",
|
|
212
|
+
salaryCompanies: "/salaries/companies",
|
|
213
|
+
salaryTitles: "/salaries/titles",
|
|
214
|
+
salarySkills: "/salaries/skills",
|
|
215
|
+
salaryLocations: "/salaries/locations",
|
|
216
|
+
blog: "/blog",
|
|
217
|
+
about: "/about",
|
|
218
|
+
privacyPolicy: "/privacy-policy",
|
|
219
|
+
termsOfService: "/terms-of-service",
|
|
220
|
+
cookiePolicy: "/cookie-policy",
|
|
221
|
+
impressum: "/impressum",
|
|
222
|
+
talent: "/talent",
|
|
223
|
+
employers: "/employers"
|
|
224
|
+
};
|
|
225
|
+
|
|
167
226
|
// src/sitemap/walker.ts
|
|
168
227
|
var MIN_JOBS_PER_INDEXED_PAGE = 5;
|
|
169
228
|
var MAX_PAGES = 200;
|
|
@@ -246,16 +305,16 @@ async function buildBucketUrls(board, origin, bucket) {
|
|
|
246
305
|
async function marketing(board, origin) {
|
|
247
306
|
const { features } = await board.context();
|
|
248
307
|
const urls = [
|
|
249
|
-
`${origin}
|
|
250
|
-
`${origin}
|
|
251
|
-
`${origin}
|
|
252
|
-
`${origin}
|
|
253
|
-
`${origin}
|
|
254
|
-
`${origin}
|
|
308
|
+
`${origin}${BOARD_PATHS.home}`,
|
|
309
|
+
`${origin}${BOARD_PATHS.jobs}`,
|
|
310
|
+
`${origin}${BOARD_PATHS.about}`,
|
|
311
|
+
`${origin}${BOARD_PATHS.privacyPolicy}`,
|
|
312
|
+
`${origin}${BOARD_PATHS.termsOfService}`,
|
|
313
|
+
`${origin}${BOARD_PATHS.cookiePolicy}`
|
|
255
314
|
];
|
|
256
|
-
if (features.impressum) urls.push(`${origin}
|
|
257
|
-
if (features.talentDirectory) urls.push(`${origin}
|
|
258
|
-
if (features.employers) urls.push(`${origin}
|
|
315
|
+
if (features.impressum) urls.push(`${origin}${BOARD_PATHS.impressum}`);
|
|
316
|
+
if (features.talentDirectory) urls.push(`${origin}${BOARD_PATHS.talent}`);
|
|
317
|
+
if (features.employers) urls.push(`${origin}${BOARD_PATHS.employers}`);
|
|
259
318
|
return urls;
|
|
260
319
|
}
|
|
261
320
|
async function jobsTaxonomy(board, origin, kind) {
|
|
@@ -267,20 +326,20 @@ async function jobsTaxonomy(board, origin, kind) {
|
|
|
267
326
|
);
|
|
268
327
|
for (const slug of slugs) counts.set(slug, (counts.get(slug) ?? 0) + 1);
|
|
269
328
|
}
|
|
270
|
-
const
|
|
271
|
-
return [...counts.entries()].filter(([, n]) => n >= MIN_JOBS_PER_INDEXED_PAGE).map(([slug]) => slug).sort().map((slug) => `${
|
|
329
|
+
const toPath = kind === "categories" ? jobsCategoryPath : jobsSkillPath;
|
|
330
|
+
return [...counts.entries()].filter(([, n]) => n >= MIN_JOBS_PER_INDEXED_PAGE).map(([slug]) => slug).sort().map((slug) => `${origin}${toPath(slug)}`);
|
|
272
331
|
}
|
|
273
332
|
async function jobsLocations(board, origin) {
|
|
274
333
|
const { language } = await board.context();
|
|
275
334
|
const { data } = await board.salaries.locations.list({ locale: language });
|
|
276
|
-
return data.filter((l) => l.jobCount >= MIN_JOBS_PER_INDEXED_PAGE).map((l) => l.placeSlug).sort().map((slug) => `${origin}
|
|
335
|
+
return data.filter((l) => l.jobCount >= MIN_JOBS_PER_INDEXED_PAGE).map((l) => l.placeSlug).sort().map((slug) => `${origin}${jobsLocationPath(slug)}`);
|
|
277
336
|
}
|
|
278
337
|
async function jobDetails(board, origin) {
|
|
279
338
|
const jobs = await enumerateJobs(board);
|
|
280
339
|
const seen = /* @__PURE__ */ new Set();
|
|
281
340
|
for (const job of jobs) {
|
|
282
341
|
if (!job.company?.slug || !job.slug) continue;
|
|
283
|
-
seen.add(`${origin}
|
|
342
|
+
seen.add(`${origin}${jobDetailPath(job.company.slug, job.slug)}`);
|
|
284
343
|
}
|
|
285
344
|
return [...seen].sort();
|
|
286
345
|
}
|
|
@@ -289,10 +348,11 @@ async function companies(board, origin) {
|
|
|
289
348
|
enumerateCompanies(board),
|
|
290
349
|
board.companies.markets()
|
|
291
350
|
]);
|
|
292
|
-
const urls = [`${origin}
|
|
293
|
-
for (const company of list)
|
|
351
|
+
const urls = [`${origin}${BOARD_PATHS.companies}`];
|
|
352
|
+
for (const company of list)
|
|
353
|
+
urls.push(`${origin}${companyPath(company.slug)}`);
|
|
294
354
|
for (const market of markets.data) {
|
|
295
|
-
urls.push(`${origin}
|
|
355
|
+
urls.push(`${origin}${companyMarketPath(market.slug)}`);
|
|
296
356
|
}
|
|
297
357
|
return urls;
|
|
298
358
|
}
|
|
@@ -307,27 +367,27 @@ async function salaries(board, origin) {
|
|
|
307
367
|
if (!salaryCompanies.data.length && !titles.data.length && !skills.data.length && !locations.data.length) {
|
|
308
368
|
return [];
|
|
309
369
|
}
|
|
310
|
-
const urls = [`${origin}
|
|
370
|
+
const urls = [`${origin}${BOARD_PATHS.salaries}`];
|
|
311
371
|
if (salaryCompanies.data.length) {
|
|
312
|
-
urls.push(`${origin}
|
|
372
|
+
urls.push(`${origin}${BOARD_PATHS.salaryCompanies}`);
|
|
313
373
|
for (const c of salaryCompanies.data) {
|
|
314
|
-
urls.push(`${origin}
|
|
374
|
+
urls.push(`${origin}${companySalaryPath(c.companySlug)}`);
|
|
315
375
|
}
|
|
316
376
|
}
|
|
317
377
|
if (titles.data.length) {
|
|
318
|
-
urls.push(`${origin}
|
|
378
|
+
urls.push(`${origin}${BOARD_PATHS.salaryTitles}`);
|
|
319
379
|
for (const t of titles.data)
|
|
320
|
-
urls.push(`${origin}
|
|
380
|
+
urls.push(`${origin}${salaryTitlePath(t.slug)}`);
|
|
321
381
|
}
|
|
322
382
|
if (skills.data.length) {
|
|
323
|
-
urls.push(`${origin}
|
|
383
|
+
urls.push(`${origin}${BOARD_PATHS.salarySkills}`);
|
|
324
384
|
for (const s of skills.data)
|
|
325
|
-
urls.push(`${origin}
|
|
385
|
+
urls.push(`${origin}${salarySkillPath(s.slug)}`);
|
|
326
386
|
}
|
|
327
387
|
if (locations.data.length) {
|
|
328
|
-
urls.push(`${origin}
|
|
388
|
+
urls.push(`${origin}${BOARD_PATHS.salaryLocations}`);
|
|
329
389
|
for (const l of locations.data) {
|
|
330
|
-
urls.push(`${origin}
|
|
390
|
+
urls.push(`${origin}${salaryLocationPath(l.placeSlug)}`);
|
|
331
391
|
}
|
|
332
392
|
}
|
|
333
393
|
return urls;
|
|
@@ -336,18 +396,18 @@ async function blog(board, origin) {
|
|
|
336
396
|
const posts = await drainPages(
|
|
337
397
|
paginate(board.blog.posts.list, { limit: 100 }).pages()
|
|
338
398
|
);
|
|
339
|
-
const urls = [`${origin}
|
|
399
|
+
const urls = [`${origin}${BOARD_PATHS.blog}`];
|
|
340
400
|
const tagSlugs = /* @__PURE__ */ new Set();
|
|
341
401
|
const authorSlugs = /* @__PURE__ */ new Set();
|
|
342
402
|
for (const post of posts) {
|
|
343
|
-
urls.push(`${origin}
|
|
403
|
+
urls.push(`${origin}${blogPostPath(post.slug)}`);
|
|
344
404
|
for (const tag of post.tags) tagSlugs.add(tag.slug);
|
|
345
405
|
for (const author of post.authors) authorSlugs.add(author.slug);
|
|
346
406
|
}
|
|
347
407
|
for (const slug of [...tagSlugs].sort())
|
|
348
|
-
urls.push(`${origin}
|
|
408
|
+
urls.push(`${origin}${blogTagPath(slug)}`);
|
|
349
409
|
for (const slug of [...authorSlugs].sort()) {
|
|
350
|
-
urls.push(`${origin}
|
|
410
|
+
urls.push(`${origin}${blogAuthorPath(slug)}`);
|
|
351
411
|
}
|
|
352
412
|
return urls;
|
|
353
413
|
}
|
package/dist/sitemap.mjs
CHANGED
|
@@ -128,6 +128,65 @@ function paginate(listFn, query, options) {
|
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// src/paths/index.ts
|
|
132
|
+
function jobDetailPath(companySlug, jobSlug) {
|
|
133
|
+
return `/companies/${companySlug}/jobs/${jobSlug}`;
|
|
134
|
+
}
|
|
135
|
+
function jobsCategoryPath(categorySlug) {
|
|
136
|
+
return `/jobs/${categorySlug}`;
|
|
137
|
+
}
|
|
138
|
+
function jobsSkillPath(skillSlug) {
|
|
139
|
+
return `/jobs/skills/${skillSlug}`;
|
|
140
|
+
}
|
|
141
|
+
function jobsLocationPath(placeSlug) {
|
|
142
|
+
return `/jobs/locations/${placeSlug}`;
|
|
143
|
+
}
|
|
144
|
+
function companyPath(companySlug) {
|
|
145
|
+
return `/companies/${companySlug}`;
|
|
146
|
+
}
|
|
147
|
+
function companyMarketPath(marketSlug) {
|
|
148
|
+
return `/companies/markets/${marketSlug}`;
|
|
149
|
+
}
|
|
150
|
+
function companySalaryPath(companySlug) {
|
|
151
|
+
return `/companies/${companySlug}/salaries`;
|
|
152
|
+
}
|
|
153
|
+
function salaryTitlePath(titleSlug) {
|
|
154
|
+
return `/salaries/titles/${titleSlug}`;
|
|
155
|
+
}
|
|
156
|
+
function salarySkillPath(skillSlug) {
|
|
157
|
+
return `/salaries/skills/${skillSlug}`;
|
|
158
|
+
}
|
|
159
|
+
function salaryLocationPath(placeSlug) {
|
|
160
|
+
return `/salaries/locations/${placeSlug}`;
|
|
161
|
+
}
|
|
162
|
+
function blogPostPath(postSlug) {
|
|
163
|
+
return `/blog/${postSlug}`;
|
|
164
|
+
}
|
|
165
|
+
function blogTagPath(tagSlug) {
|
|
166
|
+
return `/blog/tag/${tagSlug}`;
|
|
167
|
+
}
|
|
168
|
+
function blogAuthorPath(authorSlug) {
|
|
169
|
+
return `/blog/author/${authorSlug}`;
|
|
170
|
+
}
|
|
171
|
+
var BOARD_PATHS = {
|
|
172
|
+
home: "/",
|
|
173
|
+
jobs: "/jobs",
|
|
174
|
+
companies: "/companies",
|
|
175
|
+
salaries: "/salaries",
|
|
176
|
+
salaryCompanies: "/salaries/companies",
|
|
177
|
+
salaryTitles: "/salaries/titles",
|
|
178
|
+
salarySkills: "/salaries/skills",
|
|
179
|
+
salaryLocations: "/salaries/locations",
|
|
180
|
+
blog: "/blog",
|
|
181
|
+
about: "/about",
|
|
182
|
+
privacyPolicy: "/privacy-policy",
|
|
183
|
+
termsOfService: "/terms-of-service",
|
|
184
|
+
cookiePolicy: "/cookie-policy",
|
|
185
|
+
impressum: "/impressum",
|
|
186
|
+
talent: "/talent",
|
|
187
|
+
employers: "/employers"
|
|
188
|
+
};
|
|
189
|
+
|
|
131
190
|
// src/sitemap/walker.ts
|
|
132
191
|
var MIN_JOBS_PER_INDEXED_PAGE = 5;
|
|
133
192
|
var MAX_PAGES = 200;
|
|
@@ -210,16 +269,16 @@ async function buildBucketUrls(board, origin, bucket) {
|
|
|
210
269
|
async function marketing(board, origin) {
|
|
211
270
|
const { features } = await board.context();
|
|
212
271
|
const urls = [
|
|
213
|
-
`${origin}
|
|
214
|
-
`${origin}
|
|
215
|
-
`${origin}
|
|
216
|
-
`${origin}
|
|
217
|
-
`${origin}
|
|
218
|
-
`${origin}
|
|
272
|
+
`${origin}${BOARD_PATHS.home}`,
|
|
273
|
+
`${origin}${BOARD_PATHS.jobs}`,
|
|
274
|
+
`${origin}${BOARD_PATHS.about}`,
|
|
275
|
+
`${origin}${BOARD_PATHS.privacyPolicy}`,
|
|
276
|
+
`${origin}${BOARD_PATHS.termsOfService}`,
|
|
277
|
+
`${origin}${BOARD_PATHS.cookiePolicy}`
|
|
219
278
|
];
|
|
220
|
-
if (features.impressum) urls.push(`${origin}
|
|
221
|
-
if (features.talentDirectory) urls.push(`${origin}
|
|
222
|
-
if (features.employers) urls.push(`${origin}
|
|
279
|
+
if (features.impressum) urls.push(`${origin}${BOARD_PATHS.impressum}`);
|
|
280
|
+
if (features.talentDirectory) urls.push(`${origin}${BOARD_PATHS.talent}`);
|
|
281
|
+
if (features.employers) urls.push(`${origin}${BOARD_PATHS.employers}`);
|
|
223
282
|
return urls;
|
|
224
283
|
}
|
|
225
284
|
async function jobsTaxonomy(board, origin, kind) {
|
|
@@ -231,20 +290,20 @@ async function jobsTaxonomy(board, origin, kind) {
|
|
|
231
290
|
);
|
|
232
291
|
for (const slug of slugs) counts.set(slug, (counts.get(slug) ?? 0) + 1);
|
|
233
292
|
}
|
|
234
|
-
const
|
|
235
|
-
return [...counts.entries()].filter(([, n]) => n >= MIN_JOBS_PER_INDEXED_PAGE).map(([slug]) => slug).sort().map((slug) => `${
|
|
293
|
+
const toPath = kind === "categories" ? jobsCategoryPath : jobsSkillPath;
|
|
294
|
+
return [...counts.entries()].filter(([, n]) => n >= MIN_JOBS_PER_INDEXED_PAGE).map(([slug]) => slug).sort().map((slug) => `${origin}${toPath(slug)}`);
|
|
236
295
|
}
|
|
237
296
|
async function jobsLocations(board, origin) {
|
|
238
297
|
const { language } = await board.context();
|
|
239
298
|
const { data } = await board.salaries.locations.list({ locale: language });
|
|
240
|
-
return data.filter((l) => l.jobCount >= MIN_JOBS_PER_INDEXED_PAGE).map((l) => l.placeSlug).sort().map((slug) => `${origin}
|
|
299
|
+
return data.filter((l) => l.jobCount >= MIN_JOBS_PER_INDEXED_PAGE).map((l) => l.placeSlug).sort().map((slug) => `${origin}${jobsLocationPath(slug)}`);
|
|
241
300
|
}
|
|
242
301
|
async function jobDetails(board, origin) {
|
|
243
302
|
const jobs = await enumerateJobs(board);
|
|
244
303
|
const seen = /* @__PURE__ */ new Set();
|
|
245
304
|
for (const job of jobs) {
|
|
246
305
|
if (!job.company?.slug || !job.slug) continue;
|
|
247
|
-
seen.add(`${origin}
|
|
306
|
+
seen.add(`${origin}${jobDetailPath(job.company.slug, job.slug)}`);
|
|
248
307
|
}
|
|
249
308
|
return [...seen].sort();
|
|
250
309
|
}
|
|
@@ -253,10 +312,11 @@ async function companies(board, origin) {
|
|
|
253
312
|
enumerateCompanies(board),
|
|
254
313
|
board.companies.markets()
|
|
255
314
|
]);
|
|
256
|
-
const urls = [`${origin}
|
|
257
|
-
for (const company of list)
|
|
315
|
+
const urls = [`${origin}${BOARD_PATHS.companies}`];
|
|
316
|
+
for (const company of list)
|
|
317
|
+
urls.push(`${origin}${companyPath(company.slug)}`);
|
|
258
318
|
for (const market of markets.data) {
|
|
259
|
-
urls.push(`${origin}
|
|
319
|
+
urls.push(`${origin}${companyMarketPath(market.slug)}`);
|
|
260
320
|
}
|
|
261
321
|
return urls;
|
|
262
322
|
}
|
|
@@ -271,27 +331,27 @@ async function salaries(board, origin) {
|
|
|
271
331
|
if (!salaryCompanies.data.length && !titles.data.length && !skills.data.length && !locations.data.length) {
|
|
272
332
|
return [];
|
|
273
333
|
}
|
|
274
|
-
const urls = [`${origin}
|
|
334
|
+
const urls = [`${origin}${BOARD_PATHS.salaries}`];
|
|
275
335
|
if (salaryCompanies.data.length) {
|
|
276
|
-
urls.push(`${origin}
|
|
336
|
+
urls.push(`${origin}${BOARD_PATHS.salaryCompanies}`);
|
|
277
337
|
for (const c of salaryCompanies.data) {
|
|
278
|
-
urls.push(`${origin}
|
|
338
|
+
urls.push(`${origin}${companySalaryPath(c.companySlug)}`);
|
|
279
339
|
}
|
|
280
340
|
}
|
|
281
341
|
if (titles.data.length) {
|
|
282
|
-
urls.push(`${origin}
|
|
342
|
+
urls.push(`${origin}${BOARD_PATHS.salaryTitles}`);
|
|
283
343
|
for (const t of titles.data)
|
|
284
|
-
urls.push(`${origin}
|
|
344
|
+
urls.push(`${origin}${salaryTitlePath(t.slug)}`);
|
|
285
345
|
}
|
|
286
346
|
if (skills.data.length) {
|
|
287
|
-
urls.push(`${origin}
|
|
347
|
+
urls.push(`${origin}${BOARD_PATHS.salarySkills}`);
|
|
288
348
|
for (const s of skills.data)
|
|
289
|
-
urls.push(`${origin}
|
|
349
|
+
urls.push(`${origin}${salarySkillPath(s.slug)}`);
|
|
290
350
|
}
|
|
291
351
|
if (locations.data.length) {
|
|
292
|
-
urls.push(`${origin}
|
|
352
|
+
urls.push(`${origin}${BOARD_PATHS.salaryLocations}`);
|
|
293
353
|
for (const l of locations.data) {
|
|
294
|
-
urls.push(`${origin}
|
|
354
|
+
urls.push(`${origin}${salaryLocationPath(l.placeSlug)}`);
|
|
295
355
|
}
|
|
296
356
|
}
|
|
297
357
|
return urls;
|
|
@@ -300,18 +360,18 @@ async function blog(board, origin) {
|
|
|
300
360
|
const posts = await drainPages(
|
|
301
361
|
paginate(board.blog.posts.list, { limit: 100 }).pages()
|
|
302
362
|
);
|
|
303
|
-
const urls = [`${origin}
|
|
363
|
+
const urls = [`${origin}${BOARD_PATHS.blog}`];
|
|
304
364
|
const tagSlugs = /* @__PURE__ */ new Set();
|
|
305
365
|
const authorSlugs = /* @__PURE__ */ new Set();
|
|
306
366
|
for (const post of posts) {
|
|
307
|
-
urls.push(`${origin}
|
|
367
|
+
urls.push(`${origin}${blogPostPath(post.slug)}`);
|
|
308
368
|
for (const tag of post.tags) tagSlugs.add(tag.slug);
|
|
309
369
|
for (const author of post.authors) authorSlugs.add(author.slug);
|
|
310
370
|
}
|
|
311
371
|
for (const slug of [...tagSlugs].sort())
|
|
312
|
-
urls.push(`${origin}
|
|
372
|
+
urls.push(`${origin}${blogTagPath(slug)}`);
|
|
313
373
|
for (const slug of [...authorSlugs].sort()) {
|
|
314
|
-
urls.push(`${origin}
|
|
374
|
+
urls.push(`${origin}${blogAuthorPath(slug)}`);
|
|
315
375
|
}
|
|
316
376
|
return urls;
|
|
317
377
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cavuno/board",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.0",
|
|
4
4
|
"description": "Typed isomorphic client for the Cavuno Board API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -80,6 +80,16 @@
|
|
|
80
80
|
"default": "./dist/sitemap.js"
|
|
81
81
|
}
|
|
82
82
|
},
|
|
83
|
+
"./paths": {
|
|
84
|
+
"import": {
|
|
85
|
+
"types": "./dist/paths.d.mts",
|
|
86
|
+
"default": "./dist/paths.mjs"
|
|
87
|
+
},
|
|
88
|
+
"require": {
|
|
89
|
+
"types": "./dist/paths.d.ts",
|
|
90
|
+
"default": "./dist/paths.js"
|
|
91
|
+
}
|
|
92
|
+
},
|
|
83
93
|
"./server": {
|
|
84
94
|
"import": {
|
|
85
95
|
"types": "./dist/server.d.mts",
|
|
@@ -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.
|
|
2
|
+
"version": "1.34.0",
|
|
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.",
|