careermate 0.0.4 → 0.0.6
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/AGENTS.md +11 -7
- package/INSTALL.md +18 -16
- package/README.md +28 -26
- package/THIRD_PARTY_NOTICES.md +2 -3
- package/bin/careermate.mjs +8 -1
- package/dist/career-os/README.md +8 -3
- package/dist/career-os/eop/career-description.md +136 -0
- package/dist/career-os/eop/cover-letter.md +7 -5
- package/dist/career-os/eop/fit-analysis.md +15 -5
- package/dist/career-os/eop/interview-prep.md +209 -21
- package/dist/career-os/eop/job-analysis.md +5 -3
- package/dist/career-os/eop/profile-extraction.md +5 -4
- package/dist/career-os/knowledge/cover-letter.md +4 -4
- package/dist/career-os/knowledge/fit-matching.md +8 -8
- package/dist/career-os/knowledge/interview-behavioral.md +76 -8
- package/dist/career-os/knowledge/interview-technical.md +106 -4
- package/dist/career-os/knowledge/linkedin-profile.md +1 -1
- package/dist/career-os/knowledge/networking-referrals.md +1 -1
- package/dist/career-os/knowledge/offer-evaluation-decision.md +10 -10
- package/dist/career-os/knowledge/portfolio.md +3 -3
- package/dist/career-os/knowledge/verifiers/ats-compat.md +8 -4
- package/dist/career-os/knowledge/verifiers/human-voice.md +2 -2
- package/dist/career-os/knowledge/verifiers/recency-staleness.md +1 -1
- package/dist/career-os/knowledge/verifiers/responsiveness-on-target.md +2 -2
- package/dist/career-os/knowledge/verifiers/truthfulness.md +1 -1
- package/dist/doctor.mjs +1019 -127
- package/dist/mcp.mjs +1036 -126
- package/dist/migrate.mjs +52 -1
- package/dist/public/app.js +42 -16
- package/dist/public/i18n/en/applications.js +23 -0
- package/dist/public/i18n/en/common.js +22 -0
- package/dist/public/i18n/en/documents.js +133 -0
- package/dist/public/i18n/en/home.js +75 -0
- package/dist/public/i18n/en/interview.js +69 -0
- package/dist/public/i18n/en/jobs.js +152 -0
- package/dist/public/i18n/en/nav.js +18 -0
- package/dist/public/i18n/en/profile.js +201 -0
- package/dist/public/i18n/en/settings.js +127 -0
- package/dist/public/i18n/en/status.js +22 -0
- package/dist/public/i18n/en.js +17 -0
- package/dist/public/i18n/ko/applications.js +23 -0
- package/dist/public/i18n/ko/common.js +22 -0
- package/dist/public/i18n/ko/documents.js +133 -0
- package/dist/public/i18n/ko/home.js +63 -0
- package/dist/public/i18n/ko/interview.js +69 -0
- package/dist/public/i18n/ko/jobs.js +152 -0
- package/dist/public/i18n/ko/nav.js +18 -0
- package/dist/public/i18n/ko/profile.js +201 -0
- package/dist/public/i18n/ko/settings.js +127 -0
- package/dist/public/i18n/ko/status.js +23 -0
- package/dist/public/i18n/ko.js +18 -0
- package/dist/public/i18n.js +55 -0
- package/dist/public/index.html +1 -0
- package/dist/public/lib.js +25 -17
- package/dist/public/pages/applications.js +17 -21
- package/dist/public/pages/documents.js +153 -107
- package/dist/public/pages/home.js +88 -53
- package/dist/public/pages/interview.js +105 -51
- package/dist/public/pages/jobs.js +330 -106
- package/dist/public/pages/profile.js +157 -152
- package/dist/public/pages/settings.js +270 -72
- package/dist/public/styles.css +38 -3
- package/dist/web.mjs +859 -103
- package/docs/FAQ.md +26 -5
- package/docs/MCP_TOOLS.md +26 -12
- package/docs/START_WORKFLOW.md +2 -2
- package/docs/SUPPORTED_AI_APPS.md +13 -0
- package/docs/WORKFLOWS.md +80 -23
- package/docs/career-os/README.md +8 -3
- package/docs/career-os/eop/career-description.md +136 -0
- package/docs/career-os/eop/cover-letter.md +7 -5
- package/docs/career-os/eop/fit-analysis.md +15 -5
- package/docs/career-os/eop/interview-prep.md +209 -21
- package/docs/career-os/eop/job-analysis.md +5 -3
- package/docs/career-os/eop/profile-extraction.md +5 -4
- package/docs/career-os/knowledge/cover-letter.md +4 -4
- package/docs/career-os/knowledge/fit-matching.md +8 -8
- package/docs/career-os/knowledge/interview-behavioral.md +76 -8
- package/docs/career-os/knowledge/interview-technical.md +106 -4
- package/docs/career-os/knowledge/linkedin-profile.md +1 -1
- package/docs/career-os/knowledge/networking-referrals.md +1 -1
- package/docs/career-os/knowledge/offer-evaluation-decision.md +10 -10
- package/docs/career-os/knowledge/portfolio.md +3 -3
- package/docs/career-os/knowledge/verifiers/ats-compat.md +8 -4
- package/docs/career-os/knowledge/verifiers/human-voice.md +2 -2
- package/docs/career-os/knowledge/verifiers/recency-staleness.md +1 -1
- package/docs/career-os/knowledge/verifiers/responsiveness-on-target.md +2 -2
- package/docs/career-os/knowledge/verifiers/truthfulness.md +1 -1
- package/package.json +2 -2
- package/packages/core/src/onboarding.ts +2 -1
- package/packages/core/src/services.ts +138 -21
- package/packages/core/src/summary.ts +59 -1
- package/packages/core/src/verify/lint.ts +4 -0
- package/packages/db/src/index.ts +1 -0
- package/packages/db/src/repositories.ts +0 -0
- package/packages/db/src/schema.ts +17 -0
- package/packages/exporters/src/index.ts +28 -17
- package/packages/knowledge/src/index.ts +46 -7
- package/packages/mcp-tools/src/bridge.ts +194 -13
- package/packages/mcp-tools/src/tools.ts +390 -38
- package/packages/mcp-tools/src/update.ts +31 -16
- package/packages/parsers/src/documents.ts +12 -1
- package/packages/prompts/src/humanize.ts +46 -0
- package/packages/prompts/src/index.ts +5 -1
- package/packages/prompts/src/install.ts +4 -4
- package/packages/prompts/src/onboarding.ts +4 -4
- package/packages/prompts/src/system.ts +17 -8
- package/packages/shared/src/enums.ts +14 -4
- package/packages/shared/src/schemas.ts +41 -0
- package/packages/workflows/src/definitions.ts +47 -10
- package/scripts/init.ts +284 -13
- package/dist/site/analytics.js +0 -97
- package/dist/site/favicon.svg +0 -1
- package/dist/site/fonts/PretendardVariable.woff2 +0 -0
- package/dist/site/icon-180.png +0 -0
- package/dist/site/icon-512.png +0 -0
- package/dist/site/index.html +0 -565
- package/dist/site/landing.js +0 -54
- package/dist/site/llms-install.txt +0 -218
- package/dist/site/robots.txt +0 -4
- package/dist/site/sitemap.xml +0 -21
- package/dist/site/start.html +0 -353
- package/dist/site/style.css +0 -559
- package/dist/site/vercel.json +0 -20
package/dist/migrate.mjs
CHANGED
|
@@ -296,6 +296,22 @@ var MIGRATIONS = [
|
|
|
296
296
|
|
|
297
297
|
COMMIT;
|
|
298
298
|
PRAGMA foreign_keys=ON;
|
|
299
|
+
`,
|
|
300
|
+
// v3 — per-job timeline entries. Document/cover-letter refs live in payload
|
|
301
|
+
// snapshots instead of FKs so deleting a document does not erase the history.
|
|
302
|
+
`
|
|
303
|
+
CREATE TABLE IF NOT EXISTS application_timeline_events (
|
|
304
|
+
id TEXT PRIMARY KEY,
|
|
305
|
+
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
|
306
|
+
type TEXT NOT NULL,
|
|
307
|
+
title TEXT NOT NULL,
|
|
308
|
+
summary TEXT,
|
|
309
|
+
payload TEXT NOT NULL DEFAULT '{}',
|
|
310
|
+
occurred_at TEXT NOT NULL,
|
|
311
|
+
created_at TEXT NOT NULL
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
CREATE INDEX IF NOT EXISTS idx_timeline_job ON application_timeline_events(job_id, occurred_at ASC);
|
|
299
315
|
`
|
|
300
316
|
];
|
|
301
317
|
function migrate(db) {
|
|
@@ -360,6 +376,13 @@ var ACTIVITY_TYPES = [
|
|
|
360
376
|
"interview_prep_saved",
|
|
361
377
|
"document_exported"
|
|
362
378
|
];
|
|
379
|
+
var APPLICATION_TIMELINE_TYPES = [
|
|
380
|
+
"job_saved",
|
|
381
|
+
"fit_analysis_saved",
|
|
382
|
+
"cover_letter_version_saved",
|
|
383
|
+
"application_status_changed",
|
|
384
|
+
"interview_prep_saved"
|
|
385
|
+
];
|
|
363
386
|
var ENTITY_TYPES = [
|
|
364
387
|
"profile",
|
|
365
388
|
"experience",
|
|
@@ -4667,11 +4690,19 @@ var ApplicationInputSchema = external_exports.object({
|
|
|
4667
4690
|
applied_at: optLine,
|
|
4668
4691
|
notes: optNote
|
|
4669
4692
|
});
|
|
4693
|
+
var ApplicationSubmissionSchema = external_exports.object({
|
|
4694
|
+
submitted_at: optLine.describe("\uC2E4\uC81C \uC81C\uCD9C\uC77C YYYY-MM-DD"),
|
|
4695
|
+
channel: optLine.describe("\uC81C\uCD9C \uCC44\uB110(\uC6D0\uD2F0\uB4DC/\uC0AC\uB78C\uC778/\uD68C\uC0AC \uCC44\uC6A9\uD398\uC774\uC9C0 \uB4F1)"),
|
|
4696
|
+
cover_letter_id: optLine,
|
|
4697
|
+
cover_letter_version_id: optLine,
|
|
4698
|
+
document_ids: strList.optional().describe("\uC81C\uCD9C\uD55C \uC774\uB825\uC11C\xB7\uACBD\uB825\uAE30\uC220\uC11C\xB7\uD3EC\uD2B8\uD3F4\uB9AC\uC624 \uBB38\uC11C ID \uBAA9\uB85D")
|
|
4699
|
+
});
|
|
4670
4700
|
var ApplicationStatusUpdateSchema = external_exports.object({
|
|
4671
4701
|
job_id: optLine,
|
|
4672
4702
|
application_id: optLine,
|
|
4673
4703
|
status: external_exports.enum(APPLICATION_STATUSES),
|
|
4674
|
-
note: optNote.describe("\uC0C1\uD0DC \uBCC0\uACBD \uC0AC\uC720/\uBA54\uBAA8")
|
|
4704
|
+
note: optNote.describe("\uC0C1\uD0DC \uBCC0\uACBD \uC0AC\uC720/\uBA54\uBAA8"),
|
|
4705
|
+
submission: ApplicationSubmissionSchema.optional().describe("\uC9C0\uC6D0 \uC644\uB8CC \uAE30\uB85D\uC5D0 \uB0A8\uAE38 \uC81C\uCD9C \uC790\uB8CC \uC2A4\uB0C5\uC0F7")
|
|
4675
4706
|
});
|
|
4676
4707
|
var ApplicationRecordSchema = external_exports.object({
|
|
4677
4708
|
...baseRecord,
|
|
@@ -4689,8 +4720,10 @@ var StarGuideSchema = external_exports.object({
|
|
|
4689
4720
|
action: optNote,
|
|
4690
4721
|
result: optNote
|
|
4691
4722
|
});
|
|
4723
|
+
var INTERVIEW_CATEGORIES = ["technical", "behavioral"];
|
|
4692
4724
|
var InterviewQuestionSchema = external_exports.object({
|
|
4693
4725
|
question: external_exports.string().max(MAX_NOTE),
|
|
4726
|
+
category: external_exports.enum(INTERVIEW_CATEGORIES).optional().describe("\uC9C8\uBB38 \uAC08\uB798: 'technical'(\uAE30\uC220\xB7\uC9C1\uBB34 1\uCC28) | 'behavioral'(\uC778\uC131\xB7\uCEEC\uCC98\uD54F\xB7\uC784\uC6D0 2\uCC28, \uC790\uAE30\uC18C\uAC1C\uC11C \uAE30\uBC18). \uC0DD\uB7B5 \uC2DC \uAE30\uC220\uB85C \uAC04\uC8FC."),
|
|
4694
4727
|
intent: external_exports.string().max(MAX_NOTE).optional().describe("\uC9C8\uBB38 \uC758\uB3C4"),
|
|
4695
4728
|
followups: strList.optional().describe("\uC608\uC0C1 \uAF2C\uB9AC \uC9C8\uBB38"),
|
|
4696
4729
|
answer_outline: optBody.describe("\uB2F5\uBCC0 \uAC00\uC774\uB4DC/\uD575\uC2EC \uD3EC\uC778\uD2B8")
|
|
@@ -4718,6 +4751,24 @@ var ActivityRecordSchema = external_exports.object({
|
|
|
4718
4751
|
summary: external_exports.string(),
|
|
4719
4752
|
created_at: isoDateTime
|
|
4720
4753
|
});
|
|
4754
|
+
var ApplicationTimelineInputSchema = external_exports.object({
|
|
4755
|
+
job_id: reqLine,
|
|
4756
|
+
type: external_exports.enum(APPLICATION_TIMELINE_TYPES),
|
|
4757
|
+
title: reqLine,
|
|
4758
|
+
summary: optNote,
|
|
4759
|
+
payload: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
4760
|
+
occurred_at: optLine
|
|
4761
|
+
});
|
|
4762
|
+
var ApplicationTimelineRecordSchema = external_exports.object({
|
|
4763
|
+
id,
|
|
4764
|
+
job_id: external_exports.string(),
|
|
4765
|
+
type: external_exports.enum(APPLICATION_TIMELINE_TYPES),
|
|
4766
|
+
title: external_exports.string(),
|
|
4767
|
+
summary: external_exports.string().nullable(),
|
|
4768
|
+
payload: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
4769
|
+
occurred_at: isoDateTime,
|
|
4770
|
+
created_at: isoDateTime
|
|
4771
|
+
});
|
|
4721
4772
|
var OnboardingStatusSchema = external_exports.object({
|
|
4722
4773
|
completed: external_exports.boolean(),
|
|
4723
4774
|
has_profile: external_exports.boolean(),
|
package/dist/public/app.js
CHANGED
|
@@ -4,15 +4,17 @@
|
|
|
4
4
|
// with { view, params, setTitle, setActions, refreshNav } and fill the view.
|
|
5
5
|
// =============================================================================
|
|
6
6
|
import { el, icon, get, navigate, toastError, Spinner, clear } from '/lib.js';
|
|
7
|
+
import { t, onLangChange } from '/i18n.js';
|
|
7
8
|
|
|
9
|
+
// 라벨은 t('nav.<id>')로 해석 — 로케일 전환 시 재렌더로 갱신된다.
|
|
8
10
|
const ROUTES = [
|
|
9
|
-
{ id: 'home',
|
|
10
|
-
{ id: 'profile',
|
|
11
|
-
{ id: 'jobs',
|
|
12
|
-
{ id: 'applications',
|
|
13
|
-
{ id: 'documents',
|
|
14
|
-
{ id: 'interview',
|
|
15
|
-
{ id: 'settings',
|
|
11
|
+
{ id: 'home', icon: 'home', module: '/pages/home.js' },
|
|
12
|
+
{ id: 'profile', icon: 'user', module: '/pages/profile.js' },
|
|
13
|
+
{ id: 'jobs', icon: 'briefcase', module: '/pages/jobs.js', countKey: 'jobs' },
|
|
14
|
+
{ id: 'applications', icon: 'layers', module: '/pages/applications.js', countKey: 'active_applications' },
|
|
15
|
+
{ id: 'documents', icon: 'file', module: '/pages/documents.js', countKey: 'cover_letters' },
|
|
16
|
+
{ id: 'interview', icon: 'mic', module: '/pages/interview.js', countKey: 'interview_pending' },
|
|
17
|
+
{ id: 'settings', icon: 'settings', module: '/pages/settings.js' },
|
|
16
18
|
];
|
|
17
19
|
|
|
18
20
|
const view = document.getElementById('view');
|
|
@@ -30,15 +32,15 @@ function parseHash() {
|
|
|
30
32
|
|
|
31
33
|
function renderNav(counts = {}) {
|
|
32
34
|
clear(navEl);
|
|
33
|
-
navEl.append(el('div', { class: 'nav__label' }, '
|
|
35
|
+
navEl.append(el('div', { class: 'nav__label' }, t('nav.section.workspace')));
|
|
34
36
|
const { page } = parseHash();
|
|
35
37
|
for (const r of ROUTES) {
|
|
36
|
-
if (r.id === 'settings') navEl.append(el('div', { class: 'nav__label' }, '
|
|
38
|
+
if (r.id === 'settings') navEl.append(el('div', { class: 'nav__label' }, t('nav.section.system')));
|
|
37
39
|
const count = r.countKey ? counts[r.countKey] : null;
|
|
38
40
|
navEl.append(el('a', {
|
|
39
41
|
class: `nav__item${page === r.id ? ' is-active' : ''}`,
|
|
40
42
|
href: `#/${r.id}`,
|
|
41
|
-
}, icon(r.icon), el('span', {}, r.
|
|
43
|
+
}, icon(r.icon), el('span', {}, t('nav.' + r.id)),
|
|
42
44
|
count ? el('span', { class: 'nav__badge tnum' }, String(count)) : null));
|
|
43
45
|
}
|
|
44
46
|
}
|
|
@@ -67,7 +69,9 @@ async function route() {
|
|
|
67
69
|
|
|
68
70
|
// active nav state
|
|
69
71
|
navEl.querySelectorAll('.nav__item').forEach((n) => n.classList.toggle('is-active', n.getAttribute('href') === `#/${def.id}`));
|
|
70
|
-
|
|
72
|
+
const pageTitle = t('nav.' + def.id);
|
|
73
|
+
titleEl.textContent = pageTitle;
|
|
74
|
+
document.title = `${pageTitle} · CareerMate`;
|
|
71
75
|
clear(actionsEl);
|
|
72
76
|
view.scrollTop = 0;
|
|
73
77
|
window.scrollTo(0, 0);
|
|
@@ -75,7 +79,7 @@ async function route() {
|
|
|
75
79
|
const ctx = {
|
|
76
80
|
view,
|
|
77
81
|
params,
|
|
78
|
-
setTitle: (
|
|
82
|
+
setTitle: (title) => { titleEl.textContent = title; document.title = `${title} · CareerMate`; },
|
|
79
83
|
setActions: (nodes) => { clear(actionsEl); [].concat(nodes).filter(Boolean).forEach((n) => actionsEl.append(n)); },
|
|
80
84
|
navigate,
|
|
81
85
|
refreshNav,
|
|
@@ -90,14 +94,14 @@ async function route() {
|
|
|
90
94
|
await mod.render(ctx);
|
|
91
95
|
// Announce just the new page name to screen readers (titleEl reflects any
|
|
92
96
|
// detail-page ctx.setTitle override).
|
|
93
|
-
if (routeStatusEl) routeStatusEl.textContent = titleEl.textContent || def.
|
|
97
|
+
if (routeStatusEl) routeStatusEl.textContent = titleEl.textContent || t('nav.' + def.id);
|
|
94
98
|
} catch (err) {
|
|
95
99
|
if (token !== renderToken) return;
|
|
96
100
|
clear(view);
|
|
97
101
|
view.append(el('div', { class: 'card' }, el('div', { class: 'card__body' },
|
|
98
|
-
el('h3', { style: { marginBottom: '8px' } }, '
|
|
102
|
+
el('h3', { style: { marginBottom: '8px' } }, t('err.page.load')),
|
|
99
103
|
el('p', { class: 'text-secondary', style: { marginBottom: '14px' } }, err instanceof Error ? err.message : String(err)),
|
|
100
|
-
el('button', { class: 'btn btn--primary', type: 'button', onClick: () => route() }, '
|
|
104
|
+
el('button', { class: 'btn btn--primary', type: 'button', onClick: () => route() }, t('common.retry')))));
|
|
101
105
|
toastError(err);
|
|
102
106
|
}
|
|
103
107
|
}
|
|
@@ -113,7 +117,7 @@ function setupMobileNav() {
|
|
|
113
117
|
const setOpen = (open) => {
|
|
114
118
|
app.classList.toggle('nav-open', open);
|
|
115
119
|
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
116
|
-
toggle.setAttribute('aria-label', open ? '
|
|
120
|
+
toggle.setAttribute('aria-label', open ? t('nav.menu.close') : t('nav.menu.open'));
|
|
117
121
|
};
|
|
118
122
|
toggle.addEventListener('click', () => setOpen(!app.classList.contains('nav-open')));
|
|
119
123
|
scrim?.addEventListener('click', () => setOpen(false));
|
|
@@ -127,9 +131,29 @@ function initTheme() {
|
|
|
127
131
|
if (saved === 'light' || saved === 'dark') document.documentElement.dataset.theme = saved;
|
|
128
132
|
}
|
|
129
133
|
|
|
134
|
+
// Static shell strings live in index.html (not page-rendered), so set them from the
|
|
135
|
+
// catalog at boot + on locale change. foot-status belongs to the demo shim when the
|
|
136
|
+
// demo is running, so skip it there.
|
|
137
|
+
function localizeShell() {
|
|
138
|
+
const sub = document.querySelector('.sidebar__subtitle');
|
|
139
|
+
if (sub) sub.textContent = t('nav.subtitle');
|
|
140
|
+
const skip = document.getElementById('skip-link');
|
|
141
|
+
if (skip) skip.textContent = t('nav.skip');
|
|
142
|
+
const foot = document.getElementById('foot-status');
|
|
143
|
+
if (foot && !window.__CAREERMATE_DEMO__) foot.textContent = t('nav.footStatus');
|
|
144
|
+
// FAQ help link points at the marketing-site FAQ in the active locale.
|
|
145
|
+
const faq = document.getElementById('foot-faq');
|
|
146
|
+
if (faq) {
|
|
147
|
+
const en = document.documentElement.lang === 'en';
|
|
148
|
+
faq.href = en ? 'https://careermate.life/en/faq' : 'https://careermate.life/faq';
|
|
149
|
+
faq.title = en ? 'FAQ' : '자주 묻는 질문';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
130
153
|
async function boot() {
|
|
131
154
|
initTheme();
|
|
132
155
|
setupMobileNav();
|
|
156
|
+
localizeShell();
|
|
133
157
|
// Skip link: jump keyboard focus past the sidebar to the page content. A hash
|
|
134
158
|
// anchor would collide with the router, so move focus directly instead.
|
|
135
159
|
document.getElementById('skip-link')?.addEventListener('click', () => {
|
|
@@ -143,6 +167,8 @@ async function boot() {
|
|
|
143
167
|
document.getElementById('foot-path').title = h.data_dir;
|
|
144
168
|
} catch { /* ignore */ }
|
|
145
169
|
await refreshNav();
|
|
170
|
+
// 로케일 전환 시 내비게이션과 현재 라우트를 다시 그린다(전체 리로드 없이).
|
|
171
|
+
onLangChange(() => { localizeShell(); refreshNav(); route(); });
|
|
146
172
|
window.addEventListener('hashchange', route);
|
|
147
173
|
await route();
|
|
148
174
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Applications page.
|
|
2
|
+
export default {
|
|
3
|
+
// deadline D-day chip
|
|
4
|
+
'applications.dday.future': 'Due in {{n}}d',
|
|
5
|
+
'applications.dday.today': 'Due today',
|
|
6
|
+
'applications.dday.past': 'Overdue',
|
|
7
|
+
|
|
8
|
+
// empty state
|
|
9
|
+
'applications.empty.title': 'No applications yet',
|
|
10
|
+
'applications.empty.body': 'Add a posting and your applications will be organized by status.',
|
|
11
|
+
'applications.empty.action': 'Browse postings',
|
|
12
|
+
|
|
13
|
+
// status group
|
|
14
|
+
'applications.group.count': { one: '{{count}} item', other: '{{count}} items' },
|
|
15
|
+
|
|
16
|
+
// app row
|
|
17
|
+
'applications.row.score': '{{score}} pts',
|
|
18
|
+
|
|
19
|
+
// status select + toasts
|
|
20
|
+
'applications.statusSelect.title': 'Change status',
|
|
21
|
+
'applications.toast.statusChanged': 'Application status updated.',
|
|
22
|
+
'applications.hint.title': 'Interview prep',
|
|
23
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Common UI strings + toasts/errors (used by lib.js).
|
|
2
|
+
export default {
|
|
3
|
+
'common.cancel': 'Cancel',
|
|
4
|
+
'common.confirm': 'Confirm',
|
|
5
|
+
'common.close': 'Close',
|
|
6
|
+
'common.save': 'Save',
|
|
7
|
+
'common.retry': 'Try again',
|
|
8
|
+
'common.edit': 'Edit',
|
|
9
|
+
'common.delete': 'Delete',
|
|
10
|
+
'common.open': 'Open',
|
|
11
|
+
'common.justNow': 'just now',
|
|
12
|
+
|
|
13
|
+
'toast.copied': 'Copied to clipboard.',
|
|
14
|
+
'toast.copyFailed': "Couldn't copy. Please select and copy it manually.",
|
|
15
|
+
'toast.errorTitle': 'Error',
|
|
16
|
+
|
|
17
|
+
'err.connect': "Couldn't reach the server. Make sure the dashboard is running and try again.",
|
|
18
|
+
'err.404': "We couldn't find that. Refresh and try again.",
|
|
19
|
+
'err.403': 'A permission check is needed. Please refresh the dashboard.',
|
|
20
|
+
'err.500': 'Something went wrong. Please try again in a moment.',
|
|
21
|
+
'err.generic': "Couldn't complete the request. Check your input and try again.",
|
|
22
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Documents page — cover letters / career descriptions / resumes & portfolios.
|
|
2
|
+
export default {
|
|
3
|
+
// tabs
|
|
4
|
+
'documents.tab.cover': 'Cover letters',
|
|
5
|
+
'documents.tab.career': 'Career descriptions',
|
|
6
|
+
'documents.tab.docs': 'Resumes & portfolios',
|
|
7
|
+
|
|
8
|
+
// search / loading / empty
|
|
9
|
+
'documents.search.cover': 'Search cover letters',
|
|
10
|
+
'documents.search.career': 'Search career descriptions',
|
|
11
|
+
'documents.search.docs': 'Search resumes & portfolios',
|
|
12
|
+
'documents.search.aria': 'Search',
|
|
13
|
+
'documents.search.empty': "No items match '{{query}}'.",
|
|
14
|
+
'documents.search.clear': 'Clear filter',
|
|
15
|
+
'documents.loading': 'Loading…',
|
|
16
|
+
|
|
17
|
+
// create actions
|
|
18
|
+
'documents.action.coverCreate': 'Add cover letter',
|
|
19
|
+
'documents.action.careerCreate': 'Add career description',
|
|
20
|
+
'documents.action.docCreate': 'Add document',
|
|
21
|
+
|
|
22
|
+
// cover letters tab — empty state
|
|
23
|
+
'documents.cover.empty.title': 'No cover letters yet',
|
|
24
|
+
'documents.cover.empty.body': 'Write or paste one to keep it with its versions.',
|
|
25
|
+
|
|
26
|
+
// cover row
|
|
27
|
+
'documents.cover.versionCount': { one: '{{count}} version', other: '{{count}} versions' },
|
|
28
|
+
'documents.cover.primary': 'Primary',
|
|
29
|
+
'documents.cover.open': 'Open',
|
|
30
|
+
'documents.cover.copy': 'Copy',
|
|
31
|
+
'documents.cover.exportMd': 'MD',
|
|
32
|
+
'documents.cover.exportHtml': 'HTML',
|
|
33
|
+
'documents.cover.exportMdTitle': 'Export as Markdown',
|
|
34
|
+
'documents.cover.exportHtmlTitle': 'Export as HTML',
|
|
35
|
+
'documents.cover.setPrimary': 'Set as primary',
|
|
36
|
+
'documents.cover.delete': 'Delete',
|
|
37
|
+
'documents.cover.noContent': 'No content yet.',
|
|
38
|
+
|
|
39
|
+
// cover toasts / confirm
|
|
40
|
+
'documents.cover.setPrimaryToast': 'Set as your primary cover letter.',
|
|
41
|
+
'documents.cover.deleteTitle': 'Delete cover letter',
|
|
42
|
+
'documents.cover.deleteConfirm': 'Delete "{{title}}"? All version history will be deleted too.',
|
|
43
|
+
'documents.cover.deleteConfirmLabel': 'Delete',
|
|
44
|
+
'documents.cover.deleteToast': 'Cover letter deleted.',
|
|
45
|
+
|
|
46
|
+
// cover create modal
|
|
47
|
+
'documents.coverCreate.title': 'New cover letter',
|
|
48
|
+
'documents.coverCreate.titlePlaceholder': 'e.g. Backend Engineer cover letter',
|
|
49
|
+
'documents.coverCreate.contentPlaceholder': 'Paste your content. Leave it empty to create a blank cover letter.',
|
|
50
|
+
'documents.coverCreate.jobNone': 'Not linked',
|
|
51
|
+
'documents.coverCreate.fieldTitle': 'Title (required)',
|
|
52
|
+
'documents.coverCreate.fieldContent': 'Content (optional)',
|
|
53
|
+
'documents.coverCreate.fieldContentHint': 'If you add content, it’s saved as the first version.',
|
|
54
|
+
'documents.coverCreate.fieldJob': 'Posting to link (optional)',
|
|
55
|
+
'documents.coverCreate.fieldJobHint': 'Link it to a posting and it shows up on that posting’s page too.',
|
|
56
|
+
'documents.coverCreate.fieldJobHintEmpty': 'No saved postings yet.',
|
|
57
|
+
'documents.coverCreate.cancel': 'Cancel',
|
|
58
|
+
'documents.coverCreate.submit': 'Save cover letter',
|
|
59
|
+
'documents.coverCreate.titleRequired': 'Please enter a title.',
|
|
60
|
+
'documents.coverCreate.toast': 'Cover letter added.',
|
|
61
|
+
|
|
62
|
+
// cover detail body
|
|
63
|
+
'documents.coverDetail.exportMd': 'Export as Markdown',
|
|
64
|
+
'documents.coverDetail.exportHtml': 'Export as HTML',
|
|
65
|
+
'documents.coverDetail.currentVersion': 'Current version',
|
|
66
|
+
'documents.coverDetail.copy': 'Copy',
|
|
67
|
+
'documents.coverDetail.noContent': 'No content.',
|
|
68
|
+
'documents.coverDetail.history': 'Version history',
|
|
69
|
+
'documents.coverDetail.historyEmpty': 'No version history yet.',
|
|
70
|
+
|
|
71
|
+
// version item
|
|
72
|
+
'documents.version.label': 'v{{n}}',
|
|
73
|
+
'documents.version.current': 'Current version',
|
|
74
|
+
'documents.version.view': 'View this version',
|
|
75
|
+
'documents.version.viewLabel': 'Viewing v{{n}}',
|
|
76
|
+
'documents.version.makeCurrent': 'Set as current version',
|
|
77
|
+
'documents.version.copy': 'Copy',
|
|
78
|
+
'documents.version.makeCurrentToast': 'Current version changed.',
|
|
79
|
+
|
|
80
|
+
// edit new version
|
|
81
|
+
'documents.editVersion.notePlaceholder': 'e.g. Strengthened motivation, polished wording',
|
|
82
|
+
'documents.editVersion.fieldContent': 'Content',
|
|
83
|
+
'documents.editVersion.fieldNote': 'Change note (optional)',
|
|
84
|
+
'documents.editVersion.submit': 'Save as new version',
|
|
85
|
+
'documents.editVersion.contentRequired': 'Please enter some content.',
|
|
86
|
+
'documents.editVersion.toast': 'New version saved.',
|
|
87
|
+
'documents.editVersion.cancel': 'Cancel',
|
|
88
|
+
'documents.editVersion.toggle': 'Edit and save as new version',
|
|
89
|
+
|
|
90
|
+
// career descriptions tab — empty state
|
|
91
|
+
'documents.career.empty.title': 'No career descriptions yet',
|
|
92
|
+
'documents.career.empty.body': 'Ask the AI to write a career description and it’ll be saved here.',
|
|
93
|
+
'documents.career.empty.action': 'Add manually',
|
|
94
|
+
|
|
95
|
+
// documents tab — empty state
|
|
96
|
+
'documents.docs.empty.title': 'No saved documents',
|
|
97
|
+
'documents.docs.empty.body': 'Keep your resumes, portfolios, and other documents here.',
|
|
98
|
+
'documents.docs.empty.action': 'Add document',
|
|
99
|
+
|
|
100
|
+
// doc detail modal
|
|
101
|
+
'documents.docDetail.updated': 'Updated {{when}}',
|
|
102
|
+
'documents.docDetail.noContent': 'No content.',
|
|
103
|
+
'documents.docDetail.copy': 'Copy',
|
|
104
|
+
'documents.docDetail.export': 'MD',
|
|
105
|
+
'documents.docDetail.exportTitle': 'Export as Markdown',
|
|
106
|
+
'documents.docDetail.exportHtml': 'HTML',
|
|
107
|
+
'documents.docDetail.exportHtmlTitle': 'Export as HTML',
|
|
108
|
+
'documents.docDetail.delete': 'Delete',
|
|
109
|
+
'documents.docDetail.deleteTitle': 'Delete document',
|
|
110
|
+
'documents.docDetail.deleteConfirm': 'Delete "{{title}}"?',
|
|
111
|
+
'documents.docDetail.deleteConfirmLabel': 'Delete',
|
|
112
|
+
'documents.docDetail.deleteToast': 'Document deleted.',
|
|
113
|
+
'documents.docDetail.edit': 'Edit',
|
|
114
|
+
|
|
115
|
+
// doc form
|
|
116
|
+
'documents.docForm.defaultKind': 'Document',
|
|
117
|
+
'documents.docForm.editTitle': 'Edit document',
|
|
118
|
+
'documents.docForm.createTitle': 'Add {{kind}}',
|
|
119
|
+
'documents.docForm.titlePlaceholderCareer': 'e.g. Master career description',
|
|
120
|
+
'documents.docForm.titlePlaceholderDefault': 'e.g. 2026 resume',
|
|
121
|
+
'documents.docForm.contentPlaceholderCareer': 'Paste the body of your career description.',
|
|
122
|
+
'documents.docForm.contentPlaceholderDefault': 'Paste the body of your resume or portfolio.',
|
|
123
|
+
'documents.docForm.fieldKind': 'Type',
|
|
124
|
+
'documents.docForm.fieldTitle': 'Title (required)',
|
|
125
|
+
'documents.docForm.fieldContent': 'Content (required)',
|
|
126
|
+
'documents.docForm.markPrimary': 'Set as primary document',
|
|
127
|
+
'documents.docForm.cancel': 'Cancel',
|
|
128
|
+
'documents.docForm.submit': 'Save document',
|
|
129
|
+
'documents.docForm.titleRequired': 'Please enter a title.',
|
|
130
|
+
'documents.docForm.contentRequired': 'Please enter some content.',
|
|
131
|
+
'documents.docForm.editToast': 'Document updated.',
|
|
132
|
+
'documents.docForm.createToast': 'Document added.',
|
|
133
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Home page
|
|
2
|
+
export default {
|
|
3
|
+
// topbar primary action
|
|
4
|
+
'home.action.addJob': 'Add posting',
|
|
5
|
+
|
|
6
|
+
// greeting band
|
|
7
|
+
'home.greeting.title': 'Hi {{name}}',
|
|
8
|
+
'home.greeting.welcome': 'Welcome',
|
|
9
|
+
'home.greeting.firstRun': "Let's get started — fill in your basics first.",
|
|
10
|
+
'home.greeting.interviewTodo': {
|
|
11
|
+
one: '{{count}} application needs interview prep.',
|
|
12
|
+
other: '{{count}} applications need interview prep.',
|
|
13
|
+
},
|
|
14
|
+
'home.greeting.inProgress': {
|
|
15
|
+
one: 'You have {{count}} application in progress.',
|
|
16
|
+
other: 'You have {{count}} applications in progress.',
|
|
17
|
+
},
|
|
18
|
+
'home.greeting.jobsSaved': {
|
|
19
|
+
one: '{{count}} saved posting · get a fresh fit analysis.',
|
|
20
|
+
other: '{{count}} saved postings · get a fresh fit analysis.',
|
|
21
|
+
},
|
|
22
|
+
'home.greeting.idle': "Let's take one more step forward today.",
|
|
23
|
+
|
|
24
|
+
// getting started card
|
|
25
|
+
'home.start.title': 'Getting started',
|
|
26
|
+
'home.start.completeness': 'Profile {{pct}}% complete',
|
|
27
|
+
'home.start.calloutTitle': 'Your AI does the thinking, CareerMate keeps it',
|
|
28
|
+
'home.start.calloutBody': 'Just ask your usual AI to analyze postings, write cover letters and career descriptions, or prep for interviews — the results land here on your dashboard.',
|
|
29
|
+
'home.start.note': 'The more you complete, the better your AI fit analyses and cover letters get.',
|
|
30
|
+
'home.start.step.profile': 'Fill in your basic profile',
|
|
31
|
+
'home.start.step.resume': 'Add a career description or resume',
|
|
32
|
+
'home.start.step.coverLetter': 'Register an existing cover letter',
|
|
33
|
+
'home.start.step.job': 'Save a posting you care about',
|
|
34
|
+
|
|
35
|
+
// KPI tiles
|
|
36
|
+
'home.stat.jobs': 'Saved postings',
|
|
37
|
+
'home.stat.activeApplications': 'Active applications',
|
|
38
|
+
'home.stat.coverLetters': 'Cover letters',
|
|
39
|
+
'home.stat.interviewPending': 'Interview prep',
|
|
40
|
+
|
|
41
|
+
// pipeline
|
|
42
|
+
'home.pipeline.title': 'Application pipeline',
|
|
43
|
+
'home.pipeline.openBoard': 'Open board',
|
|
44
|
+
'home.pipeline.empty': 'No application stages yet. Set an application status on a posting and your pipeline fills in here.',
|
|
45
|
+
'home.pipeline.stage.applied': 'Applied',
|
|
46
|
+
'home.pipeline.stage.documentPassed': 'Screening passed',
|
|
47
|
+
'home.pipeline.stage.interview': 'Interview',
|
|
48
|
+
'home.pipeline.stage.finalPassed': 'Offer',
|
|
49
|
+
'home.pipeline.conv.total': {
|
|
50
|
+
one: '{{count}} total',
|
|
51
|
+
other: '{{count}} total',
|
|
52
|
+
},
|
|
53
|
+
'home.pipeline.conv.vsApplied': '{{pct}}% of applied',
|
|
54
|
+
'home.pipeline.conv.vsInterview': '{{pct}}% of interviews',
|
|
55
|
+
|
|
56
|
+
// action lane
|
|
57
|
+
'home.actionLane.title': 'What to do next',
|
|
58
|
+
'home.actionLane.viewAll': 'View all',
|
|
59
|
+
'home.actionLane.interviewPrep': 'Interview prep',
|
|
60
|
+
'home.actionLane.followup': 'Follow up · {{n}}d',
|
|
61
|
+
'home.actionLane.empty': 'Nothing to handle right now. Try analyzing a new posting.',
|
|
62
|
+
'home.placeholder.dash': '—',
|
|
63
|
+
|
|
64
|
+
// recent jobs
|
|
65
|
+
'home.recentJobs.title': 'Recent postings',
|
|
66
|
+
'home.recentJobs.viewAll': 'View all',
|
|
67
|
+
'home.recentJobs.empty': 'No saved postings yet. Try adding a new one.',
|
|
68
|
+
'home.recentJobs.score': '{{score}}',
|
|
69
|
+
|
|
70
|
+
// activity feed
|
|
71
|
+
'home.activity.title': 'Recent activity',
|
|
72
|
+
'home.activity.empty': 'No activity yet. Save or analyze a posting and it shows up here.',
|
|
73
|
+
'home.activity.today': 'Today',
|
|
74
|
+
'home.activity.yesterday': 'Yesterday',
|
|
75
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Interview prep page
|
|
2
|
+
export default {
|
|
3
|
+
// empty state
|
|
4
|
+
'interview.empty.title': 'No interview prep yet',
|
|
5
|
+
'interview.empty.body': 'Ask the AI to prepare you for an interview from a posting, and it shows up here.',
|
|
6
|
+
'interview.empty.cta': 'Go to postings',
|
|
7
|
+
|
|
8
|
+
// question tabs (technical 1st round / personality & culture fit 2nd round)
|
|
9
|
+
'interview.tab.technical': 'Technical',
|
|
10
|
+
'interview.tab.behavioral': 'Personality & fit',
|
|
11
|
+
|
|
12
|
+
// job card
|
|
13
|
+
'interview.card.summary': '{{q}} · {{s}} · 1-minute intro: {{intro}}',
|
|
14
|
+
'interview.card.questionsCount': { one: '{{count}} likely question', other: '{{count}} likely questions' },
|
|
15
|
+
'interview.card.questionsSplit': '{{tech}} technical · {{beh}} personality & fit',
|
|
16
|
+
'interview.card.starCount': { one: '{{count}} STAR guide', other: '{{count}} STAR guides' },
|
|
17
|
+
'interview.card.introYes': 'Yes',
|
|
18
|
+
'interview.card.introNo': 'No',
|
|
19
|
+
'interview.card.hint': 'Jot down likely questions and a 1-minute intro so you can practice it all in one place.',
|
|
20
|
+
'interview.card.view': 'View details',
|
|
21
|
+
'interview.card.create': 'Write it yourself',
|
|
22
|
+
|
|
23
|
+
// prep modal
|
|
24
|
+
'interview.modal.title': '{{company}} interview prep',
|
|
25
|
+
'interview.modal.questionsTitle': 'Likely questions & follow-ups',
|
|
26
|
+
'interview.modal.questionsEmpty': 'No likely questions added yet.',
|
|
27
|
+
'interview.modal.questionsEmptyTab': 'No questions in this track yet.',
|
|
28
|
+
'interview.modal.starTitle': 'STAR answer guide',
|
|
29
|
+
'interview.modal.starEmpty': 'No STAR guides added yet.',
|
|
30
|
+
'interview.modal.introTitle': '1-minute intro',
|
|
31
|
+
'interview.modal.introCopy': 'Copy',
|
|
32
|
+
'interview.modal.introEmpty': 'No intro written yet.',
|
|
33
|
+
'interview.modal.notesTitle': 'Notes',
|
|
34
|
+
'interview.modal.exportMd': 'Export MD',
|
|
35
|
+
'interview.modal.exportHtml': 'Export HTML',
|
|
36
|
+
'interview.modal.edit': 'Edit',
|
|
37
|
+
|
|
38
|
+
// question block
|
|
39
|
+
'interview.question.intent': 'Intent: {{intent}}',
|
|
40
|
+
'interview.question.followups': 'Follow-up questions',
|
|
41
|
+
'interview.question.answerGuide': 'Answer guide',
|
|
42
|
+
|
|
43
|
+
// STAR block
|
|
44
|
+
'interview.star.situation': 'Situation',
|
|
45
|
+
'interview.star.task': 'Task',
|
|
46
|
+
'interview.star.action': 'Action',
|
|
47
|
+
'interview.star.result': 'Result',
|
|
48
|
+
'interview.star.empty': 'No S/T/A/R content yet.',
|
|
49
|
+
|
|
50
|
+
// edit modal
|
|
51
|
+
'interview.edit.introPlaceholder': 'Write a 1-minute intro (roughly 150–200 words).',
|
|
52
|
+
'interview.edit.notesPlaceholder': 'Jot down any interview prep notes here.',
|
|
53
|
+
'interview.edit.questionPlaceholder': 'Likely question',
|
|
54
|
+
'interview.edit.answerPlaceholder': 'Answer guide (optional)',
|
|
55
|
+
'interview.edit.questionNum': 'Question {{n}}',
|
|
56
|
+
'interview.edit.delete': 'Delete',
|
|
57
|
+
'interview.edit.fieldQuestion': 'Question',
|
|
58
|
+
'interview.edit.fieldCategory': 'Interview track',
|
|
59
|
+
'interview.edit.fieldAnswer': 'Answer guide',
|
|
60
|
+
'interview.edit.introLabel': '1-minute intro',
|
|
61
|
+
'interview.edit.notesLabel': 'Notes',
|
|
62
|
+
'interview.edit.questionsLabel': 'Likely questions',
|
|
63
|
+
'interview.edit.addQuestion': 'Add question',
|
|
64
|
+
'interview.edit.titleEdit': 'Edit {{company}} interview prep',
|
|
65
|
+
'interview.edit.titleCreate': 'Write {{company}} interview prep',
|
|
66
|
+
'interview.edit.cancel': 'Cancel',
|
|
67
|
+
'interview.edit.save': 'Save interview prep',
|
|
68
|
+
'interview.edit.saved': 'Saved your interview prep.',
|
|
69
|
+
};
|