@cavuno/board 1.25.0 → 1.26.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/README.md +88 -0
- package/dist/bin.mjs +67 -34
- package/dist/filters.d.mts +55 -0
- package/dist/filters.d.ts +55 -0
- package/dist/filters.js +193 -0
- package/dist/filters.mjs +170 -0
- package/dist/format.d.mts +240 -0
- package/dist/format.d.ts +240 -0
- package/dist/format.js +453 -0
- package/dist/format.mjs +430 -0
- package/dist/index.d.mts +79 -3838
- package/dist/index.d.ts +79 -3838
- package/dist/index.js +154 -7
- package/dist/index.mjs +151 -4
- package/dist/jobs-CM67_J6H.d.mts +3836 -0
- package/dist/jobs-CM67_J6H.d.ts +3836 -0
- package/dist/theme.d.mts +63 -0
- package/dist/theme.d.ts +63 -0
- package/dist/theme.js +149 -0
- package/dist/theme.mjs +128 -0
- package/package.json +37 -2
- package/skills/cavuno-board-account/SKILL.md +147 -0
- package/skills/cavuno-board-applications/SKILL.md +110 -0
- package/skills/cavuno-board-blog/SKILL.md +99 -0
- package/skills/cavuno-board-companies/SKILL.md +99 -0
- package/skills/cavuno-board-filters/SKILL.md +89 -0
- package/skills/cavuno-board-format/SKILL.md +104 -0
- package/skills/cavuno-board-job-alerts/SKILL.md +115 -0
- package/skills/cavuno-board-job-posting/SKILL.md +130 -0
- package/skills/cavuno-board-jobs/SKILL.md +15 -0
- package/skills/cavuno-board-messaging/SKILL.md +121 -0
- package/skills/cavuno-board-paywall/SKILL.md +108 -0
- package/skills/cavuno-board-salaries/SKILL.md +87 -0
- package/skills/cavuno-board-setup/SKILL.md +26 -2
- package/skills/cavuno-board-smoke-test/SKILL.md +84 -0
- package/skills/cavuno-board-theme/SKILL.md +69 -0
- package/skills/manifest.json +92 -1
package/dist/filters.mjs
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// src/format/salary-lexicon.ts
|
|
2
|
+
var EN = {
|
|
3
|
+
timeframe: {
|
|
4
|
+
per_year: "Yearly",
|
|
5
|
+
per_month: "Monthly",
|
|
6
|
+
per_week: "Weekly",
|
|
7
|
+
per_day: "Daily",
|
|
8
|
+
per_hour: "Hourly"
|
|
9
|
+
},
|
|
10
|
+
rangePrefix: { from: "From", upTo: "Up to" },
|
|
11
|
+
seniority: {
|
|
12
|
+
entry_level: "Entry level",
|
|
13
|
+
associate: "Associate",
|
|
14
|
+
mid_level: "Mid-level",
|
|
15
|
+
senior: "Senior",
|
|
16
|
+
lead: "Lead",
|
|
17
|
+
principal: "Principal",
|
|
18
|
+
director: "Director",
|
|
19
|
+
executive: "Executive"
|
|
20
|
+
},
|
|
21
|
+
frames: {
|
|
22
|
+
entitySalariesTitle: ({ entity, range }) => `${entity} Salaries (${range}/yr)`,
|
|
23
|
+
salariesInPlaceTitle: ({ place, range }) => `Salaries in ${place} (${range}/yr)`,
|
|
24
|
+
salariesInPlaceTitleNoRange: ({ place }) => `Salaries in ${place}`,
|
|
25
|
+
entitySalariesInPlaceTitle: ({ entity, place, range }) => `${entity} Salaries in ${place} (${range}/yr)`,
|
|
26
|
+
companySalariesTitle: ({ company, range }) => `${company} Salaries${range ? ` (${range}/yr)` : ""}`,
|
|
27
|
+
companyCategorySalariesTitle: ({ company, category, range }) => `${company} ${category} Salaries${range ? ` (${range}/yr)` : ""}`,
|
|
28
|
+
professionalsEarn: ({ entity, range, count }) => `${entity} professionals earn ${range}/yr on average across ${count} job postings.`,
|
|
29
|
+
rolesPay: ({ entity, range, count }) => `${entity} roles pay ${range}/yr on average across ${count} job postings.`,
|
|
30
|
+
professionalsInLocationEarn: ({ entity, place, range, count }) => `${entity} professionals in ${place} earn ${range}/yr on average across ${count} job postings.`,
|
|
31
|
+
seniorityRange: ({ lowLabel, lowAmount, highLabel, highAmount }) => `${lowLabel} roles start at ${lowAmount}, ${highLabel} roles reach ${highAmount}.`,
|
|
32
|
+
seniorityRangeWhile: ({ lowLabel, lowAmount, highLabel, highAmount }) => `${lowLabel} salaries start at ${lowAmount}, while ${highLabel} roles reach ${highAmount}.`,
|
|
33
|
+
compareAcrossTopPaying: ({ count }) => `Compare across ${count} top-paying ${count === 1 ? "company" : "companies"}.`,
|
|
34
|
+
compareAcrossTop: ({ count }) => `Compare across ${count} top ${count === 1 ? "company" : "companies"}.`,
|
|
35
|
+
boardWideAverage: ({ entity, range }) => `Board-wide ${entity} average: ${range}/yr.`,
|
|
36
|
+
averageSalaryInCity: ({ place, range, count }) => `Average salary in ${place} is ${range}/yr across ${count} jobs. Browse salary data by title and skill.`,
|
|
37
|
+
browseLocationsInRegion: ({ count, place }) => `Browse salary data across ${count} locations in ${place}. Compare salaries by title, skill, and company.`,
|
|
38
|
+
companyPays: ({ company, range, count }) => `${company} pays ${range}/yr on average across ${count} job postings.`,
|
|
39
|
+
breakdownAcrossCategories: ({ count }) => `Breakdown across ${count} job ${count === 1 ? "category" : "categories"}.`,
|
|
40
|
+
compareWithSimilar: ({ count }) => `Compare with ${count} similar ${count === 1 ? "company" : "companies"}.`,
|
|
41
|
+
salaryInfoFor: ({ company, board }) => `Salary information for ${company} on ${board}.`,
|
|
42
|
+
categoryRolesAtCompanyPay: ({ category, company, range, count }) => `${category} roles at ${company} pay ${range}/yr based on ${count} job postings.`,
|
|
43
|
+
categorySalaryDataFor: ({ category, company, board }) => `${category} salary data for ${company} on ${board}.`
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var DE = {
|
|
47
|
+
timeframe: {
|
|
48
|
+
per_year: "pro Jahr",
|
|
49
|
+
per_month: "pro Monat",
|
|
50
|
+
per_week: "pro Woche",
|
|
51
|
+
per_day: "pro Tag",
|
|
52
|
+
per_hour: "pro Stunde"
|
|
53
|
+
},
|
|
54
|
+
rangePrefix: { from: "ab", upTo: "bis zu" },
|
|
55
|
+
seniority: {
|
|
56
|
+
entry_level: "Entry-Level",
|
|
57
|
+
associate: "Associate",
|
|
58
|
+
mid_level: "Mid-Level",
|
|
59
|
+
senior: "Senior",
|
|
60
|
+
lead: "Lead",
|
|
61
|
+
principal: "Principal",
|
|
62
|
+
director: "Director",
|
|
63
|
+
executive: "F\xFChrungskraft"
|
|
64
|
+
},
|
|
65
|
+
frames: {
|
|
66
|
+
entitySalariesTitle: ({ entity, range }) => `${entity} Geh\xE4lter (${range}/Jahr)`,
|
|
67
|
+
salariesInPlaceTitle: ({ place, range }) => `Geh\xE4lter in ${place} (${range}/Jahr)`,
|
|
68
|
+
salariesInPlaceTitleNoRange: ({ place }) => `Geh\xE4lter in ${place}`,
|
|
69
|
+
entitySalariesInPlaceTitle: ({ entity, place, range }) => `${entity} Geh\xE4lter in ${place} (${range}/Jahr)`,
|
|
70
|
+
companySalariesTitle: ({ company, range }) => `${company} Geh\xE4lter${range ? ` (${range}/Jahr)` : ""}`,
|
|
71
|
+
companyCategorySalariesTitle: ({ company, category, range }) => `${company} ${category} Geh\xE4lter${range ? ` (${range}/Jahr)` : ""}`,
|
|
72
|
+
professionalsEarn: ({ entity, range, count }) => `${entity}-Fachkr\xE4fte verdienen durchschnittlich ${range}/Jahr, basierend auf ${count} Stellenanzeigen.`,
|
|
73
|
+
rolesPay: ({ entity, range, count }) => `${entity}-Positionen werden mit durchschnittlich ${range}/Jahr verg\xFCtet, basierend auf ${count} Stellenanzeigen.`,
|
|
74
|
+
professionalsInLocationEarn: ({ entity, place, range, count }) => `${entity}-Fachkr\xE4fte in ${place} verdienen durchschnittlich ${range}/Jahr, basierend auf ${count} Stellenanzeigen.`,
|
|
75
|
+
seniorityRange: ({ lowLabel, lowAmount, highLabel, highAmount }) => `${lowLabel}-Positionen beginnen bei ${lowAmount}, ${highLabel}-Positionen erreichen ${highAmount}.`,
|
|
76
|
+
seniorityRangeWhile: ({ lowLabel, lowAmount, highLabel, highAmount }) => `${lowLabel}-Geh\xE4lter beginnen bei ${lowAmount}, w\xE4hrend ${highLabel}-Positionen ${highAmount} erreichen.`,
|
|
77
|
+
compareAcrossTopPaying: ({ count }) => `Vergleichen Sie ${count} ${count === 1 ? "bestzahlendes" : "bestzahlende"} Unternehmen.`,
|
|
78
|
+
compareAcrossTop: ({ count }) => `Vergleichen Sie ${count} Top-Unternehmen.`,
|
|
79
|
+
boardWideAverage: ({ entity, range }) => `Gesamtdurchschnitt ${entity}: ${range}/Jahr.`,
|
|
80
|
+
averageSalaryInCity: ({ place, range, count }) => `Das Durchschnittsgehalt in ${place} betr\xE4gt ${range}/Jahr, basierend auf ${count} Stellen. Gehaltsdaten nach Titel und F\xE4higkeiten durchsuchen.`,
|
|
81
|
+
browseLocationsInRegion: ({ count, place }) => `Gehaltsdaten f\xFCr ${count} Standorte in ${place} durchsuchen. Geh\xE4lter nach Titel, F\xE4higkeiten und Unternehmen vergleichen.`,
|
|
82
|
+
companyPays: ({ company, range, count }) => `${company} zahlt durchschnittlich ${range}/Jahr, basierend auf ${count} Stellenanzeigen.`,
|
|
83
|
+
breakdownAcrossCategories: ({ count }) => `Aufschl\xFCsselung \xFCber ${count} Job-${count === 1 ? "Kategorie" : "Kategorien"}.`,
|
|
84
|
+
compareWithSimilar: ({ count }) => `Vergleichen Sie mit ${count} \xE4hnlichen Unternehmen.`,
|
|
85
|
+
salaryInfoFor: ({ company, board }) => `Gehaltsinformationen f\xFCr ${company} auf ${board}.`,
|
|
86
|
+
categoryRolesAtCompanyPay: ({ category, company, range, count }) => `${category}-Positionen bei ${company} werden mit ${range}/Jahr verg\xFCtet, basierend auf ${count} Stellenanzeigen.`,
|
|
87
|
+
categorySalaryDataFor: ({ category, company, board }) => `${category}-Gehaltsdaten f\xFCr ${company} auf ${board}.`
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var LEXICONS = { en: EN, de: DE };
|
|
91
|
+
function getSalaryLexicon(locale) {
|
|
92
|
+
return locale && LEXICONS[locale] || EN;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/filters/index.ts
|
|
96
|
+
var REMOTE_OPTIONS = [
|
|
97
|
+
"on_site",
|
|
98
|
+
"hybrid",
|
|
99
|
+
"remote"
|
|
100
|
+
];
|
|
101
|
+
var EMPLOYMENT_TYPES = [
|
|
102
|
+
"full_time",
|
|
103
|
+
"part_time",
|
|
104
|
+
"contract",
|
|
105
|
+
"internship",
|
|
106
|
+
"temporary"
|
|
107
|
+
];
|
|
108
|
+
var SENIORITIES = [
|
|
109
|
+
"entry_level",
|
|
110
|
+
"associate",
|
|
111
|
+
"mid_level",
|
|
112
|
+
"senior",
|
|
113
|
+
"lead",
|
|
114
|
+
"principal",
|
|
115
|
+
"director",
|
|
116
|
+
"executive"
|
|
117
|
+
];
|
|
118
|
+
var JOB_SORTS = [
|
|
119
|
+
"relevance",
|
|
120
|
+
"newest",
|
|
121
|
+
"salary_high"
|
|
122
|
+
];
|
|
123
|
+
var DEFAULT_SORT = "relevance";
|
|
124
|
+
function seniorityLabels(locale) {
|
|
125
|
+
return getSalaryLexicon(locale).seniority;
|
|
126
|
+
}
|
|
127
|
+
function sortLabels(_locale) {
|
|
128
|
+
return {
|
|
129
|
+
relevance: "AI-ranked",
|
|
130
|
+
newest: "Most recent",
|
|
131
|
+
salary_high: "Salary: high to low"
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
var SENIORITY_LOOKUP = new Set(SENIORITIES);
|
|
135
|
+
function parseSeniority(raw) {
|
|
136
|
+
const values = Array.isArray(raw) ? raw : typeof raw === "string" && raw ? raw.split(",") : [];
|
|
137
|
+
const result = [];
|
|
138
|
+
const seen = /* @__PURE__ */ new Set();
|
|
139
|
+
for (const value of values) {
|
|
140
|
+
if (typeof value !== "string") continue;
|
|
141
|
+
const normalized = value.trim().toLowerCase();
|
|
142
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
143
|
+
if (!SENIORITY_LOOKUP.has(normalized)) continue;
|
|
144
|
+
seen.add(normalized);
|
|
145
|
+
result.push(normalized);
|
|
146
|
+
}
|
|
147
|
+
return result.length > 0 ? result : void 0;
|
|
148
|
+
}
|
|
149
|
+
function parseListingFilters(search) {
|
|
150
|
+
return {
|
|
151
|
+
q: typeof search.q === "string" && search.q ? search.q : void 0,
|
|
152
|
+
remoteOption: REMOTE_OPTIONS.includes(search.remoteOption) ? search.remoteOption : void 0,
|
|
153
|
+
employmentType: EMPLOYMENT_TYPES.includes(
|
|
154
|
+
search.employmentType
|
|
155
|
+
) ? search.employmentType : void 0,
|
|
156
|
+
seniority: parseSeniority(search.seniority),
|
|
157
|
+
sort: JOB_SORTS.includes(search.sort) ? search.sort : void 0
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
export {
|
|
161
|
+
DEFAULT_SORT,
|
|
162
|
+
EMPLOYMENT_TYPES,
|
|
163
|
+
JOB_SORTS,
|
|
164
|
+
REMOTE_OPTIONS,
|
|
165
|
+
SENIORITIES,
|
|
166
|
+
parseListingFilters,
|
|
167
|
+
parseSeniority,
|
|
168
|
+
seniorityLabels,
|
|
169
|
+
sortLabels
|
|
170
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { P as PublicJob, m as PublicJobCard } from './jobs-CM67_J6H.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Date display helpers in the board language (required leading parameter,
|
|
5
|
+
* ADR-0057) — transcribed from the hosted board's `date-labels.ts` and
|
|
6
|
+
* golden-tested against it in-monorepo.
|
|
7
|
+
*
|
|
8
|
+
* Job cards and the job-detail header render the RELATIVE form
|
|
9
|
+
* (`formatPublishedRelativeDate` — "5d ago"); the absolute medium date
|
|
10
|
+
* (`formatDate`) is what hosted uses for blog metadata and detail facts.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Absolute medium date in the board language, UTC-pinned — e.g.
|
|
14
|
+
* "Jun 24, 2026" (`en`) / "24.06.2026"-style per locale. What hosted uses
|
|
15
|
+
* for blog metadata and detail facts; job CARDS use the relative form.
|
|
16
|
+
* Returns `null` for missing/unparseable input.
|
|
17
|
+
*/
|
|
18
|
+
declare function formatDate(locale: string, value: string | number | null): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Relative published label — "5d ago" (en) / "vor 5 T." (de) — the form the
|
|
21
|
+
* hosted board renders on job cards and the job-detail header. Under one
|
|
22
|
+
* minute renders "now". `referenceNowMs` exists for deterministic tests.
|
|
23
|
+
*/
|
|
24
|
+
declare function formatPublishedRelativeDate(locale: string, value: string | number | null, referenceNowMs?: number): string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Month-year label, e.g. "Jun 2023" — transcribed from the hosted board's
|
|
27
|
+
* `formatMonthYear` (golden-tested in-monorepo). The `T00:00:00` suffix
|
|
28
|
+
* prevents timezone-related off-by-one-month issues on date-only strings.
|
|
29
|
+
*/
|
|
30
|
+
declare function formatMonthYear(locale: string, dateStr: string): string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Adapt a full `PublicJob` (e.g. an embedded saved job) to the slim
|
|
34
|
+
* `PublicJobCard` shape a list card renders — computing the card's
|
|
35
|
+
* pre-resolved `locationLabel`/`remoteLocationLabel` from the full job's
|
|
36
|
+
* fields. Board language is required because the computed location label is
|
|
37
|
+
* display text (ADR-0057).
|
|
38
|
+
*/
|
|
39
|
+
declare function fullJobToCard(locale: string, job: PublicJob): PublicJobCard;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Display label for a job enum value (`remoteOption`, `employmentType`,
|
|
43
|
+
* `seniority`) in the board language. Unknown values pass through unchanged
|
|
44
|
+
* (enums grow additively within v1).
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* fieldLabel('en', 'mid_level'); // "Mid-level"
|
|
48
|
+
* fieldLabel('de', 'executive'); // "Führungskraft"
|
|
49
|
+
* fieldLabel('de', 'full_time'); // "Full-time" (hosted parity: en-only vocab)
|
|
50
|
+
*/
|
|
51
|
+
declare function fieldLabel(locale: string, value: string | null): string | null;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Location display labels for job cards and detail pages, in the board
|
|
55
|
+
* language (required leading parameter, ADR-0057). The wrapper words
|
|
56
|
+
* ("Remote", "(hybrid)", "Worldwide") follow the hosted board's current
|
|
57
|
+
* English-only rendering; place names arrive already board-language from the
|
|
58
|
+
* wire (the API serves canonical, localized labels).
|
|
59
|
+
*/
|
|
60
|
+
/**
|
|
61
|
+
* Location label for the full `PublicJob` (detail pages, embedded saved
|
|
62
|
+
* jobs): first office location's display name, remote/hybrid wrapping per
|
|
63
|
+
* `remoteOption`.
|
|
64
|
+
*/
|
|
65
|
+
declare function locationLabel(locale: string, job: PublicJob): string;
|
|
66
|
+
/**
|
|
67
|
+
* Location label for a list CARD. The card read-model pre-computes
|
|
68
|
+
* `locationLabel` and (for remote jobs) `remoteLocationLabel` server-side —
|
|
69
|
+
* the slim card carries no `officeLocations`/`remoteWorldwide` — so those
|
|
70
|
+
* wire fields are used directly, matching what the hosted card renders.
|
|
71
|
+
*/
|
|
72
|
+
declare function cardLocationLabel(locale: string, job: Pick<PublicJobCard, 'remoteOption' | 'remoteLocationLabel' | 'locationLabel'>): string;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The closed, board-language salary LEXICON — the fixed salary words:
|
|
76
|
+
* timeframe labels + range prefixes, seniority labels, and metadata
|
|
77
|
+
* `<title>`/`<description>` sentence FRAMES, keyed by the board language
|
|
78
|
+
* (`board.context().language`), NOT by any visitor locale.
|
|
79
|
+
*
|
|
80
|
+
* Transcribed from the hosted board's `salary-lexicon.ts` (CAV-352 /
|
|
81
|
+
* ADR-0012) and golden-tested against it in-monorepo (ADR-0057): the two
|
|
82
|
+
* copies cannot drift silently. The `de` strings are native-reviewed.
|
|
83
|
+
* Unseeded locales fall back to the English source WORDS while number and
|
|
84
|
+
* currency formatting stays locale-correct.
|
|
85
|
+
*/
|
|
86
|
+
/** Wire enum for `salaryTimeframe`. */
|
|
87
|
+
type SalaryTimeframeValue = 'per_year' | 'per_month' | 'per_week' | 'per_day' | 'per_hour';
|
|
88
|
+
type SeniorityKey = 'entry_level' | 'associate' | 'mid_level' | 'senior' | 'lead' | 'principal' | 'director' | 'executive';
|
|
89
|
+
/**
|
|
90
|
+
* Board-language builders for the salary-page metadata sentence frames. Each
|
|
91
|
+
* receives already-formatted parts (entity name, board-language `range`/amount
|
|
92
|
+
* strings, counts) and returns one localized sentence. The numbers are
|
|
93
|
+
* formatted upstream (`formatSalaryRange`); the FRAME is the words.
|
|
94
|
+
*/
|
|
95
|
+
interface SalaryFrames {
|
|
96
|
+
entitySalariesTitle(a: {
|
|
97
|
+
entity: string;
|
|
98
|
+
range: string;
|
|
99
|
+
}): string;
|
|
100
|
+
salariesInPlaceTitle(a: {
|
|
101
|
+
place: string;
|
|
102
|
+
range: string;
|
|
103
|
+
}): string;
|
|
104
|
+
salariesInPlaceTitleNoRange(a: {
|
|
105
|
+
place: string;
|
|
106
|
+
}): string;
|
|
107
|
+
entitySalariesInPlaceTitle(a: {
|
|
108
|
+
entity: string;
|
|
109
|
+
place: string;
|
|
110
|
+
range: string;
|
|
111
|
+
}): string;
|
|
112
|
+
companySalariesTitle(a: {
|
|
113
|
+
company: string;
|
|
114
|
+
range: string | null;
|
|
115
|
+
}): string;
|
|
116
|
+
companyCategorySalariesTitle(a: {
|
|
117
|
+
company: string;
|
|
118
|
+
category: string;
|
|
119
|
+
range: string | null;
|
|
120
|
+
}): string;
|
|
121
|
+
professionalsEarn(a: {
|
|
122
|
+
entity: string;
|
|
123
|
+
range: string;
|
|
124
|
+
count: number;
|
|
125
|
+
}): string;
|
|
126
|
+
rolesPay(a: {
|
|
127
|
+
entity: string;
|
|
128
|
+
range: string;
|
|
129
|
+
count: number;
|
|
130
|
+
}): string;
|
|
131
|
+
professionalsInLocationEarn(a: {
|
|
132
|
+
entity: string;
|
|
133
|
+
place: string;
|
|
134
|
+
range: string;
|
|
135
|
+
count: number;
|
|
136
|
+
}): string;
|
|
137
|
+
seniorityRange(a: {
|
|
138
|
+
lowLabel: string;
|
|
139
|
+
lowAmount: string;
|
|
140
|
+
highLabel: string;
|
|
141
|
+
highAmount: string;
|
|
142
|
+
}): string;
|
|
143
|
+
seniorityRangeWhile(a: {
|
|
144
|
+
lowLabel: string;
|
|
145
|
+
lowAmount: string;
|
|
146
|
+
highLabel: string;
|
|
147
|
+
highAmount: string;
|
|
148
|
+
}): string;
|
|
149
|
+
compareAcrossTopPaying(a: {
|
|
150
|
+
count: number;
|
|
151
|
+
}): string;
|
|
152
|
+
compareAcrossTop(a: {
|
|
153
|
+
count: number;
|
|
154
|
+
}): string;
|
|
155
|
+
boardWideAverage(a: {
|
|
156
|
+
entity: string;
|
|
157
|
+
range: string;
|
|
158
|
+
}): string;
|
|
159
|
+
averageSalaryInCity(a: {
|
|
160
|
+
place: string;
|
|
161
|
+
range: string;
|
|
162
|
+
count: number;
|
|
163
|
+
}): string;
|
|
164
|
+
browseLocationsInRegion(a: {
|
|
165
|
+
count: number;
|
|
166
|
+
place: string;
|
|
167
|
+
}): string;
|
|
168
|
+
companyPays(a: {
|
|
169
|
+
company: string;
|
|
170
|
+
range: string;
|
|
171
|
+
count: number;
|
|
172
|
+
}): string;
|
|
173
|
+
breakdownAcrossCategories(a: {
|
|
174
|
+
count: number;
|
|
175
|
+
}): string;
|
|
176
|
+
compareWithSimilar(a: {
|
|
177
|
+
count: number;
|
|
178
|
+
}): string;
|
|
179
|
+
salaryInfoFor(a: {
|
|
180
|
+
company: string;
|
|
181
|
+
board: string;
|
|
182
|
+
}): string;
|
|
183
|
+
categoryRolesAtCompanyPay(a: {
|
|
184
|
+
category: string;
|
|
185
|
+
company: string;
|
|
186
|
+
range: string;
|
|
187
|
+
count: number;
|
|
188
|
+
}): string;
|
|
189
|
+
categorySalaryDataFor(a: {
|
|
190
|
+
category: string;
|
|
191
|
+
company: string;
|
|
192
|
+
board: string;
|
|
193
|
+
}): string;
|
|
194
|
+
}
|
|
195
|
+
interface SalaryLexicon {
|
|
196
|
+
/** Per-timeframe display word, e.g. `per_year` → "Yearly" / "pro Jahr". */
|
|
197
|
+
timeframe: Record<SalaryTimeframeValue, string>;
|
|
198
|
+
/** Open-ended range prefixes, e.g. "From" / "ab", "Up to" / "bis zu". */
|
|
199
|
+
rangePrefix: {
|
|
200
|
+
from: string;
|
|
201
|
+
upTo: string;
|
|
202
|
+
};
|
|
203
|
+
/** Seniority-level labels, e.g. `senior` → "Senior". */
|
|
204
|
+
seniority: Record<SeniorityKey, string>;
|
|
205
|
+
/** Metadata `<title>`/`<description>` sentence builders. */
|
|
206
|
+
frames: SalaryFrames;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Resolve the salary lexicon for a board language; unseeded locales fall back
|
|
210
|
+
* to the English source.
|
|
211
|
+
*/
|
|
212
|
+
declare function getSalaryLexicon(locale: string | undefined): SalaryLexicon;
|
|
213
|
+
|
|
214
|
+
type SalaryTimeframeInput = SalaryTimeframeValue | string | null | undefined;
|
|
215
|
+
/** Owner-customized timeframe words (board settings) — from the board's SEO/labels config. */
|
|
216
|
+
interface SalaryTimeframeOverrides {
|
|
217
|
+
yearlyLabel?: string;
|
|
218
|
+
monthlyLabel?: string;
|
|
219
|
+
weeklyLabel?: string;
|
|
220
|
+
dailyLabel?: string;
|
|
221
|
+
hourlyLabel?: string;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Format a per-job salary range in the BOARD language
|
|
225
|
+
* (`board.context().language` — required, never defaulted; see ADR-0057).
|
|
226
|
+
* The fixed words (timeframe, "From"/"Up to") come from
|
|
227
|
+
* `getSalaryLexicon(language)` with precedence owner-override > lexicon >
|
|
228
|
+
* English source. Currency placement is gated: `en` keeps the legacy manual
|
|
229
|
+
* symbol-before form (byte-identical to the hosted board); non-`en` uses
|
|
230
|
+
* `Intl` `style:'currency'` so the symbol sits per locale.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* formatSalaryRange('en', 90000, 120000, 'per_year', 'USD');
|
|
234
|
+
* // "$90K – $120K Yearly"
|
|
235
|
+
* formatSalaryRange('de', 90000, null, 'per_year', 'EUR');
|
|
236
|
+
* // "ab 90.000 € pro Jahr"
|
|
237
|
+
*/
|
|
238
|
+
declare function formatSalaryRange(language: string | undefined, min: number | null, max: number | null, timeframe: SalaryTimeframeInput, currency?: string | null, timeframeOverrides?: SalaryTimeframeOverrides): string | null;
|
|
239
|
+
|
|
240
|
+
export { type SalaryFrames, type SalaryLexicon, type SalaryTimeframeInput, type SalaryTimeframeOverrides, type SalaryTimeframeValue, type SeniorityKey, cardLocationLabel, fieldLabel, formatDate, formatMonthYear, formatPublishedRelativeDate, formatSalaryRange, fullJobToCard, getSalaryLexicon, locationLabel };
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { P as PublicJob, m as PublicJobCard } from './jobs-CM67_J6H.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Date display helpers in the board language (required leading parameter,
|
|
5
|
+
* ADR-0057) — transcribed from the hosted board's `date-labels.ts` and
|
|
6
|
+
* golden-tested against it in-monorepo.
|
|
7
|
+
*
|
|
8
|
+
* Job cards and the job-detail header render the RELATIVE form
|
|
9
|
+
* (`formatPublishedRelativeDate` — "5d ago"); the absolute medium date
|
|
10
|
+
* (`formatDate`) is what hosted uses for blog metadata and detail facts.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Absolute medium date in the board language, UTC-pinned — e.g.
|
|
14
|
+
* "Jun 24, 2026" (`en`) / "24.06.2026"-style per locale. What hosted uses
|
|
15
|
+
* for blog metadata and detail facts; job CARDS use the relative form.
|
|
16
|
+
* Returns `null` for missing/unparseable input.
|
|
17
|
+
*/
|
|
18
|
+
declare function formatDate(locale: string, value: string | number | null): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Relative published label — "5d ago" (en) / "vor 5 T." (de) — the form the
|
|
21
|
+
* hosted board renders on job cards and the job-detail header. Under one
|
|
22
|
+
* minute renders "now". `referenceNowMs` exists for deterministic tests.
|
|
23
|
+
*/
|
|
24
|
+
declare function formatPublishedRelativeDate(locale: string, value: string | number | null, referenceNowMs?: number): string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Month-year label, e.g. "Jun 2023" — transcribed from the hosted board's
|
|
27
|
+
* `formatMonthYear` (golden-tested in-monorepo). The `T00:00:00` suffix
|
|
28
|
+
* prevents timezone-related off-by-one-month issues on date-only strings.
|
|
29
|
+
*/
|
|
30
|
+
declare function formatMonthYear(locale: string, dateStr: string): string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Adapt a full `PublicJob` (e.g. an embedded saved job) to the slim
|
|
34
|
+
* `PublicJobCard` shape a list card renders — computing the card's
|
|
35
|
+
* pre-resolved `locationLabel`/`remoteLocationLabel` from the full job's
|
|
36
|
+
* fields. Board language is required because the computed location label is
|
|
37
|
+
* display text (ADR-0057).
|
|
38
|
+
*/
|
|
39
|
+
declare function fullJobToCard(locale: string, job: PublicJob): PublicJobCard;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Display label for a job enum value (`remoteOption`, `employmentType`,
|
|
43
|
+
* `seniority`) in the board language. Unknown values pass through unchanged
|
|
44
|
+
* (enums grow additively within v1).
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* fieldLabel('en', 'mid_level'); // "Mid-level"
|
|
48
|
+
* fieldLabel('de', 'executive'); // "Führungskraft"
|
|
49
|
+
* fieldLabel('de', 'full_time'); // "Full-time" (hosted parity: en-only vocab)
|
|
50
|
+
*/
|
|
51
|
+
declare function fieldLabel(locale: string, value: string | null): string | null;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Location display labels for job cards and detail pages, in the board
|
|
55
|
+
* language (required leading parameter, ADR-0057). The wrapper words
|
|
56
|
+
* ("Remote", "(hybrid)", "Worldwide") follow the hosted board's current
|
|
57
|
+
* English-only rendering; place names arrive already board-language from the
|
|
58
|
+
* wire (the API serves canonical, localized labels).
|
|
59
|
+
*/
|
|
60
|
+
/**
|
|
61
|
+
* Location label for the full `PublicJob` (detail pages, embedded saved
|
|
62
|
+
* jobs): first office location's display name, remote/hybrid wrapping per
|
|
63
|
+
* `remoteOption`.
|
|
64
|
+
*/
|
|
65
|
+
declare function locationLabel(locale: string, job: PublicJob): string;
|
|
66
|
+
/**
|
|
67
|
+
* Location label for a list CARD. The card read-model pre-computes
|
|
68
|
+
* `locationLabel` and (for remote jobs) `remoteLocationLabel` server-side —
|
|
69
|
+
* the slim card carries no `officeLocations`/`remoteWorldwide` — so those
|
|
70
|
+
* wire fields are used directly, matching what the hosted card renders.
|
|
71
|
+
*/
|
|
72
|
+
declare function cardLocationLabel(locale: string, job: Pick<PublicJobCard, 'remoteOption' | 'remoteLocationLabel' | 'locationLabel'>): string;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The closed, board-language salary LEXICON — the fixed salary words:
|
|
76
|
+
* timeframe labels + range prefixes, seniority labels, and metadata
|
|
77
|
+
* `<title>`/`<description>` sentence FRAMES, keyed by the board language
|
|
78
|
+
* (`board.context().language`), NOT by any visitor locale.
|
|
79
|
+
*
|
|
80
|
+
* Transcribed from the hosted board's `salary-lexicon.ts` (CAV-352 /
|
|
81
|
+
* ADR-0012) and golden-tested against it in-monorepo (ADR-0057): the two
|
|
82
|
+
* copies cannot drift silently. The `de` strings are native-reviewed.
|
|
83
|
+
* Unseeded locales fall back to the English source WORDS while number and
|
|
84
|
+
* currency formatting stays locale-correct.
|
|
85
|
+
*/
|
|
86
|
+
/** Wire enum for `salaryTimeframe`. */
|
|
87
|
+
type SalaryTimeframeValue = 'per_year' | 'per_month' | 'per_week' | 'per_day' | 'per_hour';
|
|
88
|
+
type SeniorityKey = 'entry_level' | 'associate' | 'mid_level' | 'senior' | 'lead' | 'principal' | 'director' | 'executive';
|
|
89
|
+
/**
|
|
90
|
+
* Board-language builders for the salary-page metadata sentence frames. Each
|
|
91
|
+
* receives already-formatted parts (entity name, board-language `range`/amount
|
|
92
|
+
* strings, counts) and returns one localized sentence. The numbers are
|
|
93
|
+
* formatted upstream (`formatSalaryRange`); the FRAME is the words.
|
|
94
|
+
*/
|
|
95
|
+
interface SalaryFrames {
|
|
96
|
+
entitySalariesTitle(a: {
|
|
97
|
+
entity: string;
|
|
98
|
+
range: string;
|
|
99
|
+
}): string;
|
|
100
|
+
salariesInPlaceTitle(a: {
|
|
101
|
+
place: string;
|
|
102
|
+
range: string;
|
|
103
|
+
}): string;
|
|
104
|
+
salariesInPlaceTitleNoRange(a: {
|
|
105
|
+
place: string;
|
|
106
|
+
}): string;
|
|
107
|
+
entitySalariesInPlaceTitle(a: {
|
|
108
|
+
entity: string;
|
|
109
|
+
place: string;
|
|
110
|
+
range: string;
|
|
111
|
+
}): string;
|
|
112
|
+
companySalariesTitle(a: {
|
|
113
|
+
company: string;
|
|
114
|
+
range: string | null;
|
|
115
|
+
}): string;
|
|
116
|
+
companyCategorySalariesTitle(a: {
|
|
117
|
+
company: string;
|
|
118
|
+
category: string;
|
|
119
|
+
range: string | null;
|
|
120
|
+
}): string;
|
|
121
|
+
professionalsEarn(a: {
|
|
122
|
+
entity: string;
|
|
123
|
+
range: string;
|
|
124
|
+
count: number;
|
|
125
|
+
}): string;
|
|
126
|
+
rolesPay(a: {
|
|
127
|
+
entity: string;
|
|
128
|
+
range: string;
|
|
129
|
+
count: number;
|
|
130
|
+
}): string;
|
|
131
|
+
professionalsInLocationEarn(a: {
|
|
132
|
+
entity: string;
|
|
133
|
+
place: string;
|
|
134
|
+
range: string;
|
|
135
|
+
count: number;
|
|
136
|
+
}): string;
|
|
137
|
+
seniorityRange(a: {
|
|
138
|
+
lowLabel: string;
|
|
139
|
+
lowAmount: string;
|
|
140
|
+
highLabel: string;
|
|
141
|
+
highAmount: string;
|
|
142
|
+
}): string;
|
|
143
|
+
seniorityRangeWhile(a: {
|
|
144
|
+
lowLabel: string;
|
|
145
|
+
lowAmount: string;
|
|
146
|
+
highLabel: string;
|
|
147
|
+
highAmount: string;
|
|
148
|
+
}): string;
|
|
149
|
+
compareAcrossTopPaying(a: {
|
|
150
|
+
count: number;
|
|
151
|
+
}): string;
|
|
152
|
+
compareAcrossTop(a: {
|
|
153
|
+
count: number;
|
|
154
|
+
}): string;
|
|
155
|
+
boardWideAverage(a: {
|
|
156
|
+
entity: string;
|
|
157
|
+
range: string;
|
|
158
|
+
}): string;
|
|
159
|
+
averageSalaryInCity(a: {
|
|
160
|
+
place: string;
|
|
161
|
+
range: string;
|
|
162
|
+
count: number;
|
|
163
|
+
}): string;
|
|
164
|
+
browseLocationsInRegion(a: {
|
|
165
|
+
count: number;
|
|
166
|
+
place: string;
|
|
167
|
+
}): string;
|
|
168
|
+
companyPays(a: {
|
|
169
|
+
company: string;
|
|
170
|
+
range: string;
|
|
171
|
+
count: number;
|
|
172
|
+
}): string;
|
|
173
|
+
breakdownAcrossCategories(a: {
|
|
174
|
+
count: number;
|
|
175
|
+
}): string;
|
|
176
|
+
compareWithSimilar(a: {
|
|
177
|
+
count: number;
|
|
178
|
+
}): string;
|
|
179
|
+
salaryInfoFor(a: {
|
|
180
|
+
company: string;
|
|
181
|
+
board: string;
|
|
182
|
+
}): string;
|
|
183
|
+
categoryRolesAtCompanyPay(a: {
|
|
184
|
+
category: string;
|
|
185
|
+
company: string;
|
|
186
|
+
range: string;
|
|
187
|
+
count: number;
|
|
188
|
+
}): string;
|
|
189
|
+
categorySalaryDataFor(a: {
|
|
190
|
+
category: string;
|
|
191
|
+
company: string;
|
|
192
|
+
board: string;
|
|
193
|
+
}): string;
|
|
194
|
+
}
|
|
195
|
+
interface SalaryLexicon {
|
|
196
|
+
/** Per-timeframe display word, e.g. `per_year` → "Yearly" / "pro Jahr". */
|
|
197
|
+
timeframe: Record<SalaryTimeframeValue, string>;
|
|
198
|
+
/** Open-ended range prefixes, e.g. "From" / "ab", "Up to" / "bis zu". */
|
|
199
|
+
rangePrefix: {
|
|
200
|
+
from: string;
|
|
201
|
+
upTo: string;
|
|
202
|
+
};
|
|
203
|
+
/** Seniority-level labels, e.g. `senior` → "Senior". */
|
|
204
|
+
seniority: Record<SeniorityKey, string>;
|
|
205
|
+
/** Metadata `<title>`/`<description>` sentence builders. */
|
|
206
|
+
frames: SalaryFrames;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Resolve the salary lexicon for a board language; unseeded locales fall back
|
|
210
|
+
* to the English source.
|
|
211
|
+
*/
|
|
212
|
+
declare function getSalaryLexicon(locale: string | undefined): SalaryLexicon;
|
|
213
|
+
|
|
214
|
+
type SalaryTimeframeInput = SalaryTimeframeValue | string | null | undefined;
|
|
215
|
+
/** Owner-customized timeframe words (board settings) — from the board's SEO/labels config. */
|
|
216
|
+
interface SalaryTimeframeOverrides {
|
|
217
|
+
yearlyLabel?: string;
|
|
218
|
+
monthlyLabel?: string;
|
|
219
|
+
weeklyLabel?: string;
|
|
220
|
+
dailyLabel?: string;
|
|
221
|
+
hourlyLabel?: string;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Format a per-job salary range in the BOARD language
|
|
225
|
+
* (`board.context().language` — required, never defaulted; see ADR-0057).
|
|
226
|
+
* The fixed words (timeframe, "From"/"Up to") come from
|
|
227
|
+
* `getSalaryLexicon(language)` with precedence owner-override > lexicon >
|
|
228
|
+
* English source. Currency placement is gated: `en` keeps the legacy manual
|
|
229
|
+
* symbol-before form (byte-identical to the hosted board); non-`en` uses
|
|
230
|
+
* `Intl` `style:'currency'` so the symbol sits per locale.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* formatSalaryRange('en', 90000, 120000, 'per_year', 'USD');
|
|
234
|
+
* // "$90K – $120K Yearly"
|
|
235
|
+
* formatSalaryRange('de', 90000, null, 'per_year', 'EUR');
|
|
236
|
+
* // "ab 90.000 € pro Jahr"
|
|
237
|
+
*/
|
|
238
|
+
declare function formatSalaryRange(language: string | undefined, min: number | null, max: number | null, timeframe: SalaryTimeframeInput, currency?: string | null, timeframeOverrides?: SalaryTimeframeOverrides): string | null;
|
|
239
|
+
|
|
240
|
+
export { type SalaryFrames, type SalaryLexicon, type SalaryTimeframeInput, type SalaryTimeframeOverrides, type SalaryTimeframeValue, type SeniorityKey, cardLocationLabel, fieldLabel, formatDate, formatMonthYear, formatPublishedRelativeDate, formatSalaryRange, fullJobToCard, getSalaryLexicon, locationLabel };
|