Remove unused build-messages.mjs i18n generator script.
The app loads frontend/messages/*.json directly via next-intl; the script was never wired into npm scripts or CI and had drifted out of sync with the JSON files. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,636 +0,0 @@
|
|||||||
import fs from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
||||||
const messagesDir = path.join(__dirname, '..', 'messages');
|
|
||||||
|
|
||||||
const en = {
|
|
||||||
common: {
|
|
||||||
appName: 'DyoLink',
|
|
||||||
loading: 'Loading...',
|
|
||||||
loadingApp: 'Loading app...',
|
|
||||||
loadingWorkspace: 'Loading workspace...',
|
|
||||||
continue: 'Continue',
|
|
||||||
back: 'Back',
|
|
||||||
save: 'Save',
|
|
||||||
cancel: 'Cancel',
|
|
||||||
delete: 'Delete',
|
|
||||||
edit: 'Edit',
|
|
||||||
next: 'Next',
|
|
||||||
dismiss: 'Dismiss',
|
|
||||||
or: 'Or',
|
|
||||||
and: 'and',
|
|
||||||
close: 'Close',
|
|
||||||
redirecting: 'Redirecting…',
|
|
||||||
readOnlyAccess: 'Read-only access for this organization.',
|
|
||||||
errorGeneric: 'Something went wrong',
|
|
||||||
loadingEllipsis: 'Loading...',
|
|
||||||
search: 'Search',
|
|
||||||
action: 'Action',
|
|
||||||
status: 'Status',
|
|
||||||
name: 'Name',
|
|
||||||
email: 'Email',
|
|
||||||
date: 'Date',
|
|
||||||
organization: 'Organization',
|
|
||||||
backToApp: '← Back to app',
|
|
||||||
copied: 'Copied',
|
|
||||||
copyLink: 'Copy link',
|
|
||||||
none: 'None',
|
|
||||||
preview: 'Preview',
|
|
||||||
},
|
|
||||||
language: {
|
|
||||||
label: 'Language',
|
|
||||||
selectLanguage: 'Select language',
|
|
||||||
en: 'English',
|
|
||||||
fa: 'Persian',
|
|
||||||
nl: 'Dutch',
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
switchToLight: 'Switch to light mode',
|
|
||||||
switchToDark: 'Switch to dark mode',
|
|
||||||
lightMode: 'Light mode',
|
|
||||||
darkMode: 'Dark mode',
|
|
||||||
},
|
|
||||||
nav: {
|
|
||||||
dashboard: 'Dashboard',
|
|
||||||
staff: 'Staff',
|
|
||||||
patients: 'Patients',
|
|
||||||
appointment: 'Appointment',
|
|
||||||
treatment: 'Treatment',
|
|
||||||
billing: 'Billing',
|
|
||||||
reports: 'Reports',
|
|
||||||
clinics: 'Clinics',
|
|
||||||
labs: 'Labs',
|
|
||||||
},
|
|
||||||
auth: {
|
|
||||||
login: 'Login',
|
|
||||||
signIn: 'Sign in',
|
|
||||||
signOut: 'Log out',
|
|
||||||
register: 'Register',
|
|
||||||
startTrial: 'Start Trial',
|
|
||||||
startFreeTrial: 'Start Free Trial',
|
|
||||||
dashboard: 'Dashboard',
|
|
||||||
signInTitle: 'Sign in to your account',
|
|
||||||
signInPrompt: 'Or {link}',
|
|
||||||
startTrialLink: 'start your free trial',
|
|
||||||
registerTitle: 'Start your 30-day free trial',
|
|
||||||
registerPrompt: 'Already have an account?',
|
|
||||||
signInLink: 'Sign in',
|
|
||||||
email: 'Email address',
|
|
||||||
password: 'Password',
|
|
||||||
confirmPassword: 'Confirm password',
|
|
||||||
fullName: 'Full name',
|
|
||||||
rememberMe: 'Remember me',
|
|
||||||
showPassword: 'Show password',
|
|
||||||
hidePassword: 'Hide password',
|
|
||||||
forgotPassword: 'Forgot your password?',
|
|
||||||
invalidCredentials: 'Invalid email or password',
|
|
||||||
loginFailed: 'Login failed',
|
|
||||||
registrationFailed: 'Registration failed. Please try again.',
|
|
||||||
startMyFreeTrial: 'Start my free trial',
|
|
||||||
trialIncludes: 'Your trial includes:',
|
|
||||||
trialTeamMembers: 'Up to 5 team members',
|
|
||||||
trialFullAccess: 'Full access to all features',
|
|
||||||
trialNoCard: '30 days free, no credit card required',
|
|
||||||
termsAgreement: 'By signing up, you agree to our {terms} and {privacy}',
|
|
||||||
termsOfService: 'Terms of Service',
|
|
||||||
privacyPolicy: 'Privacy Policy',
|
|
||||||
signedIn: 'Signed in',
|
|
||||||
switchOrganization: 'Switch organization',
|
|
||||||
subscriptions: 'Subscriptions',
|
|
||||||
account: 'Account',
|
|
||||||
emailPlaceholder: 'you@example.com',
|
|
||||||
passwordPlaceholder: '••••••••',
|
|
||||||
namePlaceholder: 'John Doe',
|
|
||||||
termsIntro: 'By signing up, you agree to our',
|
|
||||||
errorRegistrationFailed: 'Registration failed',
|
|
||||||
errorLoginFailed: 'Login failed',
|
|
||||||
errorCreateOrganization: 'Failed to create organization',
|
|
||||||
acceptInviteTitle: 'Accept invitation',
|
|
||||||
loadingInvitation: 'Loading invitation...',
|
|
||||||
invalidInvitationLink: 'Invalid invitation link',
|
|
||||||
invitationAlreadyAccepted: 'This invitation is already accepted. You can log in now.',
|
|
||||||
errorLoadInvitation: 'Could not load invitation',
|
|
||||||
organizationLabel: 'Organization:',
|
|
||||||
emailLabel: 'Email:',
|
|
||||||
nameRequired: 'Name is required',
|
|
||||||
passwordMinLength8: 'Password must be at least 8 characters',
|
|
||||||
passwordsDoNotMatch: 'Passwords do not match',
|
|
||||||
labelName: 'Name',
|
|
||||||
labelCreatePassword: 'Create password',
|
|
||||||
labelConfirmPassword: 'Confirm password',
|
|
||||||
activateAccount: 'Activate account',
|
|
||||||
invitationAcceptedRedirect: 'Invitation Accepted. Redirecting to login...',
|
|
||||||
errorAcceptInvitation: 'Could not accept invitation',
|
|
||||||
alreadyHaveAccess: 'Already have access?',
|
|
||||||
goToLogin: 'Go to login',
|
|
||||||
acceptOrganizationTitle: 'Accept organization invitation',
|
|
||||||
alreadyHaveAccount: 'Already have an account?',
|
|
||||||
invitedBy: 'Invited by:',
|
|
||||||
ownerEmail: 'Owner email',
|
|
||||||
activateOrganization: 'Activate organization',
|
|
||||||
organizationAcceptedRedirect: 'Invitation accepted. Redirecting to login...',
|
|
||||||
stepAccount: 'Account',
|
|
||||||
stepOrganization: 'Organization',
|
|
||||||
organizationName: 'Organization name',
|
|
||||||
organizationNamePlaceholder: 'Sunshine Dental Clinic',
|
|
||||||
organizationEmail: 'Organization email',
|
|
||||||
organizationEmailPlaceholder: 'contact@sunshineclinic.com',
|
|
||||||
organizationType: 'Organization type',
|
|
||||||
dentalClinic: 'Dental Clinic',
|
|
||||||
dentalLab: 'Dental Lab',
|
|
||||||
},
|
|
||||||
landing: {
|
|
||||||
heroTitle: 'Connect Dental Clinics & Labs',
|
|
||||||
heroHighlight: 'Seamlessly',
|
|
||||||
heroSubtitle:
|
|
||||||
'Streamline communication between dental professionals. Start with a 30-day free trial, no credit card required.',
|
|
||||||
featureClinicsTitle: 'For Clinics',
|
|
||||||
featureClinicsDescription:
|
|
||||||
'Manage patients, appointments, and send cases to labs instantly.',
|
|
||||||
featureLabsTitle: 'For Labs',
|
|
||||||
featureLabsDescription: 'Receive cases, track progress, and communicate with clinics.',
|
|
||||||
featureTeamTitle: 'Team Management',
|
|
||||||
featureTeamDescription: 'Add up to 5 team members during trial. Scale as you grow.',
|
|
||||||
featureTrialTitle: '30-Day Trial',
|
|
||||||
featureTrialDescription: 'Full access to all features. No credit card required.',
|
|
||||||
featureRealtimeTitle: 'Real-time Updates',
|
|
||||||
featureRealtimeDescription: 'Get instant notifications on case status changes.',
|
|
||||||
featureSecurityTitle: 'Secure & Compliant',
|
|
||||||
featureSecurityDescription: 'HIPAA-compliant with enterprise-grade security.',
|
|
||||||
footerCopyright: '© 2026 DyoLink. All rights reserved.',
|
|
||||||
termsAndConditions: 'Terms & Conditions',
|
|
||||||
},
|
|
||||||
accountMenu: {
|
|
||||||
noActiveSubscription: 'No active subscription — review Subscriptions',
|
|
||||||
trialEnded: 'Trial ended — review Subscriptions',
|
|
||||||
trialEndingSoon: 'Trial ending soon — review Subscriptions',
|
|
||||||
seatsLow: 'Seats running low — review Subscriptions',
|
|
||||||
reviewSubscriptions: 'Review Subscriptions',
|
|
||||||
},
|
|
||||||
validation: {
|
|
||||||
emailInvalid: 'Please enter a valid email address',
|
|
||||||
passwordRequired: 'Password is required',
|
|
||||||
nameMinLength: 'Name must be at least 2 characters',
|
|
||||||
passwordMinLength: 'Password must be at least 8 characters',
|
|
||||||
passwordUppercase: 'Password must contain at least one uppercase letter',
|
|
||||||
passwordNumber: 'Password must contain at least one number',
|
|
||||||
organizationNameMinLength: 'Organization name must be at least 2 characters',
|
|
||||||
organizationEmailInvalid: 'Please enter a valid organization email',
|
|
||||||
organizationTypeRequired: 'Please select organization type',
|
|
||||||
passwordsDoNotMatch: "Passwords don't match",
|
|
||||||
},
|
|
||||||
today: {
|
|
||||||
welcomeBack: 'Welcome back!!',
|
|
||||||
noSubscriptionNotice: 'This organization does not have an active subscription yet.',
|
|
||||||
choosePlanLink: 'Choose a plan',
|
|
||||||
noSubscriptionCta: 'to start the purchase process.',
|
|
||||||
noWidgets: 'No dashboard metrics are available for your current permissions.',
|
|
||||||
loadError: 'Could not load dashboard metrics.',
|
|
||||||
seatsUnlimited: 'Unlimited plan',
|
|
||||||
widgetAppointmentsToday: "Today's Appointments",
|
|
||||||
widgetPatientsToday: 'Patients Today',
|
|
||||||
widgetTreatmentsToday: 'Treatments Today',
|
|
||||||
widgetDraftTreatments: 'Draft Treatments',
|
|
||||||
widgetLabCasesPendingSend: 'Lab Cases Pending Send',
|
|
||||||
widgetCasesReceivedToday: 'Cases Received Today',
|
|
||||||
widgetTasksInProgress: 'Tasks In Progress',
|
|
||||||
widgetImportantTasks: 'Important Tasks',
|
|
||||||
widgetPendingConnections: 'Pending Connections',
|
|
||||||
widgetSeats: 'Seat Usage',
|
|
||||||
widgetPendingStaffInvites: 'Pending Staff Invites',
|
|
||||||
chartTreatmentMixTitle: 'Treatment Mix',
|
|
||||||
chartTreatmentMixSubtitle: 'Last 7 days',
|
|
||||||
chartTasksByStepTitle: 'Tasks by Workflow Step',
|
|
||||||
chartTasksByStepSubtitle: 'In progress now',
|
|
||||||
chartEmpty: 'No data for this period yet.',
|
|
||||||
},
|
|
||||||
staff: {
|
|
||||||
redirecting: 'Redirecting…',
|
|
||||||
title: 'Staff Management',
|
|
||||||
subtitle: 'Invite teammates, set tab access, and stay within your plan seat limit.',
|
|
||||||
inviteMember: 'Invite member',
|
|
||||||
seatsLabel: 'Seats:',
|
|
||||||
unlimitedPlan: '(unlimited plan)',
|
|
||||||
seatLimitReached: 'Plan seat limit reached for this organization.',
|
|
||||||
noActivePlan:
|
|
||||||
'No active plan selected for this organization. Choose a subscription plan to invite members.',
|
|
||||||
invitedPending:
|
|
||||||
'Invitation is pending until they open the link, set a password, and log in.',
|
|
||||||
invitedAccepted: 'Invitation was accepted immediately.',
|
|
||||||
inviteLinkHeading: 'Invite link',
|
|
||||||
shareLinkHint:
|
|
||||||
'Share this link manually via SMS or email. A new link is generated if the previous one expired or was lost.',
|
|
||||||
loadingTeam: 'Loading team…',
|
|
||||||
tableName: 'Name',
|
|
||||||
tableEmail: 'Email',
|
|
||||||
tableRole: 'Role',
|
|
||||||
tableStatus: 'Status',
|
|
||||||
tableAccess: 'Access',
|
|
||||||
tableAction: 'Action',
|
|
||||||
roleOwner: 'Owner',
|
|
||||||
roleStaff: 'Staff',
|
|
||||||
statusActive: 'Active',
|
|
||||||
statusPending: 'Pending',
|
|
||||||
statusDisabled: 'Disabled',
|
|
||||||
statusExpired: 'Expired',
|
|
||||||
allFeatures: 'All features',
|
|
||||||
inviteModalTitle: 'Invite team member',
|
|
||||||
stepOf: 'Step {step} of 2',
|
|
||||||
permissionView: 'View',
|
|
||||||
permissionEdit: 'Edit',
|
|
||||||
labelEmail: 'Email',
|
|
||||||
labelDisplayName: 'Display name',
|
|
||||||
tabAccess: 'Tab access',
|
|
||||||
sendInvite: 'Send invite',
|
|
||||||
skipForNow: 'Skip for now',
|
|
||||||
enableModalTitle: 'Enable team member',
|
|
||||||
enableConfirm: 'Enable {name} ({email})?',
|
|
||||||
enableBullet1: 'They can sign in to this organization again with their existing account.',
|
|
||||||
enableBullet2: 'No new invitation is sent and no data was removed while they were disabled.',
|
|
||||||
enableBullet3: 'Enabling uses one seat on your plan.',
|
|
||||||
noSeatsAvailable:
|
|
||||||
'No seats are available. Disable another member or upgrade your plan before enabling this person.',
|
|
||||||
enableMemberButton: 'Enable member',
|
|
||||||
disableModalTitle: 'Disable team member',
|
|
||||||
disableConfirm: 'Disable {name} ({email})?',
|
|
||||||
disableBullet1: 'They will not be able to sign in to this organization.',
|
|
||||||
disableBullet2: 'No data will be removed.',
|
|
||||||
disableBullet3: 'Disabling frees one seat on your plan so you can invite someone else.',
|
|
||||||
disableMemberButton: 'Disable member',
|
|
||||||
editModalTitle: 'Edit member',
|
|
||||||
loadingWorkingHours: 'Loading working hours…',
|
|
||||||
errorLoadStaff: 'Failed to load staff.',
|
|
||||||
errorCopyInvite: 'Could not copy invitation link.',
|
|
||||||
errorSendInvite: 'Failed to send invitation.',
|
|
||||||
errorLoadWorkingHours: 'Failed to load working hours.',
|
|
||||||
successMemberUpdated: 'Member updated.',
|
|
||||||
errorUpdateMember: 'Failed to update member.',
|
|
||||||
errorDeleteNotImplemented: 'Delete is not implemented yet.',
|
|
||||||
successMemberDisabled: '{name} was disabled. A seat is now available.',
|
|
||||||
errorDisableMember: 'Failed to disable member.',
|
|
||||||
successMemberEnabled: '{name} was enabled and can sign in again.',
|
|
||||||
errorEnableMember: 'Failed to enable member.',
|
|
||||||
successInvited: '{name} ({email}) was invited.',
|
|
||||||
copyInviteLink: 'Copy invitation link',
|
|
||||||
copyInviteLinkTitle: 'Copy invitation link (generates a new link if needed)',
|
|
||||||
enableMemberAria: 'Enable member',
|
|
||||||
enableMemberTitle: 'Enable member (uses a seat)',
|
|
||||||
disableMemberAria: 'Disable member',
|
|
||||||
disableMemberTitle: 'Disable member (frees a seat)',
|
|
||||||
editMemberAria: 'Edit member',
|
|
||||||
deleteMemberAria: 'Delete member',
|
|
||||||
deleteMemberTitle: 'Delete member (not implemented)',
|
|
||||||
features: {
|
|
||||||
featureToday: 'Today',
|
|
||||||
featureStaff: 'Staff',
|
|
||||||
featureOrganizations: 'Organizations',
|
|
||||||
featureClinics: 'Clinics',
|
|
||||||
featureLabs: 'Labs',
|
|
||||||
featurePatients: 'Patients',
|
|
||||||
featureAppointment: 'Appointment',
|
|
||||||
featureTreatment: 'Treatment',
|
|
||||||
featureBilling: 'Billing',
|
|
||||||
featureReports: 'Reports',
|
|
||||||
noTabAccess: 'No tab access',
|
|
||||||
readOnlySuffix: '(Read only)',
|
|
||||||
},
|
|
||||||
workingHours: {
|
|
||||||
recommendedTitle: 'Working hours recommended',
|
|
||||||
recommendedBody:
|
|
||||||
'Staff with treatment edit access appear as provider columns in Appointments. Set their weekly hours so the schedule grid shows the right bookable times.',
|
|
||||||
intro:
|
|
||||||
'Set weekly working hours for this provider. The appointments grid uses these hours to show bookable time slots.',
|
|
||||||
workingDay: 'Working day',
|
|
||||||
start: 'Start',
|
|
||||||
end: 'End',
|
|
||||||
removeShift: 'Remove shift',
|
|
||||||
addShift: 'Add shift',
|
|
||||||
autoRepeatWeekly: 'Repeat these hours at the start of each week (copy forward on Monday)',
|
|
||||||
weekdayMon: 'Mon',
|
|
||||||
weekdayTue: 'Tue',
|
|
||||||
weekdayWed: 'Wed',
|
|
||||||
weekdayThu: 'Thu',
|
|
||||||
weekdayFri: 'Fri',
|
|
||||||
weekdaySat: 'Sat',
|
|
||||||
weekdaySun: 'Sun',
|
|
||||||
validationNeedsShift: '{day} needs at least one shift or should be marked off.',
|
|
||||||
validationEndAfterStart: '{day} shift end time must be after start time.',
|
|
||||||
validationOverlap: '{day} shifts cannot overlap.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
patients: {
|
|
||||||
title: 'Patients',
|
|
||||||
newPatient: 'New Patient',
|
|
||||||
errorLoadPatients: 'Failed to load patients.',
|
|
||||||
successPatientSaved: 'Patient {firstName} {lastName} was saved successfully.',
|
|
||||||
errorSavePatient: 'Failed to save patient.',
|
|
||||||
firstName: 'First name',
|
|
||||||
lastName: 'Last name',
|
|
||||||
phone: 'Phone',
|
|
||||||
savePatient: 'Save Patient',
|
|
||||||
dialogTitle: 'New patient',
|
|
||||||
searchPlaceholder: 'Search patients by name, phone, email',
|
|
||||||
loadingPatients: 'Loading patients...',
|
|
||||||
noResults: 'No patients found for this search.',
|
|
||||||
noContact: 'No contact',
|
|
||||||
selectPatient: 'Select a patient to view details.',
|
|
||||||
phoneLabel: 'Phone:',
|
|
||||||
emailLabel: 'Email:',
|
|
||||||
statusLabel: 'Status:',
|
|
||||||
statusActive: 'Active',
|
|
||||||
statusInactive: 'Inactive',
|
|
||||||
emptyValue: '-',
|
|
||||||
},
|
|
||||||
appointments: {
|
|
||||||
title: 'Appointments',
|
|
||||||
subtitle: 'Search a patient, pick a date, then click a time slot under a provider to book.',
|
|
||||||
loadingSchedule: 'Loading schedule…',
|
|
||||||
infoPastViewOnly: 'Past appointments are view-only.',
|
|
||||||
infoSelectPatient: 'Select a patient before booking.',
|
|
||||||
errorOutsideHours:
|
|
||||||
"This appointment falls outside the provider's current working hours and cannot be edited.",
|
|
||||||
successUpdated: 'Appointment updated.',
|
|
||||||
successSaved: 'Appointment saved.',
|
|
||||||
errorUpdate: 'Could not update appointment.',
|
|
||||||
errorSave: 'Could not save appointment.',
|
|
||||||
confirmRemove: 'Remove this appointment?',
|
|
||||||
successRemoved: 'Appointment removed.',
|
|
||||||
errorDelete: 'Could not delete appointment.',
|
|
||||||
errorLoadSchedule: 'Failed to load schedule.',
|
|
||||||
successPatientSaved: 'Patient {firstName} {lastName} was saved.',
|
|
||||||
searchPlaceholder: 'Search existing patients',
|
|
||||||
searching: 'Searching…',
|
|
||||||
searchHint: 'Type to search patients by name, phone, or email.',
|
|
||||||
noPermissionAdd: 'You do not have permission to add patients.',
|
|
||||||
editTitle: 'Edit appointment',
|
|
||||||
newTitle: 'New appointment',
|
|
||||||
providerLabel: 'Provider:',
|
|
||||||
patientLabel: 'Patient',
|
|
||||||
startLabel: 'Start',
|
|
||||||
endLabel: 'End',
|
|
||||||
purposeLabel: 'Purpose',
|
|
||||||
errorSelectPatient: 'Select a patient first.',
|
|
||||||
errorEndAfterStart: 'End time must be after start time.',
|
|
||||||
errorPastSchedule: 'Cannot schedule in the past.',
|
|
||||||
errorPastViewOnly: 'Past appointments are view-only.',
|
|
||||||
errorMissingDetails: 'Missing appointment details.',
|
|
||||||
noProviders:
|
|
||||||
'No providers available. Add staff with treatment edit access to see columns here.',
|
|
||||||
noWorkingHours:
|
|
||||||
'No working hours are configured for this day. Set provider working hours in Staff management.',
|
|
||||||
noHoursSet: 'No hours set',
|
|
||||||
offToday: 'Off today',
|
|
||||||
slotOffToday: 'Provider is off today',
|
|
||||||
slotHoursNotConfigured: 'Working hours not configured',
|
|
||||||
slotOutsideHours: 'Outside working hours',
|
|
||||||
slotCannotCreate: 'You cannot create appointments',
|
|
||||||
slotBookAt: 'Book {time}',
|
|
||||||
outsideHoursBlocked: 'Outside working hours — editing blocked',
|
|
||||||
overlappingChoose: '{count} overlapping — click to choose',
|
|
||||||
overlapping: '{count} overlapping',
|
|
||||||
legend: 'Legend',
|
|
||||||
overlappingTitle: 'Overlapping appointments ({count})',
|
|
||||||
purposeConsultation: 'Consultation',
|
|
||||||
purposeFilling: 'Filling',
|
|
||||||
purposeEndo: 'Endo',
|
|
||||||
purposeVisit: 'Visit',
|
|
||||||
purposeHygiene: 'Hygiene',
|
|
||||||
},
|
|
||||||
treatment: {
|
|
||||||
loading: 'Loading…',
|
|
||||||
noPermissionTitle: 'Treatment workspace',
|
|
||||||
noPermissionBody: 'You do not have permission to view the Treatment tab for this organization.',
|
|
||||||
title: 'Treatment',
|
|
||||||
subtitleEdit:
|
|
||||||
'Document cases for your appointments, save drafts, and send work to linked organizations.',
|
|
||||||
subtitleReadOnly:
|
|
||||||
'View-only access — you can review appointments and treatment history but cannot edit.',
|
|
||||||
pastDayNotice:
|
|
||||||
'Past days are view-only. You can review appointments and history, but treatment cases cannot be added or changed.',
|
|
||||||
selectedPatient: 'Selected patient',
|
|
||||||
purposeLabel: 'Purpose:',
|
|
||||||
loadingAppointments: 'Loading appointments…',
|
|
||||||
selectDayWithAppointment: 'Select a day with at least one appointment.',
|
|
||||||
confirmDiscard: 'You have unsaved changes. Discard them and continue?',
|
|
||||||
successDraftSaved: 'Treatment draft saved.',
|
|
||||||
errorChooseOrg: 'Choose at least one active organization to send this case.',
|
|
||||||
successCaseSent: 'Case sent to selected organizations.',
|
|
||||||
successFilesUploaded: '{count} file(s) uploaded successfully.',
|
|
||||||
errorLoadAppointments: 'Failed to load appointments.',
|
|
||||||
errorLoadOrgs: 'Failed to load linked organizations.',
|
|
||||||
errorLoadHistory: 'Failed to load treatment history.',
|
|
||||||
errorLoadDraft: 'Failed to load treatment draft.',
|
|
||||||
errorUpload: 'Failed to upload attachments.',
|
|
||||||
errorSaveDraft: 'Failed to save treatment draft.',
|
|
||||||
errorSendCase: 'Failed to send case.',
|
|
||||||
errorCaseMustSave: 'Case must be saved before sending.',
|
|
||||||
draftTitle: 'Draft · {patientName}',
|
|
||||||
hiddenMessage: 'Appointments are hidden.',
|
|
||||||
showAppointments: 'Show appointments',
|
|
||||||
appointmentsTitle: 'My appointments',
|
|
||||||
hideAppointments: 'Hide appointments',
|
|
||||||
emptyDay: 'No appointments assigned to you on this day.',
|
|
||||||
casesTitle: 'Treatment cases',
|
|
||||||
casesSubtitle: 'Each case has its own teeth, notes, attachments, and destinations for send.',
|
|
||||||
addCase: 'Add case',
|
|
||||||
caseLabel: 'Case {n}',
|
|
||||||
comments: 'Comments',
|
|
||||||
commentsPlaceholder: 'Write clinical notes for this case…',
|
|
||||||
treatmentType: 'Treatment type',
|
|
||||||
typeConsultation: 'consultation',
|
|
||||||
typeFilling: 'filling',
|
|
||||||
typeEndo: 'endo',
|
|
||||||
typeVisit: 'visit',
|
|
||||||
typeHygiene: 'hygiene',
|
|
||||||
attachments: 'Attachments',
|
|
||||||
attachFiles: 'Attach files for this treatment case',
|
|
||||||
chooseFiles: 'Choose files',
|
|
||||||
sendToOrgs: 'Send this case to linked organizations',
|
|
||||||
searchOrgsPlaceholder: 'Search active organizations...',
|
|
||||||
recent: 'Recent:',
|
|
||||||
noOrgMatch: 'No active organization matches your search.',
|
|
||||||
sendThisCase: 'Send this case',
|
|
||||||
saveDraft: 'Save treatment draft',
|
|
||||||
unsavedChanges: 'Unsaved changes',
|
|
||||||
draftSaved: 'Draft saved',
|
|
||||||
sendSavesFirst: 'Sending is per case and saves first automatically.',
|
|
||||||
historyTitle: 'Previous treatments',
|
|
||||||
historySubtitle: 'Completed treatments for this patient. Each case is listed separately.',
|
|
||||||
loadingHistory: 'Loading history…',
|
|
||||||
historyEmpty: 'No prior treatments for this patient.',
|
|
||||||
statusLabel: 'Status:',
|
|
||||||
historyCaseLabel: 'Case {n} · {type}',
|
|
||||||
teethLabel: 'Teeth:',
|
|
||||||
teethNone: 'None selected',
|
|
||||||
reviewDetails: 'Review details',
|
|
||||||
previewTitle: 'Treatment preview',
|
|
||||||
previewDraft: 'Preview current draft',
|
|
||||||
selectAppointment: 'Select an appointment to preview its draft.',
|
|
||||||
caseCount: '{n} case(s)',
|
|
||||||
attachmentCount: '{n} attachment(s)',
|
|
||||||
caseSummary: 'Case {n}: {type}',
|
|
||||||
teethPrefix: '· Teeth',
|
|
||||||
moreCases: '+ {n} more case(s)',
|
|
||||||
previewDialogTitle: 'Treatment preview',
|
|
||||||
previewDialogSubtitle: 'Review cases, attachments, and send destinations.',
|
|
||||||
noCases: 'No cases in this treatment.',
|
|
||||||
typeLabel: 'Type:',
|
|
||||||
commentsLabel: 'Comments:',
|
|
||||||
commentsEmpty: 'Comments: —',
|
|
||||||
attachFilesShort: 'Attach files',
|
|
||||||
sendCase: 'Send this case',
|
|
||||||
sendToLinkedOrgs: 'Send to linked organizations',
|
|
||||||
noActiveOrgs: 'No active linked organizations.',
|
|
||||||
confirmSend: 'Confirm send',
|
|
||||||
toothChartTitle: 'FDI tooth chart',
|
|
||||||
toothChartHint: 'Tap teeth to multi-select. Applies to the active case.',
|
|
||||||
selectedLabel: 'Selected:',
|
|
||||||
selectedEmpty: '—',
|
|
||||||
upperArch: 'Upper arch',
|
|
||||||
lowerArch: 'Lower arch',
|
|
||||||
toothAria: 'FDI tooth {fdi}',
|
|
||||||
toothSelectedSuffix: ', selected',
|
|
||||||
sentToAt: 'Sent to {orgName} at {datetime}',
|
|
||||||
fallbackOrgName: 'organization',
|
|
||||||
},
|
|
||||||
organizations: {
|
|
||||||
loadingOrganization: 'Loading organization...',
|
|
||||||
subtitle:
|
|
||||||
'Search organizations, send connection requests to existing accounts, or invitation links when they are not on DyoLink yet.',
|
|
||||||
invitationHistory: 'Invitation History',
|
|
||||||
searchPlaceholder: 'Search {counterpart} by name, email, or phone...',
|
|
||||||
backToList: 'Back to list',
|
|
||||||
tableOrganization: 'Organization',
|
|
||||||
tableOwnerEmail: 'Owner email',
|
|
||||||
tableDate: 'Date',
|
|
||||||
tableStatus: 'Status',
|
|
||||||
tableAction: 'Action',
|
|
||||||
emptyConnections: 'No connections yet. Search to send a connection request or an invitation link.',
|
|
||||||
statusInvitationPending: 'Invitation pending',
|
|
||||||
statusConnectionPending: 'Connection request pending',
|
|
||||||
statusConnected: 'Connected',
|
|
||||||
statusDeclined: 'Connection request declined',
|
|
||||||
statusFound: 'Found',
|
|
||||||
statusToday: 'Today',
|
|
||||||
acceptRequest: 'Accept connection request',
|
|
||||||
declineRequest: 'Decline connection request',
|
|
||||||
removeConnection: 'Remove connection',
|
|
||||||
sendRequest: 'Send connection request',
|
|
||||||
noDirectoryResults: 'No organization found in directory search.',
|
|
||||||
hideInvitationFields: 'Hide invitation fields',
|
|
||||||
sendInvitationLink: 'Send invitation link',
|
|
||||||
counterpartNameLabel: '{counterpart} name',
|
|
||||||
ownerEmailLabel: 'Owner email',
|
|
||||||
sendInvitation: 'Send invitation',
|
|
||||||
successConnectionSent: '{counterpart} connection request sent.',
|
|
||||||
successInviteCreated: 'Invitation link created for {email}',
|
|
||||||
successLinkCopied: 'Invitation link copied to clipboard.',
|
|
||||||
successAccepted: 'Connection request accepted.',
|
|
||||||
successDeclined: 'Connection request declined.',
|
|
||||||
successRemoved: 'Connection removed.',
|
|
||||||
historyTitle: 'Invitation History',
|
|
||||||
loadingHistory: 'Loading invitation history...',
|
|
||||||
historyEmpty: 'No invitations yet.',
|
|
||||||
tableInvitationLink: 'Invitation link',
|
|
||||||
statusPending: 'Invitation pending',
|
|
||||||
statusAccepted: 'Invitation accepted',
|
|
||||||
statusRejected: 'Invitation rejected',
|
|
||||||
statusExpired: 'Invitation expired',
|
|
||||||
copyInvitationLink: 'Copy invitation link',
|
|
||||||
copyInvitationLinkTitle: 'Copy invitation link (generates a new link if needed)',
|
|
||||||
selectorTitle: 'Organizations',
|
|
||||||
selectorSubtitleWithCreate: 'Select an organization to continue, or create a new one.',
|
|
||||||
selectorSubtitleSelectOnly: 'Select an organization to continue.',
|
|
||||||
createOrganization: 'Create Organization',
|
|
||||||
createAndContinue: 'Create and Continue',
|
|
||||||
emptyCanCreate: 'No organizations found. Create your first one to continue.',
|
|
||||||
emptyAskOwner: 'No organizations found. Ask an organization owner to invite you.',
|
|
||||||
continueArrow: 'Continue →',
|
|
||||||
planLabel: 'Plan: {name} • {maxUsers} users',
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
accountTitle: 'Account',
|
|
||||||
accountSubtitle: 'Profile and security settings for your login.',
|
|
||||||
accountPlaceholder:
|
|
||||||
'Password change and profile editing will be wired here next (e.g. invite flow, reset password).',
|
|
||||||
subscriptionsTitle: 'Subscriptions',
|
|
||||||
subscriptionsSubtitle:
|
|
||||||
'Your DyoLink workspace plan and seats for {orgName}. Clinic and lab income tracking stays under the sidebar Billing tab.',
|
|
||||||
noSubscriptionNotice:
|
|
||||||
'This organization has no active subscription. Select a plan below to start the purchase process.',
|
|
||||||
currentPlan: 'Current plan',
|
|
||||||
planPrice: 'Plan price',
|
|
||||||
seatsUsed: 'Seats used',
|
|
||||||
seatsRemaining: 'Seats remaining',
|
|
||||||
daysRemaining: 'Days remaining',
|
|
||||||
unlimited: 'Unlimited',
|
|
||||||
unlimitedSeats: 'Unlimited seats',
|
|
||||||
seatsCount: '{n} seats',
|
|
||||||
pricePerMonth: '${price} / month',
|
|
||||||
noActiveSubscription: 'No active subscription for this organization.',
|
|
||||||
trialEnded: 'Trial period has ended. Choose a plan when checkout is available.',
|
|
||||||
trialEndsIn: 'Trial ends in {days} day(s).',
|
|
||||||
seatsLow: 'Seat usage is high for this organization.',
|
|
||||||
choosePlanIntro:
|
|
||||||
'Choose a plan to continue. Purchase integration is not active yet, so this currently prepares the selection step only.',
|
|
||||||
planSolo: 'Solo',
|
|
||||||
planSmall: 'Small',
|
|
||||||
planMedium: 'Medium',
|
|
||||||
planLarge: 'Large',
|
|
||||||
planEnterprise: 'Enterprise',
|
|
||||||
startPurchase: 'Start purchase process',
|
|
||||||
purchaseNotice:
|
|
||||||
'Purchase flow will be enabled soon. {plan} is selected and ready for checkout setup.',
|
|
||||||
},
|
|
||||||
schedule: {
|
|
||||||
defaultLabel: 'Schedule date',
|
|
||||||
previousDay: 'Previous day',
|
|
||||||
nextDay: 'Next day',
|
|
||||||
chooseDate: 'Choose schedule date',
|
|
||||||
year: 'Year',
|
|
||||||
month: 'Month',
|
|
||||||
day: 'Day',
|
|
||||||
monthJanuary: 'January',
|
|
||||||
monthFebruary: 'February',
|
|
||||||
monthMarch: 'March',
|
|
||||||
monthApril: 'April',
|
|
||||||
monthMay: 'May',
|
|
||||||
monthJune: 'June',
|
|
||||||
monthJuly: 'July',
|
|
||||||
monthAugust: 'August',
|
|
||||||
monthSeptember: 'September',
|
|
||||||
monthOctober: 'October',
|
|
||||||
monthNovember: 'November',
|
|
||||||
monthDecember: 'December',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function deepMerge(base, overlay) {
|
|
||||||
const result = { ...base };
|
|
||||||
for (const key of Object.keys(base)) {
|
|
||||||
const baseVal = base[key];
|
|
||||||
const overlayVal = overlay?.[key];
|
|
||||||
if (baseVal && typeof baseVal === 'object' && !Array.isArray(baseVal)) {
|
|
||||||
result[key] = deepMerge(baseVal, overlayVal ?? {});
|
|
||||||
} else if (overlayVal !== undefined) {
|
|
||||||
result[key] = overlayVal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeJson(file, data) {
|
|
||||||
fs.writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
|
||||||
}
|
|
||||||
|
|
||||||
writeJson(path.join(messagesDir, 'en.json'), en);
|
|
||||||
|
|
||||||
for (const locale of ['fa', 'nl']) {
|
|
||||||
const file = path.join(messagesDir, `${locale}.json`);
|
|
||||||
const existing = fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : {};
|
|
||||||
writeJson(file, deepMerge(en, existing));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Messages built: en.json updated; fa.json and nl.json merged with existing translations.');
|
|
||||||
Reference in New Issue
Block a user