Style footer repository link
This commit is contained in:
parent
6bcb87febe
commit
15b139d877
672
src/App.tsx
672
src/App.tsx
File diff suppressed because it is too large
Load Diff
808
src/i18n.tsx
Normal file
808
src/i18n.tsx
Normal file
@ -0,0 +1,808 @@
|
||||
import { createContext, useContext, useMemo, useState } from "react";
|
||||
import type React from "react";
|
||||
import {
|
||||
accommodationLabels as nbAccommodationLabels,
|
||||
categoryTooltipTexts as nbCategoryTooltipTexts,
|
||||
delayedSymptomLabels as nbDelayedSymptomLabels,
|
||||
limitationLabels as nbLimitationLabels,
|
||||
negativeScale as nbNegativeScale,
|
||||
positiveScale as nbPositiveScale,
|
||||
recoveryLabels as nbRecoveryLabels,
|
||||
statusLabels as nbStatusLabels,
|
||||
taskCompletionLabels as nbTaskCompletionLabels,
|
||||
taskTypeLabels as nbTaskTypeLabels,
|
||||
tooltipTexts as nbTooltipTexts,
|
||||
worseLabels as nbWorseLabels
|
||||
} from "./labels";
|
||||
import type {
|
||||
Accommodation,
|
||||
DelayedSymptom,
|
||||
Limitation,
|
||||
RecoveryStatus,
|
||||
ReportStatus,
|
||||
ScaleValue,
|
||||
TaskCompletion,
|
||||
TaskType,
|
||||
VerificationStatus,
|
||||
WorseThanBefore
|
||||
} from "./types";
|
||||
|
||||
export type Language = "nb-NO" | "en";
|
||||
|
||||
const LANGUAGE_KEY = "arbeidspuls.language";
|
||||
|
||||
type TranslationTree = { [key: string]: string | TranslationTree };
|
||||
|
||||
const nb = {
|
||||
common: {
|
||||
cancel: "Avbryt",
|
||||
close: "Lukk",
|
||||
copied: "Kopiert",
|
||||
delete: "Slett",
|
||||
save: "Lagre",
|
||||
notSelected: "Ikke valgt",
|
||||
notProvided: "Ikke oppgitt",
|
||||
notCalculated: "Ikke beregnet",
|
||||
unknownWorkplace: "Ukjent arbeidssted"
|
||||
},
|
||||
app: {
|
||||
eyebrow: "Lokal egenvurdering",
|
||||
title: "Arbeidsevne",
|
||||
evaluator: "Veileder",
|
||||
navLabel: "Hovednavigasjon"
|
||||
},
|
||||
nav: {
|
||||
register: "Registrer",
|
||||
followup: "Oppfølging",
|
||||
overview: "Oversikt",
|
||||
privacy: "Personvern"
|
||||
},
|
||||
footer: {
|
||||
norwegian: "Norsk",
|
||||
english: "English",
|
||||
switchToNorwegian: "Bytt språk til norsk",
|
||||
switchToEnglish: "Switch language to English",
|
||||
developedBy: "Utviklet av Rolfsvaag Datateknikk med",
|
||||
heart: "hjerte",
|
||||
languageChoice: "Språkvalg",
|
||||
sourceCode: "Kildekode"
|
||||
},
|
||||
deleteModal: {
|
||||
title: "Bekreft sletting",
|
||||
body: "Du er i ferd med å slette: {itemName}. Denne handlingen kan ikke angres.",
|
||||
waiting: "Slett ({secondsLeft})",
|
||||
ready: "Slett"
|
||||
},
|
||||
register: {
|
||||
intro: "Fyll ut hovedfeltene. Valgfrie detaljer kan legges til ved behov.",
|
||||
workSession: "Arbeidsøkt",
|
||||
workplace: "Arbeidssted",
|
||||
workAbility: "Arbeidsevne akkurat nå",
|
||||
energyLevel: "Energinivå",
|
||||
mentalClarity: "Mental klarhet / konsentrasjon",
|
||||
symptomBurden: "Symptombelastning",
|
||||
effortStrain: "Anstrengelse / belastning",
|
||||
optional: "Valgfritt",
|
||||
startTime: "Starttid",
|
||||
endTime: "Sluttid",
|
||||
continueQuestion: "Kan du fortsette?",
|
||||
chooseBestNow: "Velg det som passer best akkurat nå.",
|
||||
optionalDetails: "Valgfrie detaljer",
|
||||
physicalEnergy: "Fysisk energi",
|
||||
mentalEnergy: "Mental energi",
|
||||
productivity: "Opplevd produktivitet",
|
||||
taskCompletion: "Hvor mye fikk du gjort?",
|
||||
taskTypes: "Arbeidet innebar",
|
||||
limitations: "Hva begrenset deg mest?",
|
||||
accommodations: "Hva hjalp?",
|
||||
note: "Kort kommentar",
|
||||
missing: "Mangler: {fields}.",
|
||||
saved: "Rapport lagret lokalt.",
|
||||
saveReport: "Lagre rapport",
|
||||
oneEqualsFiveEquals: "1 = {low}, 5 = {high}."
|
||||
},
|
||||
followup: {
|
||||
intro: "Brukes neste dag eller senere for å fange forsinket reaksjon.",
|
||||
worseQuestion: "Er du verre enn før arbeidsøkten?",
|
||||
chooseOne: "Velg ett svar.",
|
||||
connectReport: "Knytt til rapport",
|
||||
delayedSymptoms: "Forsinkede symptomer",
|
||||
recovery: "Restitusjon",
|
||||
missingWorse: "Velg om du er verre enn før arbeidsøkten.",
|
||||
saved: "Oppfølging lagret lokalt.",
|
||||
saveFollowup: "Lagre oppfølging",
|
||||
orphan: "Oppfølging uten valgt hovedrapport",
|
||||
worsePrefix: "verre"
|
||||
},
|
||||
overview: {
|
||||
noReports: "Ingen rapporter er lagret ennå.",
|
||||
report: "Rapport",
|
||||
followup: "Oppfølging",
|
||||
closeReport: "Lukk rapportvisning",
|
||||
trend: "Trend",
|
||||
explainTrend: "Forklar trendgraf",
|
||||
trendTooltip: "Grafen bruker normaliserte 0-100-verdier. Høyere verdi betyr bedre funksjon eller lavere belastning.",
|
||||
minimumTwo: "Minst to rapporter i valgt periode trengs for graf.",
|
||||
average: "Normalisert snitt i perioden",
|
||||
chartLabel: "Tidsbasert trendgraf for rapporter",
|
||||
dashedLine: "Striplet linje betyr at det er 3 dager eller mer mellom rapportene.",
|
||||
from: "Fra",
|
||||
to: "Til",
|
||||
all: "Alt",
|
||||
week: "1 uke",
|
||||
month: "1 måned",
|
||||
threeMonths: "3 måneder",
|
||||
sixMonths: "6 måneder",
|
||||
year: "1 år",
|
||||
threeYears: "3 år",
|
||||
fiveYears: "5 år"
|
||||
},
|
||||
privacy: {
|
||||
warning: "Rapportene kan inneholde sensitive helseopplysninger og lagres bare lokalt i denne nettleseren.",
|
||||
exportJson: "Eksporter JSON",
|
||||
importBackup: "Importer sikkerhetskopi",
|
||||
deleteAll: "Slett alle lokale data",
|
||||
importHelp: "Importer tidligere eksportert JSON hvis du har byttet enhet eller mistet lokale data. Importerte data lagres lokalt i denne nettleseren.",
|
||||
importFrom: "Importer data fra {fileName}",
|
||||
importStats:
|
||||
"Nye rapporter: {reports}. Nye oppfølginger: {followups}. Duplikater hoppet over: {duplicates}. Konflikter: {conflicts}.",
|
||||
cancelImport: "Avbryt import",
|
||||
importData: "Importer data",
|
||||
baseCopy:
|
||||
"Appen samler ikke inn navn, fødselsnummer eller kontaktinformasjon. Data sendes ikke til eksterne tjenester av appen. Fritekst er valgfritt og bør bare brukes når det er nødvendig.",
|
||||
storageTitle: "Hvor lagres dataene?",
|
||||
storageBody:
|
||||
"Rapportene lagres lokalt i nettleseren på enheten du bruker, i localStorage. Det betyr at dataene normalt blir liggende på telefonen eller datamaskinen din.",
|
||||
backupTitle: "Backup og enhetsbytte",
|
||||
backupBody:
|
||||
"Fordi dataene lagres lokalt, kan de gå tapt hvis du bytter enhet, sletter nettleserdata, bruker en annen nettleser eller tilbakestiller enheten. Eksporter JSON-fil jevnlig hvis du vil ta vare på rapportene.",
|
||||
sharingTitle: "Deling med veileder",
|
||||
sharingBody:
|
||||
"Hvis en veileder eller annen person skal gjennomgå rapportene, må du eksportere en JSON-fil og dele filen med dem, for eksempel via e-post. Mottakeren kan importere JSON-filen i veiledervisningen.",
|
||||
sensitiveTitle: "Sensitive opplysninger",
|
||||
sensitiveBody:
|
||||
"Rapportene kan inneholde helse- og funksjonsrelatert informasjon. Del JSON-filen på en måte som passer for sensitiv informasjon, og bare med personer som faktisk skal ha tilgang.",
|
||||
exportIntegrity: "Eksport inkluderer lokal integritetskontroll der rapportene har verifiseringsdata.",
|
||||
deleteAllName: "alle lokale rapporter"
|
||||
},
|
||||
evaluator: {
|
||||
eyebrow: "Veileder",
|
||||
title: "Veiledergjennomgang",
|
||||
backToApp: "Til appen",
|
||||
importJson: "Importer JSON",
|
||||
importHelp: "Velg en eksport fra appen. Data lagres lokalt i denne nettleseren.",
|
||||
displayName: "Navn/identifikator",
|
||||
optionalImportName: "Valgfritt ved import",
|
||||
connectExisting: "Knytt til eksisterende person",
|
||||
autoMatch: "Opprett ny eller bruk automatisk match",
|
||||
chooseJson: "Velg JSON-fil",
|
||||
exportBackup: "Eksporter veilederdata",
|
||||
importBackup: "Importer veilederbackup",
|
||||
importedBackup: "Veilederbackup er importert.",
|
||||
importStart: "Importer en JSON-fil for å starte gjennomgang.",
|
||||
saveName: "Lagre navn",
|
||||
deletePerson: "Slett bruker og rapporter",
|
||||
deletePersonTitle: "Sletter denne brukeren, importerte rapporter, oppfølginger og veiledernotater etter bekreftelse.",
|
||||
reportCount: "{count} rapporter",
|
||||
personNote: "Generelt notat for bruker",
|
||||
personNoteHelp: "Dette notatet gjelder personen som helhet, ikke én enkelt rapport.",
|
||||
saveNote: "Lagre notat",
|
||||
changedAt: "Sist endret: {time}",
|
||||
searchReports: "Søk i rapporter",
|
||||
newest: "Nyeste først",
|
||||
oldest: "Eldste først",
|
||||
noSelectedTitle: "Ingen rapport valgt",
|
||||
noSelectedBody: "Velg en rapport fra listen for å se detaljer, oppfølginger og notater.",
|
||||
details: "Vis detaljer",
|
||||
reportDetails: "Rapportdetaljer",
|
||||
followupDetails: "Oppfølgingsdetaljer",
|
||||
closeDetails: "Lukk detaljer",
|
||||
reportNote: "Notat til rapport",
|
||||
followupNote: "Notat til oppfølging",
|
||||
noteHelp: "Veiledernotatet lagres lokalt og endrer ikke importerte rapportdata.",
|
||||
deleteNote: "Slett notat",
|
||||
validationError: "Valideringsfeil",
|
||||
comment: "Kommentar",
|
||||
evaluatorNote: "Veiledernotat",
|
||||
followupNoteBadge: "Oppfølgingsnotat",
|
||||
hasFollowup: "Har oppfølging",
|
||||
hasComment: "Har brukerkommentar",
|
||||
hasEvaluatorNote: "Har veiledernotat",
|
||||
scoreValues: "Scoreverdier"
|
||||
},
|
||||
details: {
|
||||
summary: "Kort oppsummering",
|
||||
coreValues: "Kjerneverdier",
|
||||
details: "Detaljer",
|
||||
userComment: "Kommentar fra bruker",
|
||||
followups: "Oppfølginger",
|
||||
technical: "Teknisk informasjon",
|
||||
rawData: "Rådata",
|
||||
integrity: "Integritetskontroll",
|
||||
time: "Tidspunkt",
|
||||
workTime: "Arbeidstid",
|
||||
status: "Status",
|
||||
totalScore: "Totalscore",
|
||||
functionLevel: "Funksjonsnivå",
|
||||
description: "Beskrivelse",
|
||||
important: "Viktig",
|
||||
workAbility: "Arbeidsevne",
|
||||
energy: "Energinivå",
|
||||
physicalEnergy: "Fysisk energi",
|
||||
mentalEnergy: "Mental energi",
|
||||
mentalClarity: "Mental klarhet",
|
||||
symptoms: "Symptombelastning",
|
||||
strain: "Anstrengelse",
|
||||
productivity: "Produktivitet",
|
||||
taskCompletion: "Hvor mye fikk du gjort?",
|
||||
taskTypes: "Arbeidet innebar",
|
||||
limitations: "Begrenset av",
|
||||
accommodations: "Hva hjalp",
|
||||
comment: "Kommentar",
|
||||
noComment: "Ingen kommentar",
|
||||
noOptionalDetails: "Ingen valgfrie detaljer oppgitt.",
|
||||
technicalDescription: "Avansert informasjon for kontroll, feilsøking og verifisering.",
|
||||
rawDescription: "Full JSON-visning for feilsøking eller teknisk kontroll.",
|
||||
copyRaw: "Kopier rådata",
|
||||
reportId: "Rapport-ID",
|
||||
followupId: "Oppfølgings-ID",
|
||||
connectedReport: "Tilknyttet rapport",
|
||||
created: "Opprettet",
|
||||
reportDate: "Rapportdato",
|
||||
reportTime: "Rapporttid",
|
||||
integrityStatus: "Integritetsstatus",
|
||||
explanation: "Forklaring",
|
||||
algorithm: "Algoritme",
|
||||
signed: "Signert",
|
||||
payloadHash: "Payload-hash",
|
||||
worse: "Verre enn før",
|
||||
delayedSymptoms: "Forsinkede symptomer",
|
||||
recovery: "Restitusjon",
|
||||
outOfFive: "{value} av 5",
|
||||
worseValue: "Verre: {value}",
|
||||
recoveryValue: "Restitusjon: {value}"
|
||||
},
|
||||
danger: {
|
||||
title: "Faresone",
|
||||
deleteEntryHelp: "Dette sletter rapporten eller oppfølgingen fra lokal lagring i denne nettleseren.",
|
||||
deleteReport: "Slett rapport",
|
||||
deleteFollowup: "Slett oppfølging",
|
||||
reportName: "rapport",
|
||||
followupName: "oppfølging"
|
||||
},
|
||||
graphModal: {
|
||||
open: "Åpne graf i fullskjerm",
|
||||
title: "Grafanalyse",
|
||||
close: "Lukk graf",
|
||||
timeRange: "Tidsrom",
|
||||
visibleLines: "Synlige linjer",
|
||||
visibleHelp: "Trykk én gang for delvis synlig, to ganger for skjult.",
|
||||
snapHelp: "Hold musepekeren over grafen for å se nøyaktige rapportverdier.",
|
||||
noReports: "Ingen rapporter i valgt tidsrom.",
|
||||
noPoint: "Ingen rapportpunkt valgt",
|
||||
noPointHelp: "Hold musepekeren nær et punkt for å se faktiske rapportverdier.",
|
||||
nearestReport: "Nærmeste rapport",
|
||||
chartLabel: "Fullskjerm trendgraf for rapporter"
|
||||
},
|
||||
scoring: {
|
||||
disclaimer:
|
||||
"Totalscoren er normalisert slik at 75 % omtrent tilsvarer et forventet/godt rapportert funksjonsnivå, mens 100 % tilsvarer særdeles høyt funksjonsnivå. Scoren er en intern funksjonsindikator for oversikt og trend, ikke en medisinsk fasit.",
|
||||
periodLabel: "Periodens arbeidsevnevurdering",
|
||||
periodEmpty: "Ingen data i valgt periode",
|
||||
periodTooltip:
|
||||
"Dette er en normalisert vurdering basert på hovedrapportene i den valgte tidsrammen. 75 % regnes omtrent som forventet/godt rapportert funksjonsnivå. Vurderingen er støtte for oversikt og trend, ikke medisinsk fasit.",
|
||||
exceptionallyHigh: "Særdeles høyt funksjonsnivå",
|
||||
expected: "Forventet funksjonsnivå",
|
||||
moderatelyReduced: "Moderat redusert funksjon",
|
||||
significantlyReduced: "Betydelig redusert funksjon",
|
||||
veryLow: "Svært lav funksjon",
|
||||
exceptionallyHighShort: "Særdeles høyt",
|
||||
expectedShort: "Forventet",
|
||||
moderatelyReducedShort: "Moderat redusert",
|
||||
significantlyReducedShort: "Betydelig redusert",
|
||||
veryLowShort: "Svært lav"
|
||||
},
|
||||
verification: {
|
||||
valid: "Verifisert",
|
||||
modified: "Mulig endret",
|
||||
missing: "Ikke verifisert",
|
||||
unsupported: "Verifisering støttes ikke",
|
||||
error: "Feil ved verifisering",
|
||||
invalidLabel: "Varsel: rapporten kunne ikke verifiseres"
|
||||
}
|
||||
} as const;
|
||||
|
||||
const en: TranslationTree = {
|
||||
common: {
|
||||
cancel: "Cancel",
|
||||
close: "Close",
|
||||
copied: "Copied",
|
||||
delete: "Delete",
|
||||
save: "Save",
|
||||
notSelected: "Not selected",
|
||||
notProvided: "Not provided",
|
||||
notCalculated: "Not calculated",
|
||||
unknownWorkplace: "Unknown workplace"
|
||||
},
|
||||
app: { eyebrow: "Local self-assessment", title: "Work ability", evaluator: "Guide", navLabel: "Main navigation" },
|
||||
nav: { register: "Register", followup: "Follow-up", overview: "Overview", privacy: "Privacy" },
|
||||
footer: {
|
||||
norwegian: "Norsk",
|
||||
english: "English",
|
||||
switchToNorwegian: "Bytt språk til norsk",
|
||||
switchToEnglish: "Switch language to English",
|
||||
developedBy: "Developed by Rolfsvaag Datateknikk with",
|
||||
heart: "heart",
|
||||
languageChoice: "Language choice",
|
||||
sourceCode: "Source code"
|
||||
},
|
||||
deleteModal: {
|
||||
title: "Confirm deletion",
|
||||
body: "You are about to delete: {itemName}. This action cannot be undone.",
|
||||
waiting: "Delete ({secondsLeft})",
|
||||
ready: "Delete"
|
||||
},
|
||||
register: {
|
||||
intro: "Fill in the main fields. Optional details can be added if needed.",
|
||||
workSession: "Work session",
|
||||
workplace: "Workplace",
|
||||
workAbility: "Work ability right now",
|
||||
energyLevel: "Energy level",
|
||||
mentalClarity: "Mental clarity / concentration",
|
||||
symptomBurden: "Symptom burden",
|
||||
effortStrain: "Effort / strain",
|
||||
optional: "Optional",
|
||||
startTime: "Start time",
|
||||
endTime: "End time",
|
||||
continueQuestion: "Can you continue?",
|
||||
chooseBestNow: "Choose the option that fits best right now.",
|
||||
optionalDetails: "Optional details",
|
||||
physicalEnergy: "Physical energy",
|
||||
mentalEnergy: "Mental energy",
|
||||
productivity: "Perceived productivity",
|
||||
taskCompletion: "How much did you get done?",
|
||||
taskTypes: "Work involved",
|
||||
limitations: "What limited you most?",
|
||||
accommodations: "What helped?",
|
||||
note: "Short comment",
|
||||
missing: "Missing: {fields}.",
|
||||
saved: "Report saved locally.",
|
||||
saveReport: "Save report",
|
||||
oneEqualsFiveEquals: "1 = {low}, 5 = {high}."
|
||||
},
|
||||
followup: {
|
||||
intro: "Use this the next day or later to capture delayed reactions.",
|
||||
worseQuestion: "Are you worse than before the work session?",
|
||||
chooseOne: "Choose one answer.",
|
||||
connectReport: "Connect to report",
|
||||
delayedSymptoms: "Delayed symptoms",
|
||||
recovery: "Recovery",
|
||||
missingWorse: "Choose whether you are worse than before the work session.",
|
||||
saved: "Follow-up saved locally.",
|
||||
saveFollowup: "Save follow-up",
|
||||
orphan: "Follow-up without selected main report",
|
||||
worsePrefix: "worse"
|
||||
},
|
||||
overview: {
|
||||
noReports: "No reports have been saved yet.",
|
||||
report: "Report",
|
||||
followup: "Follow-up",
|
||||
closeReport: "Close report view",
|
||||
trend: "Trend",
|
||||
explainTrend: "Explain trend chart",
|
||||
trendTooltip: "The chart uses normalized 0-100 values. Higher value means better function or lower burden.",
|
||||
minimumTwo: "At least two reports in the selected period are needed for the chart.",
|
||||
average: "Normalized average for the period",
|
||||
chartLabel: "Time-based trend chart for reports",
|
||||
dashedLine: "A dashed line means there are 3 days or more between reports.",
|
||||
from: "From",
|
||||
to: "To",
|
||||
all: "All",
|
||||
week: "1 week",
|
||||
month: "1 month",
|
||||
threeMonths: "3 months",
|
||||
sixMonths: "6 months",
|
||||
year: "1 year",
|
||||
threeYears: "3 years",
|
||||
fiveYears: "5 years"
|
||||
},
|
||||
privacy: {
|
||||
warning: "Reports may contain sensitive health information and are stored only locally in this browser.",
|
||||
exportJson: "Export JSON",
|
||||
importBackup: "Import backup",
|
||||
deleteAll: "Delete all local data",
|
||||
importHelp: "Import previously exported JSON if you changed device or lost local data. Imported data is stored locally in this browser.",
|
||||
importFrom: "Import data from {fileName}",
|
||||
importStats: "New reports: {reports}. New follow-ups: {followups}. Duplicates skipped: {duplicates}. Conflicts: {conflicts}.",
|
||||
cancelImport: "Cancel import",
|
||||
importData: "Import data",
|
||||
baseCopy:
|
||||
"The app does not collect names, national identity numbers or contact information. Data is not sent to external services by the app. Free text is optional and should only be used when needed.",
|
||||
storageTitle: "Where is the data stored?",
|
||||
storageBody:
|
||||
"Reports are stored locally in the browser on the device you use, in localStorage. This means the data normally remains on your phone or computer.",
|
||||
backupTitle: "Backup and device changes",
|
||||
backupBody:
|
||||
"Because the data is stored locally, it may be lost if you change device, delete browser data, use another browser or reset the device. Export a JSON file regularly if you want to keep the reports.",
|
||||
sharingTitle: "Sharing with a guide",
|
||||
sharingBody:
|
||||
"If a guide or another person will review the reports, export a JSON file and share it with them, for example by email. The recipient can import the JSON file in the guide view.",
|
||||
sensitiveTitle: "Sensitive information",
|
||||
sensitiveBody:
|
||||
"Reports may contain health and function-related information. Share the JSON file in a way suitable for sensitive information, and only with people who should have access.",
|
||||
exportIntegrity: "Export includes local integrity checks where reports have verification data.",
|
||||
deleteAllName: "all local reports"
|
||||
},
|
||||
evaluator: {
|
||||
eyebrow: "Guide",
|
||||
title: "Guide review",
|
||||
backToApp: "Back to app",
|
||||
importJson: "Import JSON",
|
||||
importHelp: "Choose an export from the app. Data is stored locally in this browser.",
|
||||
displayName: "Name/identifier",
|
||||
optionalImportName: "Optional when importing",
|
||||
connectExisting: "Connect to existing person",
|
||||
autoMatch: "Create new or use automatic match",
|
||||
chooseJson: "Choose JSON file",
|
||||
exportBackup: "Export guide data",
|
||||
importBackup: "Import guide backup",
|
||||
importedBackup: "Guide backup has been imported.",
|
||||
importStart: "Import a JSON file to start reviewing.",
|
||||
saveName: "Save name",
|
||||
deletePerson: "Delete user and reports",
|
||||
deletePersonTitle: "Deletes this user, imported reports, follow-ups and guide notes after confirmation.",
|
||||
reportCount: "{count} reports",
|
||||
personNote: "General note for user",
|
||||
personNoteHelp: "This note applies to the person as a whole, not one specific report.",
|
||||
saveNote: "Save note",
|
||||
changedAt: "Last changed: {time}",
|
||||
searchReports: "Search reports",
|
||||
newest: "Newest first",
|
||||
oldest: "Oldest first",
|
||||
noSelectedTitle: "No report selected",
|
||||
noSelectedBody: "Choose a report from the list to see details, follow-ups and notes.",
|
||||
details: "Show details",
|
||||
reportDetails: "Report details",
|
||||
followupDetails: "Follow-up details",
|
||||
closeDetails: "Close details",
|
||||
reportNote: "Report note",
|
||||
followupNote: "Follow-up note",
|
||||
noteHelp: "The guide note is stored locally and does not change imported report data.",
|
||||
deleteNote: "Delete note",
|
||||
validationError: "Validation error",
|
||||
comment: "Comment",
|
||||
evaluatorNote: "Guide note",
|
||||
followupNoteBadge: "Follow-up note",
|
||||
hasFollowup: "Has follow-up",
|
||||
hasComment: "Has user comment",
|
||||
hasEvaluatorNote: "Has guide note",
|
||||
scoreValues: "Score values"
|
||||
},
|
||||
details: {
|
||||
summary: "Short summary",
|
||||
coreValues: "Core values",
|
||||
details: "Details",
|
||||
userComment: "User comment",
|
||||
followups: "Follow-ups",
|
||||
technical: "Technical information",
|
||||
rawData: "Raw data",
|
||||
integrity: "Integrity check",
|
||||
time: "Time",
|
||||
workTime: "Work time",
|
||||
status: "Status",
|
||||
totalScore: "Total score",
|
||||
functionLevel: "Function level",
|
||||
description: "Description",
|
||||
important: "Important",
|
||||
workAbility: "Work ability",
|
||||
energy: "Energy level",
|
||||
physicalEnergy: "Physical energy",
|
||||
mentalEnergy: "Mental energy",
|
||||
mentalClarity: "Mental clarity",
|
||||
symptoms: "Symptom burden",
|
||||
strain: "Effort/strain",
|
||||
productivity: "Productivity",
|
||||
taskCompletion: "How much did you get done?",
|
||||
taskTypes: "Work involved",
|
||||
limitations: "Limited by",
|
||||
accommodations: "What helped",
|
||||
comment: "Comment",
|
||||
noComment: "No comment",
|
||||
noOptionalDetails: "No optional details provided.",
|
||||
technicalDescription: "Advanced information for control, troubleshooting and verification.",
|
||||
rawDescription: "Full JSON view for troubleshooting or technical control.",
|
||||
copyRaw: "Copy raw data",
|
||||
reportId: "Report ID",
|
||||
followupId: "Follow-up ID",
|
||||
connectedReport: "Connected report",
|
||||
created: "Created",
|
||||
reportDate: "Report date",
|
||||
reportTime: "Report time",
|
||||
integrityStatus: "Integrity status",
|
||||
explanation: "Explanation",
|
||||
algorithm: "Algorithm",
|
||||
signed: "Signed",
|
||||
payloadHash: "Payload hash",
|
||||
worse: "Worse than before",
|
||||
delayedSymptoms: "Delayed symptoms",
|
||||
recovery: "Recovery",
|
||||
outOfFive: "{value} of 5",
|
||||
worseValue: "Worse: {value}",
|
||||
recoveryValue: "Recovery: {value}"
|
||||
},
|
||||
danger: {
|
||||
title: "Danger zone",
|
||||
deleteEntryHelp: "This deletes the report or follow-up from local storage in this browser.",
|
||||
deleteReport: "Delete report",
|
||||
deleteFollowup: "Delete follow-up",
|
||||
reportName: "report",
|
||||
followupName: "follow-up"
|
||||
},
|
||||
graphModal: {
|
||||
open: "Open chart fullscreen",
|
||||
title: "Chart analysis",
|
||||
close: "Close chart",
|
||||
timeRange: "Time range",
|
||||
visibleLines: "Visible lines",
|
||||
visibleHelp: "Tap once for partially visible, twice for hidden.",
|
||||
snapHelp: "Hover over the chart to see exact report values.",
|
||||
noReports: "No reports in selected period.",
|
||||
noPoint: "No report point selected",
|
||||
noPointHelp: "Hover near a point to see actual report values.",
|
||||
nearestReport: "Nearest report",
|
||||
chartLabel: "Fullscreen trend chart for reports"
|
||||
},
|
||||
scoring: {
|
||||
disclaimer:
|
||||
"The total score is normalized so that 75% roughly corresponds to an expected/good reported function level, while 100% corresponds to an exceptionally high function level. The score is an internal function indicator for overview and trend, not a medical conclusion.",
|
||||
periodLabel: "Period work ability assessment",
|
||||
periodEmpty: "No data in selected period",
|
||||
periodTooltip:
|
||||
"This is a normalized assessment based on the main reports in the selected time range. 75% is roughly considered an expected/good reported function level. The assessment supports overview and trend, not a medical conclusion.",
|
||||
exceptionallyHigh: "Exceptionally high function level",
|
||||
expected: "Expected function level",
|
||||
moderatelyReduced: "Moderately reduced function",
|
||||
significantlyReduced: "Significantly reduced function",
|
||||
veryLow: "Very low function",
|
||||
exceptionallyHighShort: "Exceptionally high",
|
||||
expectedShort: "Expected",
|
||||
moderatelyReducedShort: "Moderately reduced",
|
||||
significantlyReducedShort: "Significantly reduced",
|
||||
veryLowShort: "Very low"
|
||||
},
|
||||
verification: {
|
||||
valid: "Verified",
|
||||
modified: "Possibly changed",
|
||||
missing: "Not verified",
|
||||
unsupported: "Verification is not supported",
|
||||
error: "Verification error",
|
||||
invalidLabel: "Warning: the report could not be verified"
|
||||
}
|
||||
};
|
||||
|
||||
export const labelsByLanguage = {
|
||||
"nb-NO": {
|
||||
statusLabels: nbStatusLabels,
|
||||
taskTypeLabels: nbTaskTypeLabels,
|
||||
limitationLabels: nbLimitationLabels,
|
||||
accommodationLabels: nbAccommodationLabels,
|
||||
taskCompletionLabels: nbTaskCompletionLabels,
|
||||
worseLabels: nbWorseLabels,
|
||||
delayedSymptomLabels: nbDelayedSymptomLabels,
|
||||
recoveryLabels: nbRecoveryLabels,
|
||||
positiveScale: nbPositiveScale,
|
||||
negativeScale: nbNegativeScale,
|
||||
tooltipTexts: nbTooltipTexts,
|
||||
categoryTooltipTexts: nbCategoryTooltipTexts
|
||||
},
|
||||
en: {
|
||||
statusLabels: {
|
||||
kan_fortsette: "Can continue",
|
||||
trenger_pause: "Needs a break",
|
||||
trenger_enklere_oppgave: "Needs an easier task",
|
||||
bør_stoppe: "Wants to stop",
|
||||
onsker_a_stoppe: "Wants to stop",
|
||||
ønsker_å_stoppe: "Wants to stop"
|
||||
} satisfies Record<ReportStatus, string>,
|
||||
taskTypeLabels: {
|
||||
sitting: "Sitting",
|
||||
standing: "Standing",
|
||||
walking: "Walking",
|
||||
lifting: "Lifting",
|
||||
computer_work: "Computer work",
|
||||
concentration: "Concentration",
|
||||
multitasking: "Multitasking",
|
||||
talking_social: "Talking/social",
|
||||
noise: "Noise",
|
||||
bright_light: "Bright light",
|
||||
time_pressure: "Time pressure"
|
||||
} satisfies Record<TaskType, string>,
|
||||
limitationLabels: {
|
||||
fatigue: "Fatigue",
|
||||
brain_fog: "Brain fog",
|
||||
pain: "Pain",
|
||||
headache_migraine: "Headache/migraine",
|
||||
dizziness: "Dizziness",
|
||||
nausea: "Nausea",
|
||||
sensory_overload: "Sensory overload",
|
||||
stress_anxiety: "Stress/anxiety",
|
||||
weakness: "Weakness",
|
||||
sleepiness: "Sleepiness",
|
||||
other: "Other"
|
||||
} satisfies Record<Limitation, string>,
|
||||
accommodationLabels: {
|
||||
extra_breaks: "Extra breaks",
|
||||
sitting_down: "Sitting down",
|
||||
quiet_room: "Quiet room",
|
||||
reduced_pace: "Reduced pace",
|
||||
shorter_task: "Shorter task",
|
||||
help_from_others: "Help from others",
|
||||
remote_work: "Remote work",
|
||||
task_switching: "Task switching",
|
||||
nothing_helped: "Nothing helped"
|
||||
} satisfies Record<Accommodation, string>,
|
||||
taskCompletionLabels: {
|
||||
nesten_ingenting: "Almost nothing",
|
||||
litt: "A little",
|
||||
delvis: "Partly",
|
||||
som_forventet: "As expected",
|
||||
mer_enn_forventet: "More than expected"
|
||||
} satisfies Record<TaskCompletion, string>,
|
||||
worseLabels: {
|
||||
nei: "No",
|
||||
litt: "A little",
|
||||
moderat: "Moderate",
|
||||
mye: "A lot"
|
||||
} satisfies Record<WorseThanBefore, string>,
|
||||
delayedSymptomLabels: {
|
||||
fatigue: "Fatigue",
|
||||
pain: "Pain",
|
||||
brain_fog: "Brain fog",
|
||||
flu_like: "Flu-like feeling",
|
||||
headache: "Headache",
|
||||
dizziness: "Dizziness",
|
||||
sleep_disruption: "Sleep disruption",
|
||||
sensory_sensitivity: "Light/sound sensitivity"
|
||||
} satisfies Record<DelayedSymptom, string>,
|
||||
recoveryLabels: {
|
||||
tilbake_til_baseline: "Back to usual level",
|
||||
samme_dag: "Same day",
|
||||
en_dag: "One day",
|
||||
to_tre_dager: "Two-three days",
|
||||
fire_pluss_dager: "Four+ days",
|
||||
ikke_tilbake_til_baseline: "Not back yet"
|
||||
} satisfies Record<RecoveryStatus, string>,
|
||||
positiveScale: {
|
||||
1: "Very low",
|
||||
2: "Low",
|
||||
3: "Medium",
|
||||
4: "Good",
|
||||
5: "Very good"
|
||||
} satisfies Record<ScaleValue, string>,
|
||||
negativeScale: {
|
||||
1: "Little/none",
|
||||
2: "Mild",
|
||||
3: "Moderate",
|
||||
4: "High",
|
||||
5: "Very high"
|
||||
} satisfies Record<ScaleValue, string>,
|
||||
tooltipTexts: {
|
||||
work_ability: "How well you feel you can function in the work situation right now, regardless of whether you actually get everything done.",
|
||||
energy_level: "Overall perceived energy. If you do not separate physical and mental energy, this is used for both.",
|
||||
physical_energy: "Bodily energy, endurance and physical capacity.",
|
||||
mental_energy: "Mental endurance, ability to stay focused over time and tolerate cognitive load.",
|
||||
mental_clarity: "How clearly you think, and how easy it is to concentrate, remember, understand and follow along.",
|
||||
symptom_burden: "How much symptoms bother or limit you, such as pain, headache, dizziness, nausea or sensory overload.",
|
||||
effort_strain: "How much it costs to complete the activity. This is about effort/strain, not only symptom intensity.",
|
||||
symptom_vs_effort: "Symptom burden is about how many symptoms you have. Effort/strain is about how hard you must push yourself to complete the activity.",
|
||||
total_score_percent: "A calculated function indicator from 0 to 100 based on reported answers. This is not a medically validated test score.",
|
||||
workplace: "Workplace or context for the session. The field is prefilled from the previous report when available.",
|
||||
work_time: "Start and end time for the work session. The fields can be changed before saving the report."
|
||||
},
|
||||
categoryTooltipTexts: {
|
||||
A: "Work ability",
|
||||
E: "Energy",
|
||||
K: "Mental clarity/concentration",
|
||||
S: "Symptom burden",
|
||||
B: "Effort/strain",
|
||||
T: "Total score"
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
type I18nContextValue = {
|
||||
language: Language;
|
||||
setLanguage: (language: Language) => void;
|
||||
labels: (typeof labelsByLanguage)[Language];
|
||||
t: (key: string, params?: Record<string, string | number>) => string;
|
||||
locale: string;
|
||||
};
|
||||
|
||||
const I18nContext = createContext<I18nContextValue | null>(null);
|
||||
|
||||
export function I18nProvider({ children }: { children: React.ReactNode }) {
|
||||
const [language, setLanguageState] = useState<Language>(() => readStoredLanguage());
|
||||
const value = useMemo<I18nContextValue>(() => {
|
||||
const setLanguage = (next: Language) => {
|
||||
localStorage.setItem(LANGUAGE_KEY, next);
|
||||
setLanguageState(next);
|
||||
};
|
||||
return {
|
||||
language,
|
||||
setLanguage,
|
||||
labels: labelsByLanguage[language],
|
||||
t: (key, params) => interpolate(readTranslation(language, key), params),
|
||||
locale: language === "en" ? "en-GB" : "nb-NO"
|
||||
};
|
||||
}, [language]);
|
||||
|
||||
return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
|
||||
}
|
||||
|
||||
export function useI18n() {
|
||||
const context = useContext(I18nContext);
|
||||
if (!context) throw new Error("useI18n must be used inside I18nProvider");
|
||||
return context;
|
||||
}
|
||||
|
||||
export function translateVerificationStatus(status: VerificationStatus | undefined, t: I18nContextValue["t"]) {
|
||||
if (!status) return t("common.notProvided");
|
||||
return t(`verification.${status}`);
|
||||
}
|
||||
|
||||
export function translateScoreBand(
|
||||
band: { label: string; short_label: string; description: string } | null | undefined,
|
||||
t: I18nContextValue["t"],
|
||||
mode: "label" | "short" = "label"
|
||||
) {
|
||||
if (!band) return t("common.notCalculated");
|
||||
const key = scoreBandKeyFromNorwegianLabel(band.label);
|
||||
if (!key) return mode === "short" ? band.short_label : band.label;
|
||||
if (mode === "short") return t(`scoring.${key}Short`);
|
||||
return t(`scoring.${key}`);
|
||||
}
|
||||
|
||||
export function translateScoreBandDescription(band: { label: string; description: string } | null | undefined, language: Language) {
|
||||
if (!band || language === "nb-NO") return band?.description;
|
||||
const key = scoreBandKeyFromNorwegianLabel(band.label);
|
||||
const descriptions: Record<string, string> = {
|
||||
exceptionallyHigh: "The report suggests very good function in the current work situation, above what can normally be expected.",
|
||||
expected: "The report is within an expected/good function level for the current work situation.",
|
||||
moderatelyReduced: "The report suggests a noticeable reduction in work ability, energy, symptom burden or sustainability.",
|
||||
significantlyReduced: "The report suggests clearly limited function and/or high strain when completing the activity.",
|
||||
veryLow: "The report suggests very low current function, high strain or that the activity may not be sustainable."
|
||||
};
|
||||
return key ? descriptions[key] : band.description;
|
||||
}
|
||||
|
||||
function readStoredLanguage(): Language {
|
||||
const stored = localStorage.getItem(LANGUAGE_KEY);
|
||||
return stored === "en" || stored === "nb-NO" ? stored : "nb-NO";
|
||||
}
|
||||
|
||||
function readTranslation(language: Language, key: string) {
|
||||
const translated = lookup(language === "en" ? en : nb, key);
|
||||
const fallback = lookup(nb, key);
|
||||
return typeof translated === "string" ? translated : typeof fallback === "string" ? fallback : key;
|
||||
}
|
||||
|
||||
function lookup(tree: TranslationTree, key: string): string | TranslationTree | undefined {
|
||||
return key.split(".").reduce<string | TranslationTree | undefined>((current, part) => {
|
||||
if (!current || typeof current === "string") return undefined;
|
||||
return current[part];
|
||||
}, tree);
|
||||
}
|
||||
|
||||
function interpolate(template: string, params?: Record<string, string | number>) {
|
||||
if (!params) return template;
|
||||
return template.replace(/\{(\w+)\}/g, (_, key: string) => String(params[key] ?? `{${key}}`));
|
||||
}
|
||||
|
||||
function scoreBandKeyFromNorwegianLabel(label: string) {
|
||||
if (label === "Særdeles høyt funksjonsnivå") return "exceptionallyHigh";
|
||||
if (label === "Forventet funksjonsnivå") return "expected";
|
||||
if (label === "Moderat redusert funksjon") return "moderatelyReduced";
|
||||
if (label === "Betydelig redusert funksjon") return "significantlyReduced";
|
||||
if (label === "Svært lav funksjon") return "veryLow";
|
||||
return null;
|
||||
}
|
||||
@ -1520,10 +1520,8 @@ textarea {
|
||||
}
|
||||
|
||||
.app-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
color: #65706e;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
@ -1534,6 +1532,66 @@ textarea {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-credit,
|
||||
.footer-language-switcher,
|
||||
.footer-links {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.footer-language-switcher {
|
||||
gap: 0.45rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.footer-language-button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
padding: 0.2rem 0.35rem;
|
||||
}
|
||||
|
||||
.footer-language-button[aria-current="true"] {
|
||||
color: #35413f;
|
||||
font-weight: 900;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.footer-language-separator {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.footer-link {
|
||||
align-items: center;
|
||||
background: rgba(255, 254, 250, 0.74);
|
||||
border: 1px solid #ccd7d4;
|
||||
border-radius: 999px;
|
||||
color: #235b5e;
|
||||
display: inline-flex;
|
||||
gap: 0.4rem;
|
||||
justify-content: center;
|
||||
padding: 0.44rem 0.7rem;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
background 0.16s ease,
|
||||
border-color 0.16s ease,
|
||||
color 0.16s ease,
|
||||
transform 0.16s ease;
|
||||
}
|
||||
|
||||
.footer-link:hover,
|
||||
.footer-link:focus-visible {
|
||||
background: #fffefa;
|
||||
border-color: #9db5af;
|
||||
color: #1f4f51;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.app-footer svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user