careermate 0.0.5 → 0.0.7

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.
Files changed (125) hide show
  1. package/AGENTS.md +13 -10
  2. package/INSTALL.md +16 -15
  3. package/README.md +24 -24
  4. package/THIRD_PARTY_NOTICES.md +2 -3
  5. package/bin/careermate.mjs +8 -1
  6. package/dist/career-os/README.md +12 -6
  7. package/dist/career-os/eop/career-description.md +139 -0
  8. package/dist/career-os/eop/cover-letter.md +10 -5
  9. package/dist/career-os/eop/fit-analysis.md +16 -6
  10. package/dist/career-os/eop/interview-prep.md +209 -21
  11. package/dist/career-os/eop/job-analysis.md +6 -4
  12. package/dist/career-os/eop/profile-extraction.md +5 -4
  13. package/dist/career-os/knowledge/cover-letter.md +5 -4
  14. package/dist/career-os/knowledge/fit-matching.md +8 -8
  15. package/dist/career-os/knowledge/human-writing.md +2 -0
  16. package/dist/career-os/knowledge/interview-behavioral.md +76 -8
  17. package/dist/career-os/knowledge/interview-technical.md +106 -4
  18. package/dist/career-os/knowledge/linkedin-profile.md +1 -1
  19. package/dist/career-os/knowledge/networking-referrals.md +1 -1
  20. package/dist/career-os/knowledge/offer-evaluation-decision.md +10 -10
  21. package/dist/career-os/knowledge/portfolio.md +3 -3
  22. package/dist/career-os/knowledge/verifiers/ats-compat.md +8 -4
  23. package/dist/career-os/knowledge/verifiers/human-voice.md +4 -2
  24. package/dist/career-os/knowledge/verifiers/recency-staleness.md +1 -1
  25. package/dist/career-os/knowledge/verifiers/responsiveness-on-target.md +2 -2
  26. package/dist/career-os/knowledge/verifiers/truthfulness.md +1 -1
  27. package/dist/doctor.mjs +692 -71
  28. package/dist/mcp.mjs +710 -71
  29. package/dist/migrate.mjs +52 -1
  30. package/dist/public/app.js +42 -16
  31. package/dist/public/i18n/en/applications.js +23 -0
  32. package/dist/public/i18n/en/common.js +22 -0
  33. package/dist/public/i18n/en/documents.js +133 -0
  34. package/dist/public/i18n/en/home.js +75 -0
  35. package/dist/public/i18n/en/interview.js +69 -0
  36. package/dist/public/i18n/en/jobs.js +152 -0
  37. package/dist/public/i18n/en/nav.js +18 -0
  38. package/dist/public/i18n/en/profile.js +201 -0
  39. package/dist/public/i18n/en/settings.js +127 -0
  40. package/dist/public/i18n/en/status.js +22 -0
  41. package/dist/public/i18n/en.js +17 -0
  42. package/dist/public/i18n/ko/applications.js +23 -0
  43. package/dist/public/i18n/ko/common.js +22 -0
  44. package/dist/public/i18n/ko/documents.js +133 -0
  45. package/dist/public/i18n/ko/home.js +63 -0
  46. package/dist/public/i18n/ko/interview.js +69 -0
  47. package/dist/public/i18n/ko/jobs.js +152 -0
  48. package/dist/public/i18n/ko/nav.js +18 -0
  49. package/dist/public/i18n/ko/profile.js +201 -0
  50. package/dist/public/i18n/ko/settings.js +127 -0
  51. package/dist/public/i18n/ko/status.js +23 -0
  52. package/dist/public/i18n/ko.js +18 -0
  53. package/dist/public/i18n.js +55 -0
  54. package/dist/public/index.html +1 -0
  55. package/dist/public/lib.js +24 -17
  56. package/dist/public/pages/applications.js +17 -21
  57. package/dist/public/pages/documents.js +153 -107
  58. package/dist/public/pages/home.js +88 -53
  59. package/dist/public/pages/interview.js +105 -51
  60. package/dist/public/pages/jobs.js +330 -106
  61. package/dist/public/pages/profile.js +157 -152
  62. package/dist/public/pages/settings.js +154 -102
  63. package/dist/public/styles.css +28 -1
  64. package/dist/web.mjs +495 -75
  65. package/docs/FAQ.md +23 -3
  66. package/docs/MCP_TOOLS.md +22 -13
  67. package/docs/START_WORKFLOW.md +3 -3
  68. package/docs/SUPPORTED_AI_APPS.md +13 -0
  69. package/docs/WORKFLOWS.md +77 -20
  70. package/docs/career-os/README.md +12 -6
  71. package/docs/career-os/eop/career-description.md +139 -0
  72. package/docs/career-os/eop/cover-letter.md +10 -5
  73. package/docs/career-os/eop/fit-analysis.md +16 -6
  74. package/docs/career-os/eop/interview-prep.md +209 -21
  75. package/docs/career-os/eop/job-analysis.md +6 -4
  76. package/docs/career-os/eop/profile-extraction.md +5 -4
  77. package/docs/career-os/knowledge/cover-letter.md +5 -4
  78. package/docs/career-os/knowledge/fit-matching.md +8 -8
  79. package/docs/career-os/knowledge/human-writing.md +2 -0
  80. package/docs/career-os/knowledge/interview-behavioral.md +76 -8
  81. package/docs/career-os/knowledge/interview-technical.md +106 -4
  82. package/docs/career-os/knowledge/linkedin-profile.md +1 -1
  83. package/docs/career-os/knowledge/networking-referrals.md +1 -1
  84. package/docs/career-os/knowledge/offer-evaluation-decision.md +10 -10
  85. package/docs/career-os/knowledge/portfolio.md +3 -3
  86. package/docs/career-os/knowledge/verifiers/ats-compat.md +8 -4
  87. package/docs/career-os/knowledge/verifiers/human-voice.md +4 -2
  88. package/docs/career-os/knowledge/verifiers/recency-staleness.md +1 -1
  89. package/docs/career-os/knowledge/verifiers/responsiveness-on-target.md +2 -2
  90. package/docs/career-os/knowledge/verifiers/truthfulness.md +1 -1
  91. package/package.json +2 -2
  92. package/packages/core/src/onboarding.ts +2 -1
  93. package/packages/core/src/services.ts +134 -17
  94. package/packages/core/src/summary.ts +59 -1
  95. package/packages/core/src/verify/lint.ts +4 -0
  96. package/packages/db/src/index.ts +1 -0
  97. package/packages/db/src/repositories.ts +0 -0
  98. package/packages/db/src/schema.ts +17 -0
  99. package/packages/exporters/src/index.ts +28 -17
  100. package/packages/knowledge/src/index.ts +55 -9
  101. package/packages/mcp-tools/src/tools.ts +250 -24
  102. package/packages/mcp-tools/src/update.ts +30 -15
  103. package/packages/parsers/src/documents.ts +12 -1
  104. package/packages/prompts/src/humanize.ts +46 -0
  105. package/packages/prompts/src/index.ts +5 -1
  106. package/packages/prompts/src/install.ts +4 -4
  107. package/packages/prompts/src/onboarding.ts +4 -4
  108. package/packages/prompts/src/system.ts +9 -7
  109. package/packages/shared/src/enums.ts +14 -4
  110. package/packages/shared/src/schemas.ts +41 -0
  111. package/packages/workflows/src/definitions.ts +45 -8
  112. package/scripts/init.ts +284 -13
  113. package/dist/site/analytics.js +0 -97
  114. package/dist/site/favicon.svg +0 -1
  115. package/dist/site/fonts/PretendardVariable.woff2 +0 -0
  116. package/dist/site/icon-180.png +0 -0
  117. package/dist/site/icon-512.png +0 -0
  118. package/dist/site/index.html +0 -465
  119. package/dist/site/landing.js +0 -163
  120. package/dist/site/llms-install.txt +0 -218
  121. package/dist/site/robots.txt +0 -4
  122. package/dist/site/sitemap.xml +0 -21
  123. package/dist/site/start.html +0 -356
  124. package/dist/site/style.css +0 -665
  125. 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(),
@@ -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', label: '홈', icon: 'home', module: '/pages/home.js' },
10
- { id: 'profile', label: '프로필', icon: 'user', module: '/pages/profile.js' },
11
- { id: 'jobs', label: '채용공고', icon: 'briefcase', module: '/pages/jobs.js', countKey: 'jobs' },
12
- { id: 'applications', label: '지원 현황', icon: 'layers', module: '/pages/applications.js', countKey: 'active_applications' },
13
- { id: 'documents', label: '문서', icon: 'file', module: '/pages/documents.js', countKey: 'cover_letters' },
14
- { id: 'interview', label: '면접 준비', icon: 'mic', module: '/pages/interview.js', countKey: 'interview_pending' },
15
- { id: 'settings', label: '설정', icon: 'settings', module: '/pages/settings.js' },
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.label),
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
- titleEl.textContent = def.label;
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: (t) => { titleEl.textContent = t; },
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.label;
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
+ };