Polish UI, harden company creation, and add error pages

This commit is contained in:
cesnimda
2026-03-23 19:34:29 +01:00
parent 8f5eab2fe4
commit fcafda6f52
38 changed files with 2293 additions and 1269 deletions
+9 -2
View File
@@ -1,14 +1,21 @@
import React, { createContext, useContext, useState } from "react";
import { translations, TranslationKey, UiLanguage } from "./translations";
type TranslationParams = Record<string, string | number>;
type Ctx = {
language: UiLanguage;
setLanguage: (l: UiLanguage) => void;
t: (key: TranslationKey) => string;
t: (key: TranslationKey, params?: TranslationParams) => string;
};
const I18nContext = createContext<Ctx | null>(null);
function interpolate(template: string, params?: TranslationParams) {
if (!params) return template;
return template.replace(/\{(\w+)\}/g, (_, key) => String(params[key] ?? `{${key}}`));
}
export function I18nProvider({ children }: { children: React.ReactNode }) {
const [language, setLanguageState] = useState<UiLanguage>(() => {
const raw = window.localStorage.getItem("uiLanguage");
@@ -20,7 +27,7 @@ export function I18nProvider({ children }: { children: React.ReactNode }) {
window.localStorage.setItem("uiLanguage", l);
};
const t = (key: TranslationKey) => translations[language][key] ?? translations.en[key];
const t = (key: TranslationKey, params?: TranslationParams) => interpolate(translations[language][key] ?? translations.en[key], params);
return <I18nContext.Provider value={{ language, setLanguage, t }}>{children}</I18nContext.Provider>;
}
+814 -3
View File
@@ -2,7 +2,8 @@ export type UiLanguage = "en" | "no";
export const translations = {
en: {
appTitle: "Job Tracker",
appTitle: "Jobbjakt",
appTagline: "Track your hunt",
dashboard: "Dashboard",
jobApplications: "Job Applications",
reminders: "Reminders",
@@ -14,9 +15,415 @@ export const translations = {
addJobApplication: "Add Job Application",
company: "Company",
location: "Location",
home: "Home",
analytics: "Analytics",
overview: "Overview",
account: "Account",
profile: "Profile",
admin: "Admin",
auditLog: "Audit log",
users: "Users",
system: "System",
systemStatus: "System status",
manage: "Manage",
notifications: "Notifications",
quickSearch: "Quick Search",
searchPlaceholder: "Search jobs, companies, or actions",
noMatchingCommands: "No matching commands or records.",
goToDashboard: "Go to dashboard",
analyticsOverview: "Analytics overview",
goToJobs: "Go to jobs",
mainApplicationsTable: "Main applications table",
goToReminders: "Go to reminders",
followUpQueue: "Follow-up queue",
goToCompanies: "Go to companies",
crmAndSourceTracking: "CRM and source tracking",
goToSettings: "Go to settings",
preferencesAndAdminTools: "Preferences and admin tools",
addNewJob: "Add new job",
openAddJobModal: "Open the add-job modal",
openCompanies: "Open companies",
openJobListAndSearchResult: "Open job list and search result",
profileMenu: "Profile",
settingsMenu: "Settings",
signOut: "Sign out",
user: "User",
superAdmin: "Super Admin",
close: "Close",
cancel: "Cancel",
save: "Save",
create: "Create",
createJob: "Create job",
createAndAddAnother: "Create & add another",
loading: "Loading...",
notFoundTitle: "Page not found",
notFoundBody: "The page you were looking for does not exist or may have moved.",
appErrorTitle: "Something went wrong",
appErrorBody: "An unexpected error occurred while loading this page.",
goHome: "Go home",
goBack: "Go back",
addJobModalCompanySection: "Company",
addJobModalCreateCompany: "Create \"{name}\"",
addJobModalCompanyLocation: "Company location",
addJobModalCompanySource: "Company source",
addJobModalPossibleDuplicates: "Possible duplicates found",
addJobModalJobApplicationSection: "Job application",
addJobModalJobUrl: "Job URL",
addJobModalImportFromUrl: "Import from URL",
addJobModalImporting: "Importing...",
addJobModalDateApplied: "Date applied",
addJobModalStatus: "Status",
addJobModalJobTitle: "Job title",
addJobModalSalary: "Salary",
addJobModalDeadline: "Deadline",
addJobModalDescriptionOriginal: "Description (original)",
addJobModalTranslatedDescription: "Translated description ({language})",
addJobModalDescriptionLanguage: "Description language",
addJobModalTranslatedShown: "Shown because the source language differs from your preferred language ({language}).",
addJobModalTranslatedHidden: "Translated text is only shown when the source language differs from your preferred language.",
addJobModalNotes: "Notes",
addJobModalDocuments: "Documents",
addJobModalResume: "CV / resume",
addJobModalResumeHelp: "Prefer PDF, DOC, or DOCX. Text extraction can happen later where supported.",
addJobModalCoverLetter: "Cover letter",
addJobModalCoverLetterHelp: "Upload one or more versions instead of pasting long text into the form.",
addJobModalPortfolio: "Portfolio",
addJobModalPortfolioHelp: "Use PDF, DOC, DOCX, TXT, MD, or image files for scans/screenshots.",
addJobModalOtherFiles: "Other files",
addJobModalOtherFilesHelp: "Certificates, references, role briefs, or any supporting documents.",
addJobModalChooseFiles: "Choose files",
addJobModalNoFilesSelected: "No files selected",
addJobModalFilesSelected: "{count} files selected",
addJobModalFileReady: "{count} file ready",
addJobModalFilesReady: "{count} files ready",
addJobModalPreferredFiles: "Preferred: PDF, DOC, DOCX",
addJobModalTextImageAllowed: "Text and image files also allowed",
addJobModalPasteUrlFirst: "Paste a job URL first.",
addJobModalImported: "Imported.",
addJobModalImportFailed: "Import failed.",
addJobModalFailedCreateCompany: "Failed to create company.",
addJobModalSelectCompany: "Select or create a company.",
addJobModalJobAdded: "Job added.",
addJobModalJobAndFilesAdded: "Job and files added.",
addJobModalJobCreatedUploadFailed: "Job created, but file upload failed.",
addJobModalJobCreatedFilesNotAttached: "Job created. Files could not be attached automatically.",
addJobModalFailedAddJob: "Failed to add job.",
statusApplied: "Applied",
statusWaiting: "Waiting",
statusInterview: "Interview",
statusOffer: "Offer",
statusRejected: "Rejected",
statusGhosted: "Ghosted",
settingsTitle: "Settings",
settingsSubtitle: "Preferences and admin tools.",
settingsTabGeneral: "General",
settingsTabFollowUps: "Follow-ups",
settingsTabNotifications: "Notifications",
settingsTabAccount: "Account",
settingsTabBackup: "Backup",
settingsAppearance: "Appearance",
settingsTheme: "Theme",
settingsThemeSystem: "System",
settingsThemeDark: "Dark",
settingsThemeLight: "Light",
settingsAccent: "Accent",
settingsReset: "Reset",
settingsSavedPerUser: "Saved per user on this browser.",
settingsLanguageTitle: "Language and localization",
settingsLanguageBody: "Set your preferred app language. This is also the language used when deciding whether imported job descriptions should show translated text.",
settingsPreferredLanguage: "Preferred language",
settingsEnglish: "English",
settingsNorwegian: "Norwegian Bokmål",
settingsMorePagesSoon: "More pages will be moved onto this translation system as the UI cleanup continues.",
settingsJobs: "Jobs",
settingsPagination: "Pagination",
settingsRowsPerPage: "Rows per page",
settingsColumns: "Columns",
settingsColumnStatus: "Status",
settingsColumnDateApplied: "Date applied",
settingsColumnDays: "Days",
settingsColumnJobUrl: "Job URL",
settingsNotificationsTitle: "Email notifications",
settingsNotificationsBody: "Notifications are sent via SMTP (Gmail works). Configure SMTP in the API (`Email:*` settings or env vars like `EMAIL_SMTP_HOST`).",
profileTitle: "Profile",
profileHeadlinePlaceholder: "Add a short headline to personalize your account view.",
profileLocalAccount: "Local account",
profileGoogleSession: "Google session",
profileExternalSession: "External session",
profileGoogleLinked: "Google linked",
profileGoogleLinkedWithEmail: "Google linked: {email}",
profileGoogleNotLinked: "Google not linked",
profileCvReady: "CV ready · {count} words",
profileCvMissing: "CV missing",
profileChangeImage: "Change image",
profileRemoveImage: "Remove",
profileImageUpdated: "Profile image updated.",
profileImageRemoved: "Profile image removed.",
profileImageUploadFailed: "Failed to upload profile image.",
profileImageRemoveFailed: "Failed to remove profile image.",
profileAccountSection: "Account",
profileReadOnlyInfo: "This session is not using a local app token, so profile edits are read-only right now.",
profileDisplayName: "Display name",
profileUsername: "Username",
profileFirstName: "First name",
profileLastName: "Last name",
profileEmail: "Email",
profileHeadline: "Profile headline",
profileHeadlineHelp: "Stored only in this browser to personalize your workspace.",
profileMasterCv: "Master CV",
profileMasterCvBody: "Upload a PDF, Word document, plain text file, markdown file, or image scan. Where supported, the app can extract text automatically and populate your master CV text for tailoring and outreach.",
profileUploadCv: "Upload CV",
profileUploading: "Uploading...",
profileCopyCvText: "Copy CV text",
profileCvUploaded: "CV uploaded and processed.",
profileCvUploadFailed: "Failed to upload CV.",
profileCvTextLabel: "Profile CV / master resume text",
profileCvTextHelp: "Keep this updated and specific. Include recent roles, tools, achievements, measurable outcomes, and the work you want to be hired for next. If extraction misses something, edit it here manually.",
profileCvPreferredUploads: "Preferred uploads: PDF, DOC, DOCX. Text and image files are also accepted.",
profileSaveChanges: "Save changes",
profileUpdated: "Profile updated.",
profileUpdateFailed: "Failed to update profile.",
profileChangePassword: "Change password",
profilePasswordLocalOnly: "Password changes are only available for local accounts.",
profileCurrentPassword: "Current password",
profileNewPassword: "New password",
profileUpdatePassword: "Update password",
profilePasswordUpdated: "Password updated.",
profilePasswordUpdateFailed: "Failed to change password.",
cropDialogTitle: "Crop profile image",
cropDialogBody: "Position and zoom your image. The saved avatar will be exported as a 512×512 square.",
cropDialogZoom: "Zoom",
cropDialogSave: "Save image",
dashboardOverviewTitle: "Dashboard overview",
dashboardOverviewBody: "High-level application activity only. System health and pipeline diagnostics now live in the System page to avoid duplicated or conflicting status data.",
dashboardCustomize: "Customize dashboard",
dashboardSummaryCards: "Summary cards",
dashboardActivityChart: "Activity chart",
dashboardConversionFunnel: "Conversion funnel",
dashboardTopCompanies: "Top companies",
dashboardSkillsInsights: "Skills insights",
dashboardActiveApplications: "Active applications",
dashboardCurrentlyInProgress: "Currently in progress",
dashboardApplied30Days: "Applied (30 days)",
dashboardNewApplications: "New applications",
dashboardMedianFirstResponse: "Median first response",
dashboardDaysUntilFirstReply: "Days until first reply",
dashboardResponsesLogged: "Responses logged",
dashboardAcrossActiveJobs: "Across active jobs",
dashboardLowReadiness: "Low readiness",
dashboardMissingTailoredCv: "Reminder jobs missing tailored CV",
dashboardApplicationActivity: "Application activity",
dashboardMonthlyApplicationsResponses: "Monthly applications versus responses.",
dashboardConversionFunnelTitle: "Conversion funnel",
dashboardResponseSources: "Response sources",
dashboardTopCompaniesByActivity: "Top companies by activity",
dashboardTopSkills: "Top skills",
dashboardNoTagsYet: "No tags yet.",
dashboardSkillTags: "skill tags",
dashboardSkillTrends: "Skill trends",
dashboardNoTagTrendData: "No tag trend data yet.",
remindersTitle: "Needs Follow-up",
remindersSubtitle: "Grouped by the most useful next action so you can fix gaps faster.",
remindersMissingTailoredCv: "Missing tailored CV",
remindersMissingInterviewPrep: "Missing interview prep",
remindersFollowUpDue: "Follow-up due",
remindersOther: "Other reminders",
remindersNothing: "Nothing to follow up right now.",
remindersTip: "Tip: focus on tailored CV and interview prep first for the highest-value roles.",
remindersOpen: "Open",
remindersClear: "Clear",
remindersFollowUpLabel: "Follow up",
remindersFollowUpDate: "Follow-up: {date}",
remindersFollowUpCleared: "Follow-up cleared.",
remindersFollowUpSet: "Follow-up set.",
remindersFollowUpFailed: "Failed to set follow-up.",
companiesEmpty: "No companies yet.",
companiesName: "Name",
companiesLocation: "Location",
companiesSource: "Source",
companiesPipeline: "Pipeline",
companiesRecruiter: "Recruiter",
companiesNextContact: "Next Contact",
companiesEdit: "Edit Company",
companiesPipelineStage: "Pipeline stage",
companiesRecruiterName: "Recruiter name",
companiesRecruiterEmail: "Recruiter email",
companiesRecruiterLinkedIn: "Recruiter LinkedIn",
companiesLastContacted: "Last contacted",
companiesNextContactField: "Next contact",
companiesUpdated: "Company updated.",
companiesUpdateFailed: "Failed to update company.",
adminUsersTitle: "Users",
adminUsersSubtitle: "Admin-only user management.",
adminUsersCreateUser: "Create user",
adminUsersAdmin: "Admin",
adminUsersSendReset: "Send reset",
adminUsersDelete: "Delete",
adminUsersConfirmed: "Confirmed",
adminUsersActions: "Actions",
adminUsersNoUsers: "No users.",
adminUsersRolesUpdated: "Roles updated.",
adminUsersRolesUpdateFailed: "Failed to update roles.",
adminUsersResetSent: "Password reset email sent.",
adminUsersResetFailed: "Failed to send reset.",
adminUsersDeleteConfirmTitle: "Delete user",
adminUsersDeleted: "User deleted.",
adminUsersDeleteFailed: "Failed to delete user.",
adminUsersCreated: "User created.",
adminUsersCreateFailed: "Failed to create user.",
adminSystemTitle: "System status",
adminSystemSubtitle: "Production diagnostics for runtime, database, auth, email, and summarizer health.",
adminSystemRunProbe: "Run probe now",
adminSystemRunningProbe: "Running probe...",
adminSystemRefresh: "Refresh",
adminSystemRefreshing: "Refreshing...",
adminSystemEnvironment: "Environment",
adminSystemDatabase: "Database",
adminSystemConnected: "Connected",
adminSystemOffline: "Offline",
adminSystemSmtp: "SMTP",
adminSystemEnabled: "Enabled",
adminSystemDisabled: "Disabled",
adminSystemSummarizer: "Summarizer",
adminSystemHealthy: "Healthy",
adminSystemNoLatencyData: "No latency data",
adminSystemDatabaseStorage: "Database and storage",
adminSystemRuntimeAuth: "Runtime and auth",
adminSystemEmailConfig: "Email configuration",
adminSystemSummarizerRuntime: "Summarizer runtime",
adminSystemSmtpTest: "SMTP test email",
adminSystemSmtpTestBody: "Send a quick delivery check using the configured SMTP settings. Leave the recipient blank to use your admin email.",
adminSystemRecipientEmail: "Recipient email",
adminSystemRecipientPlaceholder: "Uses your admin email if left blank",
adminSystemSubject: "Subject",
adminSystemMessage: "Message",
adminSystemSendTestEmail: "Send test email",
adminSystemSending: "Sending...",
adminSystemSummarizerTelemetry: "Summarizer telemetry",
adminSystemDatabaseConnected: "Database connected",
adminSystemDatabaseIssue: "Database issue",
adminSystemAuthEnforced: "Auth enforced",
adminSystemAuthOptional: "Auth optional",
adminSystemGoogleReady: "Google sign-in ready",
adminSystemGoogleOff: "Google sign-in off",
adminSystemGmailReady: "Gmail ready",
adminSystemGmailIncomplete: "Gmail incomplete",
adminSystemGpuVisible: "GPU visible",
adminSystemCpuMode: "CPU mode",
adminSystemNoSmtpHost: "No SMTP host configured",
googleAccountTitle: "Google account",
googleSetupHint: "Set `REACT_APP_GOOGLE_CLIENT_ID` in your UI environment to enable Google sign-in and account linking.",
googleLinked: "Linked",
googleAvailableToLink: "Available to link",
googleLinkedDate: "Linked {date}",
googleSignInHint: "Sign in with a Google account that has already been linked to your Jobbjakt user.",
googleLinkedTo: "Linked to {email}.",
googleLinkedToYourAccount: "Linked to your Google account.",
googleBindHint: "Bind a Google account to this user so you can sign in with Google and still keep your normal app roles and data.",
googleExchangeHint: "Exchange your Google sign-in for a normal Jobbjakt session.",
googleSignedIn: "Signed in with Google.",
googleNotLinkedYet: "This Google account is not linked yet. Sign in locally first to bind it.",
googleLinkedSuccess: "Google account linked.",
googleLinkedSuccessWithEmail: "Linked Google account {email}.",
googleAuthFailed: "Google authentication failed.",
googleScriptLoadFailed: "Google auth script failed to load.",
googleUnlinked: "Google account unlinked.",
googleUnlinkFailed: "Failed to unlink Google account.",
signedOut: "Signed out.",
signedInAs: "Signed in as {name}.",
unlinkGoogle: "Unlink Google",
importExportTitle: "Import / Export",
importExportBody: "Import expects the JSON exported by this app (an array of job objects with embedded company).",
exportJson: "Export JSON",
exportCsv: "Export CSV",
importJson: "Import JSON",
convertCsvToImportJson: "Convert CSV to Import JSON",
exportedJobs: "Exported jobs ({format}).",
exportFailed: "Export failed.",
importedJobs: "Imported {count} jobs.",
importFailedJson: "Import failed (expecting exported JSON array).",
convertedRows: "Converted {count} rows to import JSON.",
csvConversionFailed: "CSV conversion failed.",
lastImport: "Last import: {count}",
signInTitle: "Sign in",
authRequired: "Authentication is required to use this app.",
authOptional: "Authentication is optional in this environment.",
emailAndPassword: "Email & password",
google: "Google",
createAccount: "Create account",
signedIn: "Signed in.",
loginFailed: "Login failed.",
resetPasswordTitle: "Reset password",
resetPasswordBody: "Set a new password for your account.",
missingResetLinkInfo: "Missing email/token in link.",
passwordResetSuccess: "Password reset. Please sign in.",
resetFailed: "Reset failed.",
backToLogin: "Back to login",
updatePassword: "Update password",
jobTableSearch: "Search",
jobTableSearchPlaceholder: "Title, company, notes, messages",
jobTableStatus: "Status",
jobTableAll: "All",
jobTableCompany: "Company",
jobTableLocation: "Location",
jobTableNeedsFollowUp: "Needs follow-up",
jobTableReadiness: "Readiness",
jobTableAllReadiness: "All readiness",
jobTableNeedsWork: "Needs work",
jobTableInterviewStage: "Interview stage",
jobTableShowDeleted: "Show deleted",
jobTableColumns: "Columns",
jobTableSelected: "{count} selected",
jobTableRestoreSelected: "Restore selected",
jobTableDeleteSelected: "Delete selected",
jobTableUpdatedJobs: "Updated {count} jobs.",
jobTableBulkActionFailed: "Bulk action failed.",
jobTableMoveToTrashTitle: "Move job to trash",
jobTableMoveJobsToTrashTitle: "Move jobs to trash",
jobTableMove: "Move",
jobTableMoveOneConfirm: "Move \"{title}\" at {company} to trash?",
jobTableMoveManyConfirm: "Move {count} selected jobs to trash?",
jobTableMovedToTrash: "Job moved to trash.",
jobTableDeleteFailed: "Failed to delete job.",
jobTableRestored: "Job restored.",
jobTableRestoreFailed: "Failed to restore job.",
jobTableStatusSet: "Status set to {status}.",
jobTableStatusUpdateFailed: "Failed to update status.",
jobTableDateApplied: "Date Applied",
jobTableDays: "Days",
jobTableRole: "Role",
jobTableActions: "Actions",
jobTableLink: "Link",
jobTableEdit: "Edit",
jobTableQuickStatus: "Quick status",
jobTableOpen: "Open",
jobTableSoftDelete: "Soft delete",
jobTableRestore: "Restore",
jobTableFollowUp: "Follow up",
jobTableCvMissing: "CV missing",
jobTableCvReady: "CV ready",
jobTableOpenListing: "Open listing",
jobTableSkills: "Skills",
jobTableNoTags: "No tags",
jobTableOverview: "Overview",
jobTableNoSummaryYet: "No summary yet.",
jobTableNoJobsFound: "No jobs found.",
jobTableSetStatus: "Set {status}",
editJobTitle: "Edit job",
rulesTitle: "Follow-up + Ghosting Rules",
rulesBody: "Jobs get a “Follow up” flag based on these thresholds. Ghosting is automatic.",
rulesAppliedFollowUpDays: "Applied: follow-up days",
rulesAppliedGhostDays: "Applied: ghost days",
rulesOfferFollowUpDays: "Offer: follow-up days",
rulesOfferGhostDays: "Offer: ghost days",
rulesFeedbackFollowUpDays: "Feedback: follow-up days",
rulesFeedbackGhostDays: "Feedback: ghost days",
rulesSaving: "Saving...",
rulesSave: "Save Rules",
},
no: {
appTitle: "Jobbsporing",
appTitle: "Jobbjakt",
appTagline: "Hold oversikt over jobbsøkingen",
dashboard: "Dashboard",
jobApplications: "Jobbsøknader",
reminders: "Påminnelser",
@@ -28,8 +435,412 @@ export const translations = {
addJobApplication: "Legg til jobbsøknad",
company: "Selskap",
location: "Sted",
home: "Hjem",
analytics: "Analyse",
overview: "Oversikt",
account: "Konto",
profile: "Profil",
admin: "Admin",
auditLog: "Revisjonslogg",
users: "Brukere",
system: "System",
systemStatus: "Systemstatus",
manage: "Administrer",
notifications: "Varsler",
quickSearch: "Hurtigsøk",
searchPlaceholder: "Søk etter jobber, selskaper eller handlinger",
noMatchingCommands: "Ingen treff på kommandoer eller poster.",
goToDashboard: "Gå til dashboard",
analyticsOverview: "Analyseoversikt",
goToJobs: "Gå til jobber",
mainApplicationsTable: "Hovedtabell for søknader",
goToReminders: "Gå til påminnelser",
followUpQueue: "Oppfølgingskø",
goToCompanies: "Gå til selskaper",
crmAndSourceTracking: "CRM og kildesporing",
goToSettings: "Gå til innstillinger",
preferencesAndAdminTools: "Preferanser og adminverktøy",
addNewJob: "Legg til ny jobb",
openAddJobModal: "Åpne dialogen for ny jobb",
openCompanies: "Åpne selskaper",
openJobListAndSearchResult: "Åpne jobbliste og søkeresultat",
profileMenu: "Profil",
settingsMenu: "Innstillinger",
signOut: "Logg ut",
user: "Bruker",
superAdmin: "Superadmin",
close: "Lukk",
cancel: "Avbryt",
save: "Lagre",
create: "Opprett",
createJob: "Opprett jobb",
createAndAddAnother: "Opprett og legg til en til",
loading: "Laster...",
notFoundTitle: "Siden ble ikke funnet",
notFoundBody: "Siden du lette etter finnes ikke eller kan ha blitt flyttet.",
appErrorTitle: "Noe gikk galt",
appErrorBody: "Det oppstod en uventet feil under lasting av siden.",
goHome: "Gå hjem",
goBack: "Gå tilbake",
addJobModalCompanySection: "Selskap",
addJobModalCreateCompany: "Opprett \"{name}\"",
addJobModalCompanyLocation: "Selskapssted",
addJobModalCompanySource: "Selskapskilde",
addJobModalPossibleDuplicates: "Mulige duplikater funnet",
addJobModalJobApplicationSection: "Jobbsøknad",
addJobModalJobUrl: "Jobb-URL",
addJobModalImportFromUrl: "Importer fra URL",
addJobModalImporting: "Importerer...",
addJobModalDateApplied: "Søkt dato",
addJobModalStatus: "Status",
addJobModalJobTitle: "Stillingstittel",
addJobModalSalary: "Lønn",
addJobModalDeadline: "Frist",
addJobModalDescriptionOriginal: "Beskrivelse (original)",
addJobModalTranslatedDescription: "Oversatt beskrivelse ({language})",
addJobModalDescriptionLanguage: "Språk i beskrivelse",
addJobModalTranslatedShown: "Vises fordi kildespråket er forskjellig fra ditt foretrukne språk ({language}).",
addJobModalTranslatedHidden: "Oversatt tekst vises bare når kildespråket er forskjellig fra ditt foretrukne språk.",
addJobModalNotes: "Notater",
addJobModalDocuments: "Dokumenter",
addJobModalResume: "CV / resymé",
addJobModalResumeHelp: "Foretrekk PDF, DOC eller DOCX. Tekstuttrekk kan skje senere der det støttes.",
addJobModalCoverLetter: "Søknadsbrev",
addJobModalCoverLetterHelp: "Last opp én eller flere versjoner i stedet for å lime inn lang tekst i skjemaet.",
addJobModalPortfolio: "Portefølje",
addJobModalPortfolioHelp: "Bruk PDF, DOC, DOCX, TXT, MD eller bildefiler for skannede dokumenter/skjermbilder.",
addJobModalOtherFiles: "Andre filer",
addJobModalOtherFilesHelp: "Sertifikater, referanser, rollebeskrivelser eller andre vedlegg.",
addJobModalChooseFiles: "Velg filer",
addJobModalNoFilesSelected: "Ingen filer valgt",
addJobModalFilesSelected: "{count} filer valgt",
addJobModalFileReady: "{count} fil klar",
addJobModalFilesReady: "{count} filer klare",
addJobModalPreferredFiles: "Foretrukket: PDF, DOC, DOCX",
addJobModalTextImageAllowed: "Tekst- og bildefiler er også tillatt",
addJobModalPasteUrlFirst: "Lim inn en jobb-URL først.",
addJobModalImported: "Importert.",
addJobModalImportFailed: "Import mislyktes.",
addJobModalFailedCreateCompany: "Kunne ikke opprette selskap.",
addJobModalSelectCompany: "Velg eller opprett et selskap.",
addJobModalJobAdded: "Jobb lagt til.",
addJobModalJobAndFilesAdded: "Jobb og filer lagt til.",
addJobModalJobCreatedUploadFailed: "Jobben ble opprettet, men filopplasting mislyktes.",
addJobModalJobCreatedFilesNotAttached: "Jobben ble opprettet. Filene kunne ikke knyttes automatisk.",
addJobModalFailedAddJob: "Kunne ikke legge til jobb.",
statusApplied: "Søkt",
statusWaiting: "Venter",
statusInterview: "Intervju",
statusOffer: "Tilbud",
statusRejected: "Avslått",
statusGhosted: "Ghostet",
settingsTitle: "Innstillinger",
settingsSubtitle: "Preferanser og adminverktøy.",
settingsTabGeneral: "Generelt",
settingsTabFollowUps: "Oppfølging",
settingsTabNotifications: "Varsler",
settingsTabAccount: "Konto",
settingsTabBackup: "Sikkerhetskopi",
settingsAppearance: "Utseende",
settingsTheme: "Tema",
settingsThemeSystem: "System",
settingsThemeDark: "Mørkt",
settingsThemeLight: "Lyst",
settingsAccent: "Aksent",
settingsReset: "Tilbakestill",
settingsSavedPerUser: "Lagres per bruker i denne nettleseren.",
settingsLanguageTitle: "Språk og lokalisering",
settingsLanguageBody: "Velg foretrukket språk i appen. Dette brukes også når appen avgjør om importerte stillingsbeskrivelser skal vise oversatt tekst.",
settingsPreferredLanguage: "Foretrukket språk",
settingsEnglish: "Engelsk",
settingsNorwegian: "Norsk Bokmål",
settingsMorePagesSoon: "Flere sider flyttes til dette oversettelsessystemet etter hvert som UI-oppryddingen fortsetter.",
settingsJobs: "Jobber",
settingsPagination: "Paginering",
settingsRowsPerPage: "Rader per side",
settingsColumns: "Kolonner",
settingsColumnStatus: "Status",
settingsColumnDateApplied: "Søkt dato",
settingsColumnDays: "Dager",
settingsColumnJobUrl: "Jobb-URL",
settingsNotificationsTitle: "E-postvarsler",
settingsNotificationsBody: "Varsler sendes via SMTP (Gmail fungerer). Konfigurer SMTP i API-et (`Email:*`-innstillinger eller miljøvariabler som `EMAIL_SMTP_HOST`).",
profileTitle: "Profil",
profileHeadlinePlaceholder: "Legg til en kort overskrift for å gjøre kontovisningen mer personlig.",
profileLocalAccount: "Lokal konto",
profileGoogleSession: "Google-økt",
profileExternalSession: "Ekstern økt",
profileGoogleLinked: "Google koblet",
profileGoogleLinkedWithEmail: "Google koblet: {email}",
profileGoogleNotLinked: "Google ikke koblet",
profileCvReady: "CV klart · {count} ord",
profileCvMissing: "CV mangler",
profileChangeImage: "Bytt bilde",
profileRemoveImage: "Fjern",
profileImageUpdated: "Profilbildet ble oppdatert.",
profileImageRemoved: "Profilbildet ble fjernet.",
profileImageUploadFailed: "Kunne ikke laste opp profilbildet.",
profileImageRemoveFailed: "Kunne ikke fjerne profilbildet.",
profileAccountSection: "Konto",
profileReadOnlyInfo: "Denne økten bruker ikke et lokalt apptoken, så profilendringer er skrivebeskyttet akkurat nå.",
profileDisplayName: "Visningsnavn",
profileUsername: "Brukernavn",
profileFirstName: "Fornavn",
profileLastName: "Etternavn",
profileEmail: "E-post",
profileHeadline: "Profiloverskrift",
profileHeadlineHelp: "Lagres bare i denne nettleseren for å gjøre arbeidsområdet mer personlig.",
profileMasterCv: "Hoved-CV",
profileMasterCvBody: "Last opp en PDF, et Word-dokument, en ren tekstfil, en markdown-fil eller et bildeskann. Der det støttes kan appen automatisk hente ut tekst og fylle inn hoved-CV-en din for tilpasning og kontakt.",
profileUploadCv: "Last opp CV",
profileUploading: "Laster opp...",
profileCopyCvText: "Kopier CV-tekst",
profileCvUploaded: "CV lastet opp og behandlet.",
profileCvUploadFailed: "Kunne ikke laste opp CV.",
profileCvTextLabel: "Profil-CV / hovedtekst for CV",
profileCvTextHelp: "Hold denne oppdatert og konkret. Ta med nylige roller, verktøy, prestasjoner, målbare resultater og arbeidet du vil bli ansatt for neste gang. Hvis tekstuttrekket mangler noe, kan du redigere manuelt her.",
profileCvPreferredUploads: "Foretrukne opplastinger: PDF, DOC, DOCX. Tekst- og bildefiler aksepteres også.",
profileSaveChanges: "Lagre endringer",
profileUpdated: "Profil oppdatert.",
profileUpdateFailed: "Kunne ikke oppdatere profil.",
profileChangePassword: "Bytt passord",
profilePasswordLocalOnly: "Passordendringer er bare tilgjengelige for lokale kontoer.",
profileCurrentPassword: "Nåværende passord",
profileNewPassword: "Nytt passord",
profileUpdatePassword: "Oppdater passord",
profilePasswordUpdated: "Passord oppdatert.",
profilePasswordUpdateFailed: "Kunne ikke endre passord.",
cropDialogTitle: "Beskjær profilbilde",
cropDialogBody: "Plasser og zoom bildet. Det lagrede avataren eksporteres som en kvadratisk 512×512-fil.",
cropDialogZoom: "Zoom",
cropDialogSave: "Lagre bilde",
dashboardOverviewTitle: "Dashboard-oversikt",
dashboardOverviewBody: "Kun overordnet aktivitet for jobbsøking vises her. Systemhelse og pipelinediagnostikk ligger nå på Systemsiden for å unngå dupliserte eller motstridende statusdata.",
dashboardCustomize: "Tilpass dashboard",
dashboardSummaryCards: "Oppsummeringskort",
dashboardActivityChart: "Aktivitetsgraf",
dashboardConversionFunnel: "Konverteringstrakt",
dashboardTopCompanies: "Topp selskaper",
dashboardSkillsInsights: "Kompetanseinnsikt",
dashboardActiveApplications: "Aktive søknader",
dashboardCurrentlyInProgress: "Pågår nå",
dashboardApplied30Days: "Søkt (30 dager)",
dashboardNewApplications: "Nye søknader",
dashboardMedianFirstResponse: "Median første svar",
dashboardDaysUntilFirstReply: "Dager til første svar",
dashboardResponsesLogged: "Registrerte svar",
dashboardAcrossActiveJobs: "På tvers av aktive jobber",
dashboardLowReadiness: "Lav beredskap",
dashboardMissingTailoredCv: "Påminnelsesjobber uten skreddersydd CV",
dashboardApplicationActivity: "Søknadsaktivitet",
dashboardMonthlyApplicationsResponses: "Månedlige søknader versus svar.",
dashboardConversionFunnelTitle: "Konverteringstrakt",
dashboardResponseSources: "Svar etter kilde",
dashboardTopCompaniesByActivity: "Topp selskaper etter aktivitet",
dashboardTopSkills: "Topp ferdigheter",
dashboardNoTagsYet: "Ingen tagger ennå.",
dashboardSkillTags: "ferdighetstagger",
dashboardSkillTrends: "Ferdighetstrender",
dashboardNoTagTrendData: "Ingen trenddata for tagger ennå.",
remindersTitle: "Trenger oppfølging",
remindersSubtitle: "Gruppert etter den mest nyttige neste handlingen slik at du kan lukke hull raskere.",
remindersMissingTailoredCv: "Mangler skreddersydd CV",
remindersMissingInterviewPrep: "Mangler intervjuforberedelse",
remindersFollowUpDue: "Oppfølging forfaller",
remindersOther: "Andre påminnelser",
remindersNothing: "Ingenting å følge opp akkurat nå.",
remindersTip: "Tips: fokuser på skreddersydd CV og intervjuforberedelse først for de mest verdifulle rollene.",
remindersOpen: "Åpne",
remindersClear: "Fjern",
remindersFollowUpLabel: "Følg opp",
remindersFollowUpDate: "Oppfølging: {date}",
remindersFollowUpCleared: "Oppfølging fjernet.",
remindersFollowUpSet: "Oppfølging satt.",
remindersFollowUpFailed: "Kunne ikke sette oppfølging.",
companiesEmpty: "Ingen selskaper ennå.",
companiesName: "Navn",
companiesLocation: "Sted",
companiesSource: "Kilde",
companiesPipeline: "Pipeline",
companiesRecruiter: "Rekrutterer",
companiesNextContact: "Neste kontakt",
companiesEdit: "Rediger selskap",
companiesPipelineStage: "Pipelinetrinn",
companiesRecruiterName: "Navn på rekrutterer",
companiesRecruiterEmail: "E-post til rekrutterer",
companiesRecruiterLinkedIn: "Rekrutterer på LinkedIn",
companiesLastContacted: "Sist kontaktet",
companiesNextContactField: "Neste kontakt",
companiesUpdated: "Selskap oppdatert.",
companiesUpdateFailed: "Kunne ikke oppdatere selskap.",
adminUsersTitle: "Brukere",
adminUsersSubtitle: "Brukeradministrasjon kun for administratorer.",
adminUsersCreateUser: "Opprett bruker",
adminUsersAdmin: "Admin",
adminUsersSendReset: "Send tilbakestilling",
adminUsersDelete: "Slett",
adminUsersConfirmed: "Bekreftet",
adminUsersActions: "Handlinger",
adminUsersNoUsers: "Ingen brukere.",
adminUsersRolesUpdated: "Roller oppdatert.",
adminUsersRolesUpdateFailed: "Kunne ikke oppdatere roller.",
adminUsersResetSent: "E-post for passordtilbakestilling sendt.",
adminUsersResetFailed: "Kunne ikke sende tilbakestilling.",
adminUsersDeleteConfirmTitle: "Slett bruker",
adminUsersDeleted: "Bruker slettet.",
adminUsersDeleteFailed: "Kunne ikke slette bruker.",
adminUsersCreated: "Bruker opprettet.",
adminUsersCreateFailed: "Kunne ikke opprette bruker.",
adminSystemTitle: "Systemstatus",
adminSystemSubtitle: "Produksjonsdiagnostikk for kjøretid, database, autentisering, e-post og oppsummeringshelse.",
adminSystemRunProbe: "Kjør probe nå",
adminSystemRunningProbe: "Kjører probe...",
adminSystemRefresh: "Oppdater",
adminSystemRefreshing: "Oppdaterer...",
adminSystemEnvironment: "Miljø",
adminSystemDatabase: "Database",
adminSystemConnected: "Tilkoblet",
adminSystemOffline: "Frakoblet",
adminSystemSmtp: "SMTP",
adminSystemEnabled: "Aktivert",
adminSystemDisabled: "Deaktivert",
adminSystemSummarizer: "Oppsummerer",
adminSystemHealthy: "Frisk",
adminSystemNoLatencyData: "Ingen latensdata",
adminSystemDatabaseStorage: "Database og lagring",
adminSystemRuntimeAuth: "Kjøretid og autentisering",
adminSystemEmailConfig: "E-postkonfigurasjon",
adminSystemSummarizerRuntime: "Oppsummeringskjøretid",
adminSystemSmtpTest: "SMTP-test e-post",
adminSystemSmtpTestBody: "Send en rask leveringssjekk med de konfigurerte SMTP-innstillingene. La mottakeren stå tom for å bruke admin-eposten din.",
adminSystemRecipientEmail: "Mottaker e-post",
adminSystemRecipientPlaceholder: "Bruker admin-eposten din hvis feltet står tomt",
adminSystemSubject: "Emne",
adminSystemMessage: "Melding",
adminSystemSendTestEmail: "Send test-e-post",
adminSystemSending: "Sender...",
adminSystemSummarizerTelemetry: "Oppsummeringstelemetri",
adminSystemDatabaseConnected: "Database tilkoblet",
adminSystemDatabaseIssue: "Databaseproblem",
adminSystemAuthEnforced: "Autentisering påkrevd",
adminSystemAuthOptional: "Autentisering valgfri",
adminSystemGoogleReady: "Google-innlogging klar",
adminSystemGoogleOff: "Google-innlogging av",
adminSystemGmailReady: "Gmail klar",
adminSystemGmailIncomplete: "Gmail ufullstendig",
adminSystemGpuVisible: "GPU synlig",
adminSystemCpuMode: "CPU-modus",
adminSystemNoSmtpHost: "Ingen SMTP-vert konfigurert",
googleAccountTitle: "Google-konto",
googleSetupHint: "Sett `REACT_APP_GOOGLE_CLIENT_ID` i UI-miljøet ditt for å aktivere Google-innlogging og kontokobling.",
googleLinked: "Koblet",
googleAvailableToLink: "Tilgjengelig for kobling",
googleLinkedDate: "Koblet {date}",
googleSignInHint: "Logg inn med en Google-konto som allerede er koblet til Jobbjakt-brukeren din.",
googleLinkedTo: "Koblet til {email}.",
googleLinkedToYourAccount: "Koblet til Google-kontoen din.",
googleBindHint: "Koble en Google-konto til denne brukeren slik at du kan logge inn med Google og fortsatt beholde vanlige approller og data.",
googleExchangeHint: "Bytt Google-innloggingen din mot en vanlig Jobbjakt-økt.",
googleSignedIn: "Logget inn med Google.",
googleNotLinkedYet: "Denne Google-kontoen er ikke koblet ennå. Logg inn lokalt først for å koble den.",
googleLinkedSuccess: "Google-konto koblet.",
googleLinkedSuccessWithEmail: "Koblet Google-konto {email}.",
googleAuthFailed: "Google-autentisering mislyktes.",
googleScriptLoadFailed: "Kunne ikke laste Google-autentiseringsskriptet.",
googleUnlinked: "Google-konto koblet fra.",
googleUnlinkFailed: "Kunne ikke koble fra Google-kontoen.",
signedOut: "Logget ut.",
signedInAs: "Logget inn som {name}.",
unlinkGoogle: "Koble fra Google",
importExportTitle: "Import / eksport",
importExportBody: "Import forventer JSON eksportert av denne appen (en matrise med jobbobjekter med innebygd selskap).",
exportJson: "Eksporter JSON",
exportCsv: "Eksporter CSV",
importJson: "Importer JSON",
convertCsvToImportJson: "Konverter CSV til import-JSON",
exportedJobs: "Eksporterte jobber ({format}).",
exportFailed: "Eksport mislyktes.",
importedJobs: "Importerte {count} jobber.",
importFailedJson: "Import mislyktes (forventer eksportert JSON-array).",
convertedRows: "Konverterte {count} rader til import-JSON.",
csvConversionFailed: "CSV-konvertering mislyktes.",
lastImport: "Siste import: {count}",
signInTitle: "Logg inn",
authRequired: "Autentisering er påkrevd for å bruke denne appen.",
authOptional: "Autentisering er valgfri i dette miljøet.",
emailAndPassword: "E-post og passord",
google: "Google",
createAccount: "Opprett konto",
signedIn: "Logget inn.",
loginFailed: "Innlogging mislyktes.",
resetPasswordTitle: "Tilbakestill passord",
resetPasswordBody: "Sett et nytt passord for kontoen din.",
missingResetLinkInfo: "Mangler e-post/token i lenken.",
passwordResetSuccess: "Passord tilbakestilt. Vennligst logg inn.",
resetFailed: "Tilbakestilling mislyktes.",
backToLogin: "Tilbake til innlogging",
updatePassword: "Oppdater passord",
jobTableSearch: "Søk",
jobTableSearchPlaceholder: "Tittel, selskap, notater, meldinger",
jobTableStatus: "Status",
jobTableAll: "Alle",
jobTableCompany: "Selskap",
jobTableLocation: "Sted",
jobTableNeedsFollowUp: "Trenger oppfølging",
jobTableReadiness: "Beredskap",
jobTableAllReadiness: "All beredskap",
jobTableNeedsWork: "Trenger arbeid",
jobTableInterviewStage: "Intervjustadium",
jobTableShowDeleted: "Vis slettede",
jobTableColumns: "Kolonner",
jobTableSelected: "{count} valgt",
jobTableRestoreSelected: "Gjenopprett valgte",
jobTableDeleteSelected: "Slett valgte",
jobTableUpdatedJobs: "Oppdaterte {count} jobber.",
jobTableBulkActionFailed: "Massehandling mislyktes.",
jobTableMoveToTrashTitle: "Flytt jobb til papirkurv",
jobTableMoveJobsToTrashTitle: "Flytt jobber til papirkurv",
jobTableMove: "Flytt",
jobTableMoveOneConfirm: "Flytt \"{title}\" hos {company} til papirkurven?",
jobTableMoveManyConfirm: "Flytt {count} valgte jobber til papirkurven?",
jobTableMovedToTrash: "Jobb flyttet til papirkurven.",
jobTableDeleteFailed: "Kunne ikke slette jobb.",
jobTableRestored: "Jobb gjenopprettet.",
jobTableRestoreFailed: "Kunne ikke gjenopprette jobb.",
jobTableStatusSet: "Status satt til {status}.",
jobTableStatusUpdateFailed: "Kunne ikke oppdatere status.",
jobTableDateApplied: "Søkt dato",
jobTableDays: "Dager",
jobTableRole: "Rolle",
jobTableActions: "Handlinger",
jobTableLink: "Lenke",
jobTableEdit: "Rediger",
jobTableQuickStatus: "Hurtigstatus",
jobTableOpen: "Åpne",
jobTableSoftDelete: "Myk sletting",
jobTableRestore: "Gjenopprett",
jobTableFollowUp: "Følg opp",
jobTableCvMissing: "CV mangler",
jobTableCvReady: "CV klar",
jobTableOpenListing: "Åpne stilling",
jobTableSkills: "Ferdigheter",
jobTableNoTags: "Ingen tagger",
jobTableOverview: "Oversikt",
jobTableNoSummaryYet: "Ingen oppsummering ennå.",
jobTableNoJobsFound: "Ingen jobber funnet.",
jobTableSetStatus: "Sett {status}",
editJobTitle: "Rediger jobb",
rulesTitle: "Regler for oppfølging og ghosting",
rulesBody: "Jobber får et «Følg opp»-flagg basert på disse tersklene. Ghosting skjer automatisk.",
rulesAppliedFollowUpDays: "Søkt: oppfølgingsdager",
rulesAppliedGhostDays: "Søkt: ghostingdager",
rulesOfferFollowUpDays: "Tilbud: oppfølgingsdager",
rulesOfferGhostDays: "Tilbud: ghostingdager",
rulesFeedbackFollowUpDays: "Tilbakemelding: oppfølgingsdager",
rulesFeedbackGhostDays: "Tilbakemelding: ghostingdager",
rulesSaving: "Lagrer...",
rulesSave: "Lagre regler",
},
} as const;
export type TranslationKey = keyof typeof translations.en;