export type Locale = 'en' | 'fi' | 'sv';

const baseMessages = {
  en: {
    brand: 'lomavuokraus.fi',
    navProfile: 'Profile',
    navMyListings: 'My listings',
    navNewListing: 'New listing',
    navAdmin: 'Admin',
    navApprovals: 'Approvals',
    navUsers: 'Users',
    navMonitoring: 'Monitoring',
    navSettings: 'Settings',
    navLogout: 'Logout',
    navLogin: 'Login',
    navSignup: 'Sign up',
    navBrowse: 'Browse listings',
    navLanguage: 'Language',
    approvalsBadge: '{count}',
    heroEyebrow: 'lomavuokraus.fi',
    heroTitle: 'Find your next Finnish getaway',
    heroBody: 'Discover Finnish cabins, apartments and villas posted directly by their owners. Listings are verified before publishing and you contact hosts directly — simple and transparent.',
    ctaViewSample: 'View a sample listing',
    ctaHealth: 'Check health endpoint',
    ctaBrowse: 'Browse listings',
    highlightQualityTitle: 'Quality stays',
    highlightQualityBody: 'Curated cabins and villas with clear availability and simple booking.',
    highlightLocalTitle: 'Local-first',
    highlightLocalBody: 'Built for Finnish seasons with fast content delivery in the Nordics.',
    highlightApiTitle: 'API-friendly',
    highlightApiBody: 'Structured data so you can surface listings wherever you need them.',
    runtimeConfigTitle: 'Runtime configuration',
    runtimeConfigLead: 'Build-time and runtime values used by the service.',
    runtimeAppEnv: 'APP_ENV',
    runtimeSiteUrl: 'NEXT_PUBLIC_SITE_URL',
    runtimeApiBase: 'NEXT_PUBLIC_API_BASE',
    aboutTitle: 'About lomavuokraus.fi',
    aboutLead: 'A focused marketplace for Finnish holiday rentals with fast browsing, clear moderation, and a simple host experience.',
    pricingTitle: 'Pricing',
    pricingLead: 'Straightforward pricing for hosts with no surprises.',
    pricingMonthly: 'Monthly',
    pricingAnnual: 'Annual',
    pricingPerMonth: 'per month',
    pricingPerYear: 'per year (save 20%)',
    pricingMonthlyBody: 'Start with a monthly plan at 10€ per listing.',
    pricingAnnualBody: 'Pay annually for 100€ per listing and keep your costs predictable.',
    pricingPerListing: 'Pricing is per active listing. Cancel anytime.',
    pricingNotesTitle: 'Notes',
    pricingNotesBody: 'We keep pricing simple while we build out hosting tools, messaging, and integrations. All current features are included.',
    footerAbout: 'About',
    footerPricing: 'Pricing',
    footerPrivacy: 'Privacy & cookies',
    footerCookieNotice:
      'We use only essential cookies for login and security. By using this site you consent; if you do not accept, please do not use the site.',
    loginTitle: 'Login',
    emailLabel: 'Email',
    passwordLabel: 'Password',
    loginButton: 'Login',
    loggingIn: 'Logging in…',
    loginSuccess: 'Login successful.',
    registerTitle: 'Create an account',
    registerLead: 'Verify email is required, and an admin will approve your account.',
    nameOptional: 'Name (optional)',
    passwordHint: 'Password (min 8 chars)',
    registerButton: 'Register',
    registering: 'Submitting…',
    registerSuccess: 'Registration successful. Check your email for a verification link.',
    forgotTitle: 'Forgot your password?',
    forgotLead: 'Enter your email and we will send a reset link.',
    forgotSubmit: 'Send reset link',
    forgotSuccess: 'If that email exists, a reset link has been sent.',
    forgotError: 'Could not send reset link right now.',
    resetTitle: 'Reset password',
    resetLead: 'Set a new password for your account.',
    resetSubmit: 'Set new password',
    resetSuccess: 'Password updated. You can log in now.',
    resetError: 'Failed to reset password. The link may be invalid or expired.',
    resetMissingToken: 'Reset token missing. Please use the link from your email.',
    forgotCta: 'Forgot password?',
    pendingAdminTitle: 'Admin: pending items',
    pendingUsersTitle: 'Pending users',
    pendingListingsTitle: 'Pending listings',
    noPendingUsers: 'No pending users.',
    noPendingListings: 'No pending listings.',
    statusLabel: 'status',
    slugsLabel: 'Slugs',
    verifiedLabel: 'verified',
    approve: 'Approve',
    approveAdmin: 'Approve + make admin',
    reject: 'Reject',
    remove: 'Remove',
    publish: 'Publish',
    approvalsMessage: 'Listing updated',
    userUpdated: 'User updated',
    adminRequired: 'Admin access required',
    adminUsersTitle: 'Admin: users',
    adminUsersLead: 'Manage user roles and approvals.',
    monitorTitle: 'Admin: monitoring',
    monitorLead: 'Live status for Hetzner nodes, Kubernetes pods, and PostgreSQL.',
    monitorRefresh: 'Refresh now',
    monitorLastUpdated: 'Last updated',
    monitorNoData: 'No data yet.',
    monitorHetznerTitle: 'Hetzner nodes',
    monitorHetznerMissingToken: 'Set HCLOUD_TOKEN to show Hetzner nodes.',
    monitorHetznerEmpty: 'No Hetzner servers returned.',
    monitorK8sTitle: 'Kubernetes',
    monitorNodesTitle: 'Nodes',
    monitorPodsTitle: 'Pods',
    monitorNoPods: 'No pods in lomavuokraus namespaces.',
    monitorRestarts: 'Restarts',
    monitorAge: 'Age',
    monitorDbTitle: 'PostgreSQL',
    monitorServerTime: 'Server time',
    monitorDbSize: 'Database size',
    monitorDbRecovery: 'Recovery mode',
    monitorConnections: 'Connections',
    monitorHealthy: 'Healthy',
    monitorAttention: 'Needs attention',
    monitorLoadFailed: 'Failed to load monitoring data.',
    monitorCreated: 'Created',
    monitorLastReady: 'Last Ready transition',
    adminSettingsTitle: 'Admin: settings',
    adminSettingsLead: 'Site-wide feature toggles and policies.',
    tableEmail: 'Email',
    tableRole: 'Role',
    tableStatus: 'Status',
    tableVerified: 'Verified',
    tableApproved: 'Approved',
    myListingsTitle: 'My listings',
    createNewListing: 'Create new listing',
    noListings: 'No listings yet.',
    createOne: 'Create one',
    loading: 'Loading…',
    edit: 'Edit',
    view: 'View',
    removing: 'Removing…',
    removed: 'Listing removed',
    removeConfirm: 'Remove this listing? It will be hidden from others.',
    myProfileTitle: 'My profile',
    notLoggedIn: 'Not logged in',
    profileEmail: 'Email',
    profileName: 'Name',
    profilePhone: 'Phone',
    profileRole: 'Role',
    profileStatus: 'Status',
    profileEmailVerified: 'Email verified',
    profileApproved: 'Approved',
    profileUpdated: 'Profile updated',
    billingSettingsTitle: 'Billing assistant',
    billingSettingsLead: 'Opt into automated billing emails via the n8n agent. Set defaults and per-listing overrides.',
    billingEnableLabel: 'Enable billing assistant for my listings',
    billingAccountNameLabel: 'Billing account owner name',
    billingAccountPlaceholder: 'Use profile default',
    billingIbanLabel: 'IBAN for payouts',
    billingIbanPlaceholder: 'Use profile default',
    billingIncludeVat: 'Include a VAT line on invoices',
    billingListingsTitle: 'Per-listing billing',
    billingListingsLead: 'Override billing details per listing; blank fields inherit your profile defaults.',
    billingNoListings: 'No listings available yet.',
    billingVatChoice: 'VAT line preference',
    billingVatInherit: 'Use profile choice',
    billingVatYes: 'Include VAT line',
    billingVatNo: 'Do not include VAT line',
    billingDisabledHint: 'Enable the billing assistant to manage invoice details and VAT preferences.',
    billingLoadFailed: 'Failed to load billing settings.',
    billingSaveFailed: 'Could not save billing settings.',
    billingSaved: 'Billing settings saved.',
    settingsSaved: 'Settings saved.',
    emailLocked: 'Email cannot be changed',
    save: 'Save',
    saving: 'Saving…',
    yes: 'yes',
    no: 'no',
    verifyTitle: 'Email verification',
    verifyOk: 'Your email is verified. An admin will approve your account shortly.',
    verifyFail: 'Verification failed or token invalid.',
    listingLocation: 'Location',
    listingAddress: 'Address',
    listingCapacity: 'Capacity',
    listingPrices: 'Price (starting from)',
    listingAmenities: 'Amenities',
    listingNoAmenities: 'No amenities listed yet.',
    listingContact: 'Contact',
    contactLoginToView: 'Log in to view contact details for this listing.',
    listingMoreInfo: 'More info',
    availabilityTitle: 'Availability calendar',
    availabilityLegendBooked: 'Booked',
    availabilityMissing: 'Calendar not connected yet.',
    localeLabel: 'Locale',
    homeCrumb: 'Home',
    createListingTitle: 'Create listing',
    languageTabsLabel: 'Listing languages',
    languageTabsHint: 'Add translations for each supported language',
    localeSectionTitle: 'Listing content',
    localeReady: 'Ready',
    localePartial: 'In progress',
    localeMissing: 'Missing',
    aiHelperTitle: 'AI translation helper',
    aiHelperLead: 'Let AI fill the other languages for you.',
    aiOptionalHint: 'Optional: AI helper can fill other languages.',
    aiAutoExplain: 'Enter the texts in any one language and click the button; AI will fill the remaining locales.',
    aiAutoTranslate: 'Auto-translate missing languages',
    aiAutoTranslating: 'Translating…',
    aiAutoSuccess: 'Translations updated with AI.',
    aiAutoError: 'AI translation failed. Please try again or use manual mode below.',
    aiHelperNote: 'Uses OpenAI to translate missing texts.',
    aiManualLead: 'If auto-translate fails, copy this prompt to your AI assistant and paste the JSON reply below.',
    aiPromptLabel: 'Prompt to send to AI',
    aiCopyPrompt: 'Copy prompt',
    aiPromptCopied: 'Copied',
    aiCopyError: 'Copy failed',
    aiResponseLabel: 'Paste AI response (JSON)',
    aiApply: 'Apply AI response',
    aiApplyError: 'Could not read AI response. Please ensure it is valid JSON with a locales object.',
    aiApplySuccess: 'Translations updated from AI response.',
    settingContactVisibilityTitle: 'Listing contact visibility',
    settingContactVisibilityHelp: 'Hide host contact details from anonymous visitors so only logged-in users can see them.',
    settingRequireLoginForContact: 'Require login to see contact details',
    translationMissing: 'Add at least one language with a title and description.',
    saveDraft: 'Save draft',
    missingFields: 'Missing: {fields}',
    loginToCreate: 'Please log in first to create a listing.',
    slugLabel: 'Slug',
    slugHelp: 'Short, easy-to-type link name; use lowercase and hyphens (e.g. lake-cabin).',
    slugPreview: 'Your listing link will be: {url}',
    slugChecking: 'Checking availability…',
    slugAvailable: 'Slug is available',
    slugTaken: 'Slug already in use',
    slugCheckError: 'Could not check slug right now',
    localeInput: 'Locale',
    titleLabel: 'Title',
    descriptionLabel: 'Description',
    teaserLabel: 'Teaser',
    teaserHelp: 'Short intro shown on cards',
    countryLabel: 'Country',
    regionLabel: 'Region',
    cityLabel: 'City',
    contactNameLabel: 'Contact name',
    contactEmailLabel: 'Contact email',
    streetAddressLabel: 'Street address',
    addressNoteLabel: 'Address note (optional)',
    addressNotePlaceholder: 'Directions, parking, or arrival details',
    latitudeLabel: 'Latitude (optional)',
    longitudeLabel: 'Longitude (optional)',
    maxGuestsLabel: 'Max guests',
    bedroomsLabel: 'Bedrooms',
    bedsLabel: 'Beds',
    bathroomsLabel: 'Bathrooms',
    priceWeekdayLabel: 'Price starting from (weeknight, € / night)',
    priceWeekendLabel: 'Price starting from (weekend, € / night)',
    priceHintHelp: 'These prices are indicative only (starting from), not a binding offer.',
    priceStartingFromShort: 'Starting from {price}€ / night',
    priceWeekdayShort: '{price}€ weekday',
    priceWeekendShort: '{price}€ weekend',
    priceNotSet: 'Not provided',
    calendarUrlsLabel: 'Availability calendars (iCal URLs, one per line)',
    calendarUrlsHelp: 'Paste iCal links from other platforms. We will merge them to show availability.',
    imagesLabel: 'Images',
    imagesHelp: 'Upload up to {count} images (max {sizeMb}MB each).',
    imagesTooMany: 'Too many images (max {count}).',
    imagesTooLarge: 'Image is too large (max {sizeMb}MB).',
    imagesReadFailed: 'Could not read selected images.',
    imagesRequired: 'Please upload at least one image.',
    coverImageLabel: 'Cover image order',
    coverImageHelp: '1-based index of the uploaded images (defaults to 1)',
    coverChoice: 'Cover position {index}',
    makeCover: 'Make cover',
    existingImageLabel: 'Existing image',
    imageRemoveLastError: 'Add another image before removing the last one.',
    imageRemoveFailed: 'Failed to remove image.',
    submitListing: 'Create listing',
    submittingListing: 'Submitting…',
    createListingSuccess: 'Listing created with id {id} (status: {status})',
    approvalsCountLabel: 'Approvals',
    approvalsPending: '{count} pending',
    amenitySauna: 'Sauna',
    amenityFireplace: 'Fireplace',
    amenityWifi: 'Wi-Fi',
    amenityPets: 'Pets allowed',
    amenityLake: 'By the lake',
    amenityAirConditioning: 'Air conditioning',
    amenityKitchen: 'Kitchen',
    amenityDishwasher: 'Dishwasher',
    amenityWashingMachine: 'Washing machine',
    amenityBarbecue: 'Barbecue grill',
    amenityMicrowave: 'Microwave',
    amenityFreeParking: 'Free parking',
    amenityEvAvailable: 'EV charging nearby',
    amenityEvNearby: 'EV charging nearby',
    amenityEvOnSite: 'EV charging on-site',
    amenityWheelchairAccessible: 'Wheelchair accessible',
    amenitySkiPass: 'Ski pass included',
    evChargingLabel: 'EV charging',
    evChargingYes: 'EV charging available',
    evChargingNo: 'No EV charging',
    evChargingAny: 'Any',
    evChargingExplain: 'Is there EV charging available at the property?',
    capacityGuests: '{count} guests',
    capacityBedrooms: '{count} bedrooms',
    capacityBeds: '{count} beds',
    capacityBathrooms: '{count} bathrooms',
    capacityUnknown: 'Capacity not set',
    browseListingsTitle: 'Browse listings',
    browseListingsLead: 'Search public listings, filter by location, and explore them on the map.',
    sampleBadge: 'Sample',
    privacyTitle: 'Privacy & cookies',
    privacyUpdated: 'Updated: {date}',
    privacyCollectTitle: 'What data we collect',
    privacyCollectAccounts: 'Account data: email, password hash, name, phone (optional), role/status.',
    privacyCollectListings: 'Listing data: location, contact details, amenities, photos, translations.',
    privacyCollectLogs: 'Operational logs: minimal request metadata for diagnostics.',
    privacyUseTitle: 'How we use your data',
    privacyUseAuth: 'To authenticate users and manage sessions.',
    privacyUseListings: 'To publish and moderate listings.',
    privacyUseMail: 'To send transactional email (verification, password reset).',
    privacyUseLegal: 'To comply with legal requests or protect the service.',
    privacyStoreTitle: 'Storage & retention',
    privacyStoreDb: 'Data is stored in our Postgres database hosted in the EU.',
    privacyStoreBackups: 'Backups are retained for disaster recovery; removed accounts/listings may persist in backups for a limited time.',
    privacyCookiesTitle: 'Cookies',
    privacyCookiesSession: 'We use essential cookies only: a session cookie for login/authentication.',
    privacyCookiesNoTracking: 'No analytics, marketing, or tracking cookies are used.',
    privacySharingTitle: 'Sharing',
    privacySharingAds: 'We do not sell or share personal data with advertisers.',
    privacySharingOps: 'Data may be shared with service providers strictly for running the service (email delivery, hosting).',
    privacyRightsTitle: 'Your rights',
    privacyRightsAccess: 'Request access, correction, or deletion of your data.',
    privacyRightsConsent: 'Withdraw consent for non-essential processing (we currently process only essentials).',
    privacyRightsContact: 'Contact support for any privacy questions.',
    searchLabel: 'Search',
    searchPlaceholder: 'Search by name, description, or city',
    cityFilter: 'City',
    regionFilter: 'Region',
    searchButton: 'Search',
    searchAmenities: 'Amenities',
    searchAvailability: 'Availability',
    startDate: 'Start date',
    endDate: 'End date',
    availabilityOnlyWithCalendar: 'Only listings with a connected calendar are shown when filtering by dates.',
    availableForDates: 'Available for selected dates',
    notAvailableForDates: 'Unavailable for selected dates',
    calendarConnected: 'Calendar connected',
    clearFilters: 'Clear filters',
    addressSearchLabel: 'Find listings near an address',
    addressSearchPlaceholder: 'Street, city, or place',
    locateAddress: 'Locate on map',
    addressRadiusLabel: 'Within {km} km',
    listingsFound: '{count} listings',
    openListing: 'Open listing',
    mapNoResults: 'No listings match your filters.',
    addressNotFound: 'Address not found',
    addressLookupFailed: 'Failed to locate address',
    loadingMap: 'Loading map…',
    latestListingsTitle: 'Latest listings',
    latestListingsLead: 'Fresh rentals as they are published. Tap to browse.',
  },
  fi: {
    brand: 'lomavuokraus.fi',
    navProfile: 'Profiili',
    navMyListings: 'Omat kohteet',
    navNewListing: 'Luo kohde',
    navAdmin: 'Ylläpito',
    navApprovals: 'Tarkastettavat',
    navUsers: 'Käyttäjät',
    navMonitoring: 'Valvonta',
    navSettings: 'Asetukset',
    navLogout: 'Kirjaudu ulos',
    navLogin: 'Kirjaudu',
    navSignup: 'Rekisteröidy',
    navBrowse: 'Selaa kohteita',
    navLanguage: 'Kieli',
    approvalsBadge: '{count}',
    heroEyebrow: 'lomavuokraus.fi',
    heroTitle: 'Löydä seuraava mökkilomasi',
    heroBody: 'Selaa suomalaisten mökkien, huoneistojen ja villojen ilmoituksia suoraan omistajilta. Jokainen ilmoitus tarkistetaan ennen julkaisua, ja otat vuokranantajaan yhteyttä suoraan — yksinkertaista ja läpinäkyvää.',
    languageTabsLabel: 'Ilmoituksen kielet',
    languageTabsHint: 'Lisää käännökset kaikille tuetuille kielille',
    localeSectionTitle: 'Ilmoituksen sisältö',
    localeReady: 'Valmis',
    localePartial: 'Kesken',
    localeMissing: 'Puuttuu',
  aiHelperTitle: 'AI-käännösapu',
  aiHelperLead: 'Anna tekoälyn täydentää muut kielet puolestasi.',
    aiOptionalHint: 'Valinnainen: AI-avustaja voi täyttää muut kielet.',
    aiAutoExplain: 'Täytä tekstit yhdellä kielellä ja paina nappia; AI täyttää loput kielet.',
    aiAutoTranslate: 'Käännä puuttuvat kielet',
    aiAutoTranslating: 'Käännetään…',
    aiAutoSuccess: 'Käännökset päivitetty AI:lla.',
    aiAutoError: 'Käännös epäonnistui. Yritä uudelleen tai käytä manuaalitilaa alla.',
    aiHelperNote: 'Käyttää OpenAI:ta puuttuvien tekstien kääntämiseen.',
    aiManualLead: 'Jos automaattinen käännös ei toimi, kopioi prompti tekoälylle ja liitä JSON-vastaus alle.',
    aiPromptLabel: 'Prompti tekoälylle',
    aiCopyPrompt: 'Kopioi prompti',
    aiPromptCopied: 'Kopioitu',
    aiCopyError: 'Kopiointi epäonnistui',
    aiResponseLabel: 'Liitä tekoälyn vastaus (JSON)',
    aiApply: 'Käytä AI-vastausta',
    aiApplyError: 'Vastausta ei voitu lukea. Varmista, että se on kelvollista JSONia ja sisältää locales-avaimen.',
    aiApplySuccess: 'Käännökset päivitetty AI-vastauksesta.',
    settingContactVisibilityTitle: 'Ilmoitusten yhteystiedot',
    settingContactVisibilityHelp: 'Piilota isännän yhteystiedot kirjautumattomilta, näytä ne vain kirjautuneille käyttäjille.',
    settingRequireLoginForContact: 'Vaadi kirjautuminen yhteystietoihin',
    translationMissing: 'Täytä vähintään yhden kielen otsikko ja kuvaus.',
    saveDraft: 'Tallenna luonnos',
    missingFields: 'Puuttuu: {fields}',
    ctaViewSample: 'Katso esimerkkikohde',
    ctaHealth: 'Tarkista health-päätepiste',
    ctaBrowse: 'Selaa kohteita',
    highlightQualityTitle: 'Laadukkaat kohteet',
    highlightQualityBody: 'Kuratoidut mökit ja huvilat, selkeät saatavuudet ja helppo varaus.',
    highlightLocalTitle: 'Suomi edellä',
    highlightLocalBody: 'Tehty Suomen olosuhteisiin, nopea sisällönjakelu Pohjoismaissa.',
    highlightApiTitle: 'API-ystävällinen',
    highlightApiBody: 'Strukturoitu data, jotta löydät kohteet kaikissa kanavissa.',
    runtimeConfigTitle: 'Ajoaikainen konfiguraatio',
    runtimeConfigLead: 'Ajo- ja rakennusaikaiset arvot, joita palvelu käyttää.',
    runtimeAppEnv: 'APP_ENV',
    runtimeSiteUrl: 'NEXT_PUBLIC_SITE_URL',
    runtimeApiBase: 'NEXT_PUBLIC_API_BASE',
    aboutTitle: 'Tietoja lomavuokraus.fi:stä',
    aboutLead: 'Keskittynyt suomalainen loma-asuntopalvelu: nopea selaus, selkeä moderointi ja mutkaton kokemus vuokraajalle.',
    pricingTitle: 'Hinnasto',
    pricingLead: 'Selkeä hinnoittelu vuokraajille ilman yllätyksiä.',
    pricingMonthly: 'Kuukausi',
    pricingAnnual: 'Vuosimaksu',
    pricingPerMonth: 'kuukaudessa',
    pricingPerYear: 'vuodessa (20 % säästö)',
    pricingMonthlyBody: 'Aloita kuukausihinnalla 10 € per kohde.',
    pricingAnnualBody: 'Maksa vuodessa 100 € per kohde ja pidä kulut ennustettavina.',
    pricingPerListing: 'Hinta on per aktiivinen kohde. Voit perua milloin vain.',
    pricingNotesTitle: 'Huomiot',
    pricingNotesBody: 'Pidämme hinnoittelun yksinkertaisena samalla kun rakennamme uusia isäntätyökaluja, viestejä ja integraatioita. Kaikki nykyiset ominaisuudet sisältyvät.',
    footerAbout: 'Tietoa',
    footerPricing: 'Hinnasto',
    footerPrivacy: 'Tietosuoja ja evästeet',
    footerCookieNotice:
      'Käytämme vain välttämättömiä evästeitä kirjautumiseen ja turvallisuuteen. Käyttämällä sivustoa hyväksyt evästeet; jos et hyväksy, älä käytä sivustoa.',
    loginTitle: 'Kirjaudu sisään',
    emailLabel: 'Sähköposti',
    passwordLabel: 'Salasana',
    loginButton: 'Kirjaudu',
    loggingIn: 'Kirjaudutaan…',
    loginSuccess: 'Kirjautuminen onnistui.',
    registerTitle: 'Luo tili',
    registerLead: 'Sähköpostin varmistus vaaditaan, ja ylläpitäjä hyväksyy tilin.',
    nameOptional: 'Nimi (valinnainen)',
    passwordHint: 'Salasana (väh. 8 merkkiä)',
    registerButton: 'Rekisteröidy',
    registering: 'Lähetetään…',
    registerSuccess: 'Rekisteröinti onnistui. Tarkista sähköpostisi vahvistuslinkin vuoksi.',
    forgotTitle: 'Unohditko salasanasi?',
    forgotLead: 'Syötä sähköpostisi niin lähetämme palautuslinkin.',
    forgotSubmit: 'Lähetä palautuslinkki',
    forgotSuccess: 'Jos sähköposti löytyy, palautuslinkki on lähetetty.',
    forgotError: 'Linkin lähetys epäonnistui.',
    resetTitle: 'Vaihda salasana',
    resetLead: 'Aseta uusi salasana tilillesi.',
    resetSubmit: 'Aseta uusi salasana',
    resetSuccess: 'Salasana vaihdettu. Voit nyt kirjautua sisään.',
    resetError: 'Salasanan vaihto epäonnistui. Linkki voi olla vanhentunut.',
    resetMissingToken: 'Palautustunniste puuttuu. Käytä sähköpostista saatua linkkiä.',
    forgotCta: 'Unohdit salasanan?',
    pendingAdminTitle: 'Ylläpito: tarkastettavat',
    pendingUsersTitle: 'Odottavat käyttäjät',
    pendingListingsTitle: 'Odottavat kohteet',
    noPendingUsers: 'Ei odottavia käyttäjiä.',
    noPendingListings: 'Ei odottavia kohteita.',
    statusLabel: 'tila',
    slugsLabel: 'Osoitepolut',
    verifiedLabel: 'vahvistettu',
    approve: 'Hyväksy',
    approveAdmin: 'Hyväksy + admin',
    reject: 'Hylkää',
    remove: 'Poista käytöstä',
    publish: 'Julkaise',
    approvalsMessage: 'Kohde päivitetty',
    userUpdated: 'Käyttäjä päivitetty',
    adminRequired: 'Ylläpitäjän oikeudet vaaditaan',
    adminUsersTitle: 'Ylläpito: käyttäjät',
    adminUsersLead: 'Hallinnoi rooleja ja hyväksyntöjä.',
    monitorTitle: 'Ylläpito: valvonta',
    monitorLead: 'Hetzner-solmujen, Kubernetes-podien ja PostgreSQL:n tilanne yhdessä näkymässä.',
    monitorRefresh: 'Päivitä nyt',
    monitorLastUpdated: 'Päivitetty',
    monitorNoData: 'Ei dataa vielä.',
    monitorHetznerTitle: 'Hetzner-solmut',
    monitorHetznerMissingToken: 'Aseta HCLOUD_TOKEN, jotta Hetzner-solmut saadaan näkyviin.',
    monitorHetznerEmpty: 'Hetzner ei palauttanut palvelimia.',
    monitorK8sTitle: 'Kubernetes',
    monitorNodesTitle: 'Solmut',
    monitorPodsTitle: 'Podit',
    monitorNoPods: 'Ei podeja lomavuokraus-namespacessa.',
    monitorRestarts: 'Restartit',
    monitorAge: 'Ikä',
    monitorDbTitle: 'PostgreSQL',
    monitorServerTime: 'Palvelimen aika',
    monitorDbSize: 'Tietokannan koko',
    monitorDbRecovery: 'Recovery-tila',
    monitorConnections: 'Yhteydet',
    monitorHealthy: 'Kunnossa',
    monitorAttention: 'Huomio',
    monitorLoadFailed: 'Valvontadatan haku epäonnistui.',
    monitorCreated: 'Luotu',
    monitorLastReady: 'Viimeisin Ready-muutos',
    adminSettingsTitle: 'Ylläpito: asetukset',
    adminSettingsLead: 'Sivuston laajuiset ominaisuusasetukset ja käytännöt.',
    tableEmail: 'Sähköposti',
    tableRole: 'Rooli',
    tableStatus: 'Tila',
    tableVerified: 'Vahvistettu',
    tableApproved: 'Hyväksytty',
    myListingsTitle: 'Omat kohteet',
    createNewListing: 'Luo uusi kohde',
    noListings: 'Ei kohteita vielä.',
    createOne: 'Luo kohde',
    loading: 'Ladataan…',
    edit: 'Muokkaa',
    view: 'Näytä',
    removing: 'Poistetaan…',
    removed: 'Kohde poistettu näkyvistä',
    removeConfirm: 'Poista kohde? Se piilotetaan muilta.',
    myProfileTitle: 'Oma profiili',
    notLoggedIn: 'Et ole kirjautunut',
    profileEmail: 'Sähköposti',
    profileName: 'Nimi',
    profilePhone: 'Puhelin',
    profileRole: 'Rooli',
    profileStatus: 'Tila',
    profileEmailVerified: 'Sähköposti vahvistettu',
    profileApproved: 'Hyväksytty',
    profileUpdated: 'Profiili päivitetty',
    billingSettingsTitle: 'Laskutusavustin',
    billingSettingsLead: 'Ota n8n-laskutus käyttöön halutessasi ja määritä oletus- ja kohdekohtaiset tiedot.',
    billingEnableLabel: 'Ota laskutusavustin käyttöön kohteilleni',
    billingAccountNameLabel: 'Tilinomistajan nimi',
    billingAccountPlaceholder: 'Käytä profiilin oletusta',
    billingIbanLabel: 'IBAN-maksuissa',
    billingIbanPlaceholder: 'Käytä profiilin oletusta',
    billingIncludeVat: 'Lisää laskulle ALV-rivi',
    billingListingsTitle: 'Kohdekohtaiset asetukset',
    billingListingsLead: 'Ylikirjoita tiedot kohteittain; tyhjä kenttä käyttää profiilin arvoa.',
    billingNoListings: 'Ei kohteita vielä.',
    billingVatChoice: 'ALV-rivin valinta',
    billingVatInherit: 'Käytä profiilin asetusta',
    billingVatYes: 'Lisää ALV-rivi',
    billingVatNo: 'Älä lisää ALV-riviä',
    billingDisabledHint: 'Ota laskutusavustin käyttöön lisätäksesi laskutustiedot ja ALV-valinnat.',
    billingLoadFailed: 'Laskutusasetusten lataus epäonnistui.',
    billingSaveFailed: 'Laskutusasetusten tallennus epäonnistui.',
    billingSaved: 'Laskutusasetukset tallennettu.',
    settingsSaved: 'Asetukset tallennettu.',
    emailLocked: 'Sähköpostia ei voi vaihtaa',
    save: 'Tallenna',
    saving: 'Tallennetaan…',
    yes: 'kyllä',
    no: 'ei',
    verifyTitle: 'Sähköpostin vahvistus',
    verifyOk: 'Sähköposti vahvistettu. Ylläpitäjä hyväksyy tilin pian.',
    verifyFail: 'Vahvistus epäonnistui tai token on virheellinen.',
    listingLocation: 'Sijainti',
    listingAddress: 'Osoite',
    listingCapacity: 'Tilat',
    listingPrices: 'Hinta (alkaen)',
    listingAmenities: 'Varustelu',
    listingNoAmenities: 'Varustelua ei ole listattu.',
    listingContact: 'Yhteystiedot',
    contactLoginToView: 'Kirjaudu sisään nähdäksesi tämän ilmoituksen yhteystiedot.',
    listingMoreInfo: 'Lisätietoja',
    availabilityTitle: 'Saatavuuskalenteri',
    availabilityLegendBooked: 'Varattu',
    availabilityMissing: 'Kalenteria ei ole vielä yhdistetty.',
    localeLabel: 'Kieli',
    homeCrumb: 'Etusivu',
    createListingTitle: 'Luo kohde',
    loginToCreate: 'Kirjaudu ensin luodaksesi kohteen.',
  slugLabel: 'Osoitepolku',
  slugHelp: 'Keksi lyhyt ja helppo linkki; käytä pieniä kirjaimia ja väliviivoja (esim. saimaa-mokki).',
  slugPreview: 'Ilmoituksen linkki on: {url}',
    slugChecking: 'Tarkistetaan saatavuutta…',
    slugAvailable: 'Slug on vapaa',
    slugTaken: 'Slug on jo käytössä',
    slugCheckError: 'Slugia ei voitu tarkistaa nyt',
    localeInput: 'Kieli',
    titleLabel: 'Otsikko',
    descriptionLabel: 'Kuvaus',
    teaserLabel: 'Tiivistelmä',
    teaserHelp: 'Lyhyt ingressi kortteihin',
    countryLabel: 'Maa',
    regionLabel: 'Maakunta/alue',
    cityLabel: 'Kunta/kaupunki',
    contactNameLabel: 'Yhteyshenkilö',
    contactEmailLabel: 'Yhteyssähköposti',
    streetAddressLabel: 'Katuosoite',
    addressNoteLabel: 'Saapumisohje (valinnainen)',
    addressNotePlaceholder: 'Reittiohje, pysäköinti tai muut saapumisohjeet',
    latitudeLabel: 'Leveysaste (valinnainen)',
    longitudeLabel: 'Pituusaste (valinnainen)',
    maxGuestsLabel: 'Vieraita enintään',
    bedroomsLabel: 'Makuuhuoneita',
    bedsLabel: 'Vuoteita',
    bathroomsLabel: 'Kylpyhuoneita',
    priceWeekdayLabel: 'Hinta alkaen (arki, € / yö)',
    priceWeekendLabel: 'Hinta alkaen (viikonloppu, € / yö)',
    priceHintHelp: 'Hinnat ovat suuntaa-antavia (alkaen), eivät sitovia.',
    priceStartingFromShort: 'Alkaen {price}€ / yö',
    priceWeekdayShort: '{price}€ arki',
    priceWeekendShort: '{price}€ viikonloppu',
    priceNotSet: 'Ei ilmoitettu',
    calendarUrlsLabel: 'Saatavuuskalenterit (iCal-osoitteet, yksi per rivi)',
    calendarUrlsHelp: 'Liitä iCal-linkit muilta alustoilta. Yhdistämme ne saatavuuden näyttämiseen.',
    imagesLabel: 'Kuvat',
    imagesHelp: 'Lataa enintään {count} kuvaa (max {sizeMb} Mt / kuva).',
    imagesTooMany: 'Liikaa kuvia (enintään {count}).',
    imagesTooLarge: 'Kuva on liian suuri (max {sizeMb} Mt).',
    imagesReadFailed: 'Kuvien luku epäonnistui.',
    imagesRequired: 'Lataa vähintään yksi kuva.',
    coverImageLabel: 'Kansikuvan järjestys',
    coverImageHelp: 'Monettako ladatuista kuvista käytetään kansikuvana (oletus 1)',
    coverChoice: 'Kansipaikka {index}',
    makeCover: 'Aseta kansikuvaksi',
    existingImageLabel: 'Nykyinen kuva',
    imageRemoveLastError: 'Lisää toinen kuva ennen viimeisen poistamista.',
    imageRemoveFailed: 'Kuvan poistaminen epäonnistui.',
    submitListing: 'Luo kohde',
    submittingListing: 'Lähetetään…',
    createListingSuccess: 'Kohde luotu id:llä {id} (tila: {status})',
    approvalsCountLabel: 'Tarkastettavat',
    approvalsPending: '{count} odottaa',
    amenitySauna: 'Sauna',
    amenityFireplace: 'Takka',
    amenityWifi: 'Wi-Fi',
    amenityPets: 'Lemmikit sallittu',
    amenityLake: 'Järven rannalla',
    amenityAirConditioning: 'Ilmastointi',
    amenityKitchen: 'Keittiö',
    amenityDishwasher: 'Astianpesukone',
    amenityWashingMachine: 'Pyykinpesukone',
    amenityBarbecue: 'Grilli',
    amenityMicrowave: 'Mikroaaltouuni',
    amenityFreeParking: 'Maksuton pysäköinti',
    amenityEvAvailable: 'Sähköauton lataus lähellä',
    amenityEvNearby: 'Sähköauton lataus lähellä',
    amenityEvOnSite: 'Sähköauton lataus kohteessa',
    amenityWheelchairAccessible: 'Esteetön / pyörätuolilla',
    amenitySkiPass: 'Hissilippu sisältyy',
    evChargingLabel: 'Sähköauton lataus',
    evChargingYes: 'Latausmahdollisuus',
    evChargingNo: 'Ei latausta',
    evChargingAny: 'Kaikki',
    evChargingExplain: 'Onko kohteessa sähköauton latausmahdollisuus?',
    capacityGuests: '{count} vierasta',
    capacityBedrooms: '{count} makuuhuonetta',
    capacityBeds: '{count} vuodetta',
    capacityBathrooms: '{count} kylpyhuonetta',
    capacityUnknown: 'Kapasiteettia ei ole asetettu',
    browseListingsTitle: 'Selaa kohteita',
    browseListingsLead: 'Hae julkaistuja kohteita, rajaa sijainnilla ja tutki kartalla.',
    sampleBadge: 'Mallikohde',
    privacyTitle: 'Tietosuoja ja evästeet',
    privacyUpdated: 'Päivitetty: {date}',
    privacyCollectTitle: 'Mitä tietoja keräämme',
    privacyCollectAccounts: 'Käyttäjätiedot: sähköposti, salasanatiiviste, nimi, puhelin (valinnainen), rooli/tila.',
    privacyCollectListings: 'Kohdetiedot: sijainti, yhteystiedot, varustelu, kuvat, kieliversiot.',
    privacyCollectLogs: 'Lokit: rajattu pyyntömeta diagnostiikkaan.',
    privacyUseTitle: 'Miten käytämme tietoja',
    privacyUseAuth: 'Kirjautumiseen ja sessioiden hallintaan.',
    privacyUseListings: 'Kohteiden julkaisuun ja moderointiin.',
    privacyUseMail: 'Transaktio­sähköposteihin (vahvistus, salasanan palautus).',
    privacyUseLegal: 'Lakivelvoitteiden täyttämiseen ja palvelun suojaamiseen.',
    privacyStoreTitle: 'Säilytys',
    privacyStoreDb: 'Tiedot tallennetaan EU:ssa olevaan Postgres-tietokantaan.',
    privacyStoreBackups: 'Varmuuskopioita säilytetään palautusta varten; poistetut tiedot voivat esiintyä varmuuskopioissa rajatun ajan.',
    privacyCookiesTitle: 'Evästeet',
    privacyCookiesSession: 'Käytämme vain välttämättömiä evästeitä: kirjautumissessio.',
    privacyCookiesNoTracking: 'Ei analytiikka-, mainos- tai seurantateknologioita.',
    privacySharingTitle: 'Tietojen jakaminen',
    privacySharingAds: 'Emme myy tai jaa henkilötietoja mainostajille.',
    privacySharingOps: 'Palveluntarjoajille vain palvelun pyörittämiseen (sähköposti, hosting).',
    privacyRightsTitle: 'Oikeutesi',
    privacyRightsAccess: 'Voit pyytää tietojen nähtävyyttä, oikaisua tai poistoa.',
    privacyRightsConsent: 'Voit perua suostumuksen ei-välttämättömästä käsittelystä (tällä hetkellä vain välttämättömät).',
    privacyRightsContact: 'Ota yhteyttä, jos sinulla on kysyttävää tietosuojasta.',
    searchLabel: 'Haku',
    searchPlaceholder: 'Hae nimellä, kuvauksella tai paikkakunnalla',
    cityFilter: 'Kaupunki/kunta',
    regionFilter: 'Maakunta/alue',
    searchButton: 'Hae',
    searchAmenities: 'Varustelu',
    searchAvailability: 'Saatavuus',
    startDate: 'Alkupäivä',
    endDate: 'Loppupäivä',
    availabilityOnlyWithCalendar: 'Päiväsuodatus näyttää vain kohteet, joissa on kalenteri yhdistettynä.',
    availableForDates: 'Vapaa valituille päiville',
    notAvailableForDates: 'Ei vapaana valituille päiville',
    calendarConnected: 'Kalenteri yhdistetty',
    clearFilters: 'Tyhjennä suodattimet',
    addressSearchLabel: 'Etsi kohteita osoitteen läheltä',
    addressSearchPlaceholder: 'Katu, kaupunki tai paikka',
    locateAddress: 'Paikanna kartalle',
    addressRadiusLabel: '{km} km säteellä',
    listingsFound: '{count} kohdetta',
    openListing: 'Avaa kohde',
    mapNoResults: 'Suodattimilla ei löytynyt kohteita.',
    addressNotFound: 'Osoitetta ei löydy',
    addressLookupFailed: 'Paikannus epäonnistui',
    loadingMap: 'Ladataan karttaa…',
    latestListingsTitle: 'Viimeisimmät kohteet',
    latestListingsLead: 'Tuoreimmat kohteet heti julkaisun jälkeen.',
  },
} as const;

const svMessages: Record<keyof typeof baseMessages.en, string> = {
  ...baseMessages.en,
  navProfile: 'Profil',
  navMyListings: 'Mina annonser',
  navNewListing: 'Ny annons',
  navAdmin: 'Admin',
  navApprovals: 'Godkännanden',
  navUsers: 'Användare',
  navMonitoring: 'Övervakning',
  navSettings: 'Inställningar',
  navLogout: 'Logga ut',
  navLogin: 'Logga in',
  navSignup: 'Registrera dig',
  navBrowse: 'Bläddra bland annonser',
  navLanguage: 'Språk',
  monitorTitle: 'Admin: övervakning',
  monitorLead: 'Livesstatus för Hetzner-noder, Kubernetes-pods och PostgreSQL.',
  monitorRefresh: 'Uppdatera nu',
  monitorLastUpdated: 'Senast uppdaterad',
  monitorNoData: 'Ingen data ännu.',
  monitorHetznerTitle: 'Hetzner-noder',
  monitorHetznerMissingToken: 'Sätt HCLOUD_TOKEN för att visa Hetzner-noder.',
  monitorHetznerEmpty: 'Inga Hetzner-servrar hittades.',
  monitorK8sTitle: 'Kubernetes',
  monitorNodesTitle: 'Noder',
  monitorPodsTitle: 'Pods',
  monitorNoPods: 'Inga pods i lomavuokraus-namespaces.',
  monitorRestarts: 'Omstarter',
  monitorAge: 'Ålder',
  monitorDbTitle: 'PostgreSQL',
  monitorServerTime: 'Servertid',
  monitorDbSize: 'Databasstorlek',
  monitorDbRecovery: 'Recovery-läge',
  monitorConnections: 'Anslutningar',
  monitorHealthy: 'OK',
  monitorAttention: 'Behöver åtgärd',
  monitorLoadFailed: 'Kunde inte hämta övervakningsdata.',
  monitorCreated: 'Skapad',
  monitorLastReady: 'Senaste Ready-ändring',
  adminSettingsTitle: 'Admin: inställningar',
  adminSettingsLead: 'Webbplatsövergripande funktioner och policyer.',
  slugHelp: 'Hitta på en kort och enkel länk; använd små bokstäver och bindestreck (t.ex. sjo-stuga).',
  slugPreview: 'Länk till annonsen: {url}',
  heroTitle: 'Hitta ditt nästa finska getaway',
  heroBody: 'Upptäck stugor, lägenheter och villor direkt från ägarna. Annonser verifieras innan publicering och du kontaktar värdarna direkt — enkelt och transparent.',
  ctaBrowse: 'Bläddra bland annonser',
  createListingTitle: 'Skapa annons',
  edit: 'Redigera',
  languageTabsLabel: 'Annonsens språk',
  languageTabsHint: 'Lägg till översättningar för varje språk',
  localeSectionTitle: 'Annonsinnehåll',
  localeReady: 'Klar',
  localePartial: 'Pågår',
  localeMissing: 'Saknas',
  aiHelperTitle: 'AI-översättningshjälp',
  aiHelperLead: 'Låt AI fylla i de andra språken åt dig.',
  aiOptionalHint: 'Valfritt: AI-hjälpen kan fylla i andra språk.',
  aiAutoExplain: 'Fyll texterna på ett språk och klicka på knappen så fyller AI i resten.',
  aiAutoTranslate: 'Översätt saknade språk',
  aiAutoTranslating: 'Översätter…',
  aiAutoSuccess: 'Översättningar uppdaterades med AI.',
  aiAutoError: 'AI-översättning misslyckades. Försök igen eller använd manuellt läge nedan.',
  aiHelperNote: 'Använder OpenAI för att översätta saknade texter.',
  aiManualLead: 'Om autokorrespondens misslyckas, kopiera prompten till din AI och klistra in JSON-svaret nedan.',
  aiPromptLabel: 'Prompt till AI',
  aiCopyPrompt: 'Kopiera prompt',
  aiPromptCopied: 'Kopierad',
  aiCopyError: 'Kopiering misslyckades',
  aiResponseLabel: 'Klistra in AI-svar (JSON)',
  aiApply: 'Använd AI-svar',
  aiApplyError: 'Kunde inte läsa AI-svaret. Se till att det är giltig JSON med locales-nyckel.',
  aiApplySuccess: 'Översättningar uppdaterades från AI-svaret.',
  settingContactVisibilityTitle: 'Synlighet för kontaktuppgifter',
  settingContactVisibilityHelp: 'Dölj värdens kontaktuppgifter för anonyma besökare så att bara inloggade ser dem.',
  settingRequireLoginForContact: 'Kräv inloggning för att se kontaktuppgifter',
  billingSettingsTitle: 'Faktureringsassistent',
  billingSettingsLead: 'Aktivera n8n-fakturor om du vill och ange standard- samt objektspecifika uppgifter.',
  billingEnableLabel: 'Aktivera faktureringsassistent för mina annonser',
  billingAccountNameLabel: 'Kontoinnehavarens namn',
  billingAccountPlaceholder: 'Använd profilens standard',
  billingIbanLabel: 'IBAN för utbetalningar',
  billingIbanPlaceholder: 'Använd profilens standard',
  billingIncludeVat: 'Ta med momsrad på fakturor',
  billingListingsTitle: 'Objektvisa inställningar',
  billingListingsLead: 'Åsidosätt uppgifter per annons; tomma fält använder profilens värden.',
  billingNoListings: 'Inga annonser ännu.',
  billingVatChoice: 'Momsrad',
  billingVatInherit: 'Använd profilinställning',
  billingVatYes: 'Lägg till momsrad',
  billingVatNo: 'Lägg inte till momsrad',
  billingDisabledHint: 'Aktivera assistenten för att hantera fakturauppgifter och momsval.',
  billingLoadFailed: 'Kunde inte ladda faktureringsinställningar.',
  billingSaveFailed: 'Kunde inte spara faktureringsinställningar.',
  billingSaved: 'Faktureringsinställningar sparade.',
  settingsSaved: 'Inställningar sparade.',
  translationMissing: 'Lägg till minst ett språk med titel och beskrivning.',
  saveDraft: 'Spara utkast',
  missingFields: 'Saknas: {fields}',
  slugChecking: 'Kontrollerar tillgänglighet…',
  slugAvailable: 'Sluggen är ledig',
  slugTaken: 'Sluggen används redan',
  slugCheckError: 'Kunde inte kontrollera sluggen nu',
  teaserHelp: 'Kort ingress som syns i korten',
  priceWeekdayLabel: 'Pris från (vardag, € / natt)',
  priceWeekendLabel: 'Pris från (helg, € / natt)',
  priceHintHelp: 'Priserna är endast vägledande (från), inte ett bindande erbjudande.',
  priceStartingFromShort: 'Från {price}€ / natt',
  priceWeekdayShort: '{price}€ vardag',
  priceWeekendShort: '{price}€ helg',
  makeCover: 'Gör till omslag',
  existingImageLabel: 'Befintlig bild',
  imageRemoveLastError: 'Lägg till en annan bild innan du tar bort den sista.',
  imageRemoveFailed: 'Det gick inte att ta bort bilden.',
  priceNotSet: 'Ej angivet',
  listingPrices: 'Pris (från)',
  listingContact: 'Kontakt',
  contactLoginToView: 'Logga in för att se kontaktuppgifter för denna annons.',
  capacityUnknown: 'Kapacitet ej angiven',
  amenityEvAvailable: 'EV-laddning i närheten',
  amenityEvNearby: 'EV-laddning i närheten',
  amenityEvOnSite: 'EV-laddning på plats',
  amenityWheelchairAccessible: 'Rullstolsanpassat',
  amenitySkiPass: 'Liftkort ingår',
  amenityMicrowave: 'Mikrovågsugn',
  amenityFreeParking: 'Gratis parkering',
  evChargingLabel: 'EV-laddning',
  evChargingYes: 'EV-laddning finns',
  evChargingNo: 'Ingen EV-laddning',
  evChargingAny: 'Alla',
  evChargingExplain: 'Finns det EV-laddning på plats?',
  footerCookieNotice:
    'Vi använder endast nödvändiga cookies för inloggning och säkerhet. Genom att använda sajten godkänner du cookies; om du inte gör det, använd inte webbplatsen.',
};

export const messages = { ...baseMessages, sv: svMessages } as const;
export type MessageKey = keyof typeof baseMessages.en;

function normalizeLocale(input?: string | null): Locale | null {
  if (!input) return null;
  const lower = input.toLowerCase();
  if (lower.startsWith('fi')) return 'fi';
  if (lower.startsWith('en')) return 'en';
  if (lower.startsWith('sv')) return 'sv';
  return null;
}

export function resolveLocale(opts: { cookieLocale?: string | null; acceptLanguage?: string | null }): Locale {
  const fromCookie = normalizeLocale(opts.cookieLocale);
  if (fromCookie) return fromCookie;
  const fromHeader = normalizeLocale(opts.acceptLanguage);
  if (fromHeader) return fromHeader;
  return 'en';
}

export function t(locale: Locale, key: MessageKey, vars?: Record<string, string | number>) {
  const table = messages[locale] ?? messages.en;
  const template = String(table[key] ?? messages.en[key]);
  if (!vars) return template;
  return Object.entries(vars).reduce<string>((acc, [k, v]) => acc.replace(`{${k}}`, String(v)), template);
}
