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 = { ...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, ) { const table = messages[locale] ?? messages.en; const template = String(table[key] ?? messages.en[key]); if (!vars) return template; return Object.entries(vars).reduce( (acc, [k, v]) => acc.replace(`{${k}}`, String(v)), template, ); }