Add translations

This commit is contained in:
Andras Schmelczer 2026-04-04 09:27:55 +01:00
parent c14d28f430
commit a7aaf5effa
5 changed files with 4222 additions and 0 deletions

View file

@ -0,0 +1,852 @@
const en = {
// ── Common ──────────────────────────────────────────
common: {
save: 'Save',
cancel: 'Cancel',
close: 'Close',
delete: 'Delete',
open: 'Open',
share: 'Share',
copy: 'Copy',
copied: 'Copied!',
copiedToClipboard: 'Copied to clipboard',
loading: 'Loading...',
loadMore: 'Load More',
remaining: '{{count}} remaining',
search: 'Search',
all: 'All',
none: 'None',
viewDataSource: 'View data source',
total: 'Total',
min: 'min',
or: 'or',
area: 'Area',
properties: 'Properties',
postcode: 'Postcode',
noAreaSelected: 'No area selected',
noAreaSelectedDesc: 'Click any coloured area on the map to see crime, schools, prices, and more',
clickForDetails: 'Click for details',
property: 'property',
propertiesPlural: 'properties',
},
// ── Header / Nav ───────────────────────────────────
header: {
appName: 'Perfect Postcode',
dashboard: 'Dashboard',
learn: 'Learn',
pricing: 'Pricing',
inviteFriends: 'Invite Friends',
saved: 'Saved',
logIn: 'Log in',
createAccount: 'Create account',
sharing: 'Sharing...',
exportLabel: 'Export',
exporting: 'Exporting...',
exportToExcel: 'Export to Excel',
openMenu: 'Open menu',
closeMenu: 'Close menu',
},
// ── User Menu ──────────────────────────────────────
userMenu: {
fullAccess: 'Full Access',
demo: 'Demo',
themeLight: 'Theme: Light',
themeDark: 'Theme: Dark',
account: 'Account',
logOut: 'Log out',
},
// ── Mobile Menu ────────────────────────────────────
mobileMenu: {
menu: 'Menu',
home: 'Home',
},
// ── Auth Modal ─────────────────────────────────────
auth: {
logIn: 'Log in',
createAccount: 'Create account',
resetPassword: 'Reset password',
valueProp: 'Save searches, bookmark properties, and pick up where you left off.',
continueWithGoogle: 'Continue with Google',
email: 'Email',
emailPlaceholder: 'you@example.com',
password: 'Password',
passwordPlaceholderRegister: 'Min 8 characters',
passwordPlaceholderLogin: 'Your password',
forgotPassword: 'Forgot password?',
resetSent: 'Check your email for a reset link.',
pleaseWait: 'Please wait...',
sendResetLink: 'Send reset link',
backToLogin: 'Back to login',
},
// ── Upgrade Modal ──────────────────────────────────
upgrade: {
title: 'See all of England',
description: "You're currently exploring the demo area. Get lifetime access to every postcode, every filter, every neighbourhood. One payment, forever.",
free: 'Free',
once: '/once',
freeForEarly: 'Free for early adopters. No credit card required.',
oneTimePayment: 'One-time payment. Lifetime access. 30-day money-back guarantee.',
redirecting: 'Redirecting...',
claimFreeAccess: 'Claim free access',
upgradeFor: 'Upgrade for {{price}}',
registerAndUpgrade: 'Register & Upgrade',
alreadyHaveAccount: 'Already have an account? Log in',
continueWithDemo: 'Continue with demo',
checkoutFailed: 'Checkout failed',
},
// ── Save Search Modal ──────────────────────────────
saveSearch: {
title: 'Save Search',
saved: 'Search saved',
savedSuccess: 'Your search has been saved successfully.',
viewSavedSearches: 'View saved searches',
name: 'Name',
namePlaceholder: 'My search',
saving: 'Saving...',
},
// ── License Success ────────────────────────────────
licenseSuccess: {
title: "You're in.",
subtitle: 'Your lifetime access is now active.',
description: 'Full access to every feature, every postcode, across all of England.',
startExploring: 'Start exploring',
},
// ── Filters ────────────────────────────────────────
filters: {
activeFilters: 'Active Filters',
addFilter: 'Add Filter',
historical: 'Historical',
buy: 'Buy',
rent: 'Rent',
findingPerfectPostcode: 'Finding the Perfect Postcode',
addFiltersHint: 'Add filters below to narrow the map to areas that match your criteria',
upgradePrompt: 'See crime, schools, noise, broadband, and 50+ more filters across all of England.',
oneTimeLifetime: 'One-time payment, lifetime access.',
upgradeToFullMap: 'Upgrade to full map',
chooseFilters: 'Choose the filters that matter to you. The map updates as you go.',
searchFeatures: 'Search features...',
noMatchingFeatures: 'No matching features',
tryDifferentSearch: 'Try a different search term',
allFeaturesActive: 'All features are active',
removeFilterHint: 'Remove a filter to see available features',
featureInfo: 'Feature info',
replayTutorial: 'Replay interactive tutorial',
},
// ── Philosophy Popup ───────────────────────────────
philosophy: {
intro: 'Start with your must-haves, then layer on nice-to-haves. The map narrows as you add filters. The areas left are your best matches.',
step1Title: 'Budget and basics',
step1Desc: '(price range, floor area, property type)',
step2Title: 'Commute',
step2Desc: '(travel time to your workplace by car, bike, or transit)',
step3Title: 'Safety',
step3Desc: '(crime rates, noise levels, ground stability)',
step4Title: 'Schools',
step4Desc: '(nearby Ofsted-rated Good or Outstanding schools)',
step5Title: 'Lifestyle',
step5Desc: '(restaurants, parks, broadband speed)',
step6Title: 'Energy',
step6Desc: '(EPC ratings, insulation, heating costs)',
tip: "Tip: if nothing matches, relax one constraint at a time to see which trade-off opens up the most options.",
},
// ── Travel Time ────────────────────────────────────
travel: {
travelTime: 'Travel Time ({{mode}})',
maxTime: 'Max time',
selectDestination: 'Select destination...',
bestCase: 'Best case',
bestCaseTitle: 'Best case travel time',
bestCaseDesc: 'Uses the fastest realistic journey time (if you time your departure well and catch good connections). The default uses the <strong>median</strong>, representing a typical journey regardless of when you leave.',
previewOnMap: 'Preview on map',
stopPreviewing: 'Stop previewing',
removeTravelTime: 'Remove travel time',
addTravelTime: 'Add {{mode}} travel time',
clearDestination: 'Clear destination',
typeToFilter: 'Type to filter...',
noDestinations: 'No destinations found',
modeCar: 'Car',
modeBicycle: 'Bicycle',
modeWalking: 'Walking',
modeTransit: 'Transit',
modeCarDesc: 'Drive time via the fastest road route',
modeBicycleDesc: 'Cycling time using bike-friendly routes',
modeWalkingDesc: 'Walking time along pedestrian paths and pavements',
modeTransitDesc: 'Journey time by train, tube, and bus',
},
// ── Travel Time Info Popup ─────────────────────────
travelInfo: {
transitDesc: ' by public transport (bus, rail, tube). Times are computed across a typical weekday morning window.',
carDesc: ' by car, based on typical road speeds and the road network.',
bicycleDesc: ' by bicycle, using cycle-friendly routes.',
walkingDesc: ' on foot, using pedestrian paths and pavements.',
mainDesc: 'Shows how long it takes to reach the selected destination from each area',
sliderHint: 'Use the slider to set your maximum commute time.',
},
// ── AI Filter ──────────────────────────────────────
aiFilter: {
describeIdealArea: 'Describe your ideal area with AI',
aiSearch: 'AI Search',
describeHint: "describe what you're looking for",
placeholder: 'e.g. quiet area, under \u00A3400k, near good schools...',
example1: 'Safe area near good schools',
example2: '30 min commute to Kings Cross, under \u00A3500k',
example3: 'Quiet village, 3 bed, fast broadband',
analysing: 'Analysing your query...',
searchingDestinations: 'Searching for destinations...',
generatingFilters: 'Generating filters...',
refiningResults: 'Refining results...',
weeklyLimitReached: "You've reached the weekly AI usage limit. It will reset automatically next week.",
},
// ── Map Legend ─────────────────────────────────────
mapLegend: {
clearColourView: 'Clear colour view',
},
// ── Properties Pane ────────────────────────────────
propertyCard: {
unknownAddress: 'Unknown Address',
unsaveProperty: 'Unsave property',
saveProperty: 'Save property',
lastSold: 'Last sold: \u00A3{{price}}',
estValue: 'Est. value:',
type: 'Type:',
builtForm: 'Built form:',
tenure: 'Tenure:',
floorArea: 'Floor area:',
bedrooms: 'Bedrooms:',
bathrooms: 'Bathrooms:',
rooms: 'Rooms:',
built: 'Built:',
epcRating: 'EPC rating:',
epcPotential: 'EPC potential:',
listed: 'Listed:',
keyFeatures: 'Key features',
renovations: 'Renovations',
viewExternalListing: 'View external listing',
perMonth: '/mo',
perSqm: '/m\u00B2',
searchPlaceholder: 'Search by address or postcode...',
propertyData: 'Property Data',
propertyDataDesc: 'Prices come from HM Land Registry (what buyers actually paid). Floor area, energy ratings, construction year, and tenure come from official EPC surveys. Both sources are matched by address within each postcode.',
},
// ── Area Pane ──────────────────────────────────────
areaPane: {
areaStatistics: 'Area Statistics',
statsFor: 'Stats for all properties in this {{type}}',
matchingFilters: ' matching all active filters',
viewProperties: 'View {{count}} Properties',
priceHistory: 'Price History',
journeysFrom: 'Journeys from {{label}}',
to: 'To {{destination}}',
noJourneyData: 'No journey data available',
viewOnGoogleMaps: 'View on Google Maps',
walk: 'Walk',
cycle: 'Cycle',
},
// ── Histogram Legend ───────────────────────────────
histogramLegend: {
tealBars: 'Teal bars',
tealBarsDesc: 'show the distribution in this selected area',
greyBars: 'Grey bars',
greyBarsDesc: 'show the overall distribution across all areas',
dashedLine: 'Dashed line',
dashedLineDesc: 'indicates the national average',
},
// ── Street View ────────────────────────────────────
streetView: {
title: 'Street View',
},
// ── POI Pane ───────────────────────────────────────
poiPane: {
pois: 'POIs',
pointsOfInterest: 'Points of Interest',
poiDescription: 'Sourced from OpenStreetMap. Covers public transport stops, shops, restaurants, healthcare, leisure, and more. Updated regularly with complete category coverage.',
searchCategories: 'Search categories...',
dataSourceInfo: 'Data source info',
},
// ── External Search Links ──────────────────────────
externalSearch: {
searchOn: 'Search {{radius}} on',
outcodeNotRecognised: 'Outcode not recognised',
},
// ── Location Search ────────────────────────────────
locationSearch: {
placeholder: 'Search places or postcodes...',
postcodeNotFound: 'Postcode not found',
lookupFailed: 'Lookup failed',
searchLabel: 'Search places or postcodes',
locateMe: 'Go to my location',
geolocationUnsupported: 'Geolocation not supported by your browser',
geolocationFailed: 'Could not determine your location',
},
// ── Mobile Drawer ──────────────────────────────────
mobileDrawer: {
closeDrawer: 'Close drawer',
},
// ── Home Page ──────────────────────────────────────
home: {
heroTitle1: 'Maximum',
heroTitle2: 'Value',
heroTitle3: 'Minimum Compromise.',
heroSubtitle: 'House hunting? Make your biggest investment your smartest move.',
heroDescription: 'So many options - choosing the right one can feel overwhelming. Our interactive map makes it simple: select your must-haves and instantly see the areas that fit.',
exploreTheMap: 'Explore the map',
seeTheDifference: 'See the difference',
statProperties: 'properties',
statFilters: 'filters',
statEvery: 'Every',
statPostcodeInEngland: 'postcode in England',
ourPhilosophy: 'Our philosophy',
philosophyP1: "On Rightmove, you pick an area first, then hope it's good. You end up cross-referencing crime stats, school reports, and broadband checkers across a dozen tabs, one postcode at a time.",
philosophyP2: 'We flip that. Tell us what you need (budget, commute, schools, safety) and we show you every area in England that qualifies. No guesswork. No wasted viewings.',
howToUseIt: 'How to use it',
howStep1Title: 'Set your must-haves',
howStep1Desc: 'Budget, commute, schools \u2014 the map shows only what qualifies.',
howStep2Title: 'Explore areas and discover hidden gems',
howStep2Desc: 'Zoom in, dig into details and nice to haves.',
howStep3Title: 'Drill into postcodes',
howStep3Desc: 'See individual properties, sale prices, floor area, and compare.',
howStep4Title: 'Shortlist with confidence',
howStep4Desc: 'Every area on your list meets your actual criteria \u2014 not just what was listed that week.',
othersVs: 'Others vs',
listingPortals: 'Listing portals',
checkMyPostcode: '\u201CCheck my postcode\u201D',
areaGuides: 'Area guides',
compSearchWithout: 'Search without choosing an area first',
compSearchWithoutSub: '(start with needs, not a location)',
compAreaData: 'Area data',
compAreaDataSub: '(crime, schools, noise, broadband)',
compPropertyData: 'Property-specific data',
compPropertyDataSub: '(price, EPC, floor area)',
compFilters: '56 combinable filters in one place',
compFiltersSub: '(all insights, one interactive map)',
ctaTitle: 'Make your biggest investment your smartest\u00A0move.',
ctaDescription: "This deserves proper tools behind it, don't leave it to luck.",
},
// ── Pricing Page ───────────────────────────────────
pricingPage: {
title: 'Early access pricing',
subtitle: 'Pay once, access forever. The earlier you join, the less you pay.',
costContext: "Buying a home costs \u00A310k+ in stamp duty, \u00A31,500 in solicitor fees, \u00A3500 for a survey. Get the wrong area and you're stuck with a long commute, bad schools, or a road you didn't know about.",
lessThanSurvey: 'Less than a home survey. Far more useful.',
currentTier: 'Current tier',
firstNUsers: 'First {{count}} users',
everyoneAfter: 'Everyone after',
nextNUsers: 'Next {{count}} users',
lifetime: '/lifetime',
spotsRemaining: '{{count}} spot remaining',
spotsRemainingPlural: '{{count}} spots remaining',
filled: 'Filled',
openDashboard: 'Open dashboard',
getStarted: 'Get started',
getStartedPrice: 'Get started - {{price}}',
noCreditCard: 'No credit card required',
moneyBackGuarantee: '30-day money-back guarantee',
soldOut: 'Sold out',
upcoming: 'Upcoming',
failedToLoad: 'Failed to load pricing. Please try again later.',
feat1: '56 data layers across England',
feat2: 'Every postcode scored and filterable',
feat3: 'Unlimited map exploration and exports',
feat4: 'Multiple decades of historical price data',
feat5: 'Crime, schools, transport, broadband and more',
feat6: 'All future data updates included',
},
// ── Learn Page ─────────────────────────────────────
learnPage: {
faq: 'FAQ',
dataSources: 'Data Sources',
support: 'Support',
dataSourcesIntro: 'This application combines {{count}} open datasets covering property prices, energy performance, transport, demographics, crime, environment, and more.',
faqIntro: "Whether you're buying, renting, or just exploring, here's how Perfect Postcode helps you find the right area.",
supportIntro: 'Have a question? Check our FAQ or reach out to us directly.',
source: 'Source:',
optOut: 'Opt out of public disclosure',
attribution: 'Attribution',
attrLandRegistry: 'Contains HM Land Registry data \u00A9 Crown copyright and database right 2025.',
attrOgl: 'Contains public sector information licensed under the',
attrOglLink: 'Open Government Licence v3.0',
attrOs: 'Contains OS data \u00A9 Crown copyright and database rights 2025.',
attrTfl: 'Powered by TfL Open Data.',
attrOsm: 'Contains data from',
attrOsmContrib: '\u00A9 OpenStreetMap contributors',
attrOsmLicense: 'available under the',
attrOsmLicenseLink: 'Open Data Commons Open Database License (ODbL)',
// Data source names & descriptions
dsPricePaidName: 'Price Paid Data',
dsPricePaidOrigin: 'HM Land Registry',
dsPricePaidUse: 'Complete historical property sale prices for England.',
dsEpcName: 'Energy Performance Certificates (EPC)',
dsEpcOrigin: 'Ministry of Housing, Communities & Local Government',
dsEpcUse: 'Domestic Energy Performance Certificates providing floor area, number of rooms, construction year, energy ratings, property type, and built form. Matched with Price Paid records by address within each postcode. Property owners can opt out of public disclosure.',
dsNsplName: 'National Statistics Postcode Lookup (NSPL)',
dsNsplOrigin: 'ONS / ArcGIS',
dsNsplUse: 'Maps postcodes to coordinates and statistical area codes, used to link all area-level datasets to individual properties.',
dsIodName: 'English Indices of Deprivation 2025',
dsIodOrigin: 'Ministry of Housing, Communities & Local Government',
dsIodUse: 'Relative deprivation scores across income, employment, education, health, crime, and living environment for every neighbourhood in England.',
dsEthnicityName: 'Population by Ethnicity (2021 Census)',
dsEthnicityOrigin: 'ONS',
dsEthnicityUse: 'Population percentages by ethnic group (South Asian, East Asian, Black, Mixed, White, Other) per local authority.',
dsCrimeName: 'Street-level Crime Data',
dsCrimeOrigin: 'data.police.uk',
dsCrimeUse: 'Street-level crime data from 2023 to 2025, aggregated into yearly averages by LSOA and crime type (violence, burglary, anti-social behaviour, drugs, vehicle crime, etc.).',
dsOsmName: 'OpenStreetMap POIs',
dsOsmOrigin: 'OpenStreetMap contributors / Geofabrik',
dsOsmUse: 'Points of interest covering shops, restaurants, healthcare, leisure, tourism, and more across Great Britain.',
dsGreenspaceName: 'OS Open Greenspace',
dsGreenspaceOrigin: 'Ordnance Survey',
dsGreenspaceUse: 'Authoritative green space boundaries for Great Britain, including public parks, gardens, playing fields, and play spaces. Polygon centroids are used for park proximity counts and distance-to-nearest-park calculations.',
dsNaptanName: 'NaPTAN (Public Transport Stops)',
dsNaptanOrigin: 'Department for Transport',
dsNaptanUse: 'Station and stop locations for rail, bus, metro/tram, ferry, and airports across England.',
dsNoiseName: 'Defra Noise Mapping',
dsNoiseOrigin: 'Defra / Environment Agency',
dsNoiseUse: 'Road noise levels (24-hour weighted average) from the 2022 strategic noise mapping, modelled at high resolution and sampled at each postcode.',
dsOfstedName: 'Ofsted School Inspections',
dsOfstedOrigin: 'Ofsted',
dsOfstedUse: 'Latest inspection outcomes for state-funded schools (as at April 2025). Averaged per postcode to give a local school quality score (1=Outstanding to 4=Inadequate).',
dsBroadbandName: 'Ofcom Broadband Performance',
dsBroadbandOrigin: 'Ofcom',
dsBroadbandUse: 'Fixed broadband coverage and maximum download speeds by area from Ofcom Connected Nations 2025.',
dsCouncilTaxName: 'Council Tax Levels 2025-26',
dsCouncilTaxOrigin: 'Ministry of Housing, Communities & Local Government',
dsCouncilTaxUse: 'Annual council tax rates for Bands A-H for all 296 billing authorities in England, for a dwelling occupied by two adults. Joined to properties via local authority district code from the NSPL postcode lookup.',
dsRentalName: 'Private Rental Market Statistics',
dsRentalOrigin: 'ONS / Valuation Office Agency',
dsRentalUse: 'Median monthly private rental prices by local authority and bedroom category (Oct 2022 - Sep 2023). Joined to properties via local authority district code and estimated bedroom count.',
// FAQ section titles
faqFindingTitle: 'Finding Your Area',
faqCommuteTitle: 'Commute and Travel',
faqBudgetTitle: 'Budget and Value',
faqSafetyTitle: 'Safety and Neighbourhood',
faqFamiliesTitle: 'Families and Schools',
faqEnvironmentTitle: 'Environment and Quality of Life',
faqWhyTitle: 'Why Perfect Postcode',
faqPricingTitle: 'Pricing and Access',
faqTipsTitle: 'Tips and Tricks',
// FAQ items — Finding Your Area
faqFinding1Q: "I don't even know which areas to look at. Can this help?",
faqFinding1A: "That's exactly what it's for. Set your filters (budget, commute time, low crime, good schools) and the map lights up to show you every area that ticks every box. No more Googling \"best areas to live near Manchester\" at midnight.",
faqFinding2Q: "I'm moving somewhere I've never been. How do I even start?",
faqFinding2A: "Set your filters for what matters and the map instantly highlights the areas that qualify. You go from \"I don't know a single street\" to a shortlist in minutes.",
faqFinding3Q: 'How do I find areas that tick all my boxes at once?',
faqFinding3A: 'Stack multiple filters (crime below average, good schools, commute under 40 minutes) then colour the map by price to spot the best value areas. The map updates live as you drag sliders, so you can see results change in real time.',
// FAQ items — Commute and Travel
faqCommute1Q: 'Can I see how long my commute would actually be from different areas?',
faqCommute1A: "Set your workplace as a destination and we'll colour every postcode by journey time, whether that's by car, bike, or public transport. Filter to your max commute and the rest disappears.",
faqCommute2Q: 'How is that better than checking Google Maps?',
faqCommute2A: 'Google Maps shows you one journey at a time. We colour every postcode in England by commute time in one go, so you can compare hundreds of areas side by side instead of searching them one by one.',
// FAQ items — Budget and Value
faqBudget1Q: 'How do I find areas where I get the most space for my money?',
faqBudget1A: "Filter by price per sqm and you'll instantly see which postcodes give you the most space per pound. Pair it with the energy rating filter to avoid properties with high heating costs.",
faqBudget2Q: "How do I make sure a cheap area isn't cheap for a reason?",
faqBudget2A: "Layer deprivation scores, crime stats, school ratings, and broadband speeds alongside price. If a postcode is affordable and scores well on everything that matters, you've found genuine value, not just a low price with trade-offs you haven't spotted yet.",
// FAQ items — Safety and Neighbourhood
faqSafety1Q: 'How can I check if an area is safe before I move there?',
faqSafety1A: 'We overlay real police-recorded crime data, broken down by type, onto every neighbourhood in England. Filter by violent crime, burglary, or antisocial behaviour and instantly see which postcodes have the lowest numbers.',
faqSafety2Q: 'I keep finding flats that look great online, then the area turns out to be rough.',
faqSafety2A: "That's exactly why this exists. Stack crime rates, noise levels, deprivation scores, nearby pubs and parks, and broadband speeds all on one map so you know what a neighbourhood is actually like before you book a viewing.",
// FAQ items — Families and Schools
faqFamilies1Q: 'Can I find areas with good schools AND low crime in one search?',
faqFamilies1A: "Yes. Stack filters for Ofsted ratings, crime rates, parks, and whatever else matters to your family, and the map highlights only the areas that tick every box. No more cross-referencing five different websites.",
faqFamilies2Q: 'How do I know if a neighbourhood has parks and playgrounds nearby?',
faqFamilies2A: 'Toggle on the parks and green spaces POI layer to see them right on the map. You can also filter by how many are within walking distance of each postcode.',
// FAQ items — Environment and Quality of Life
faqEnv1Q: "Can I find energy-efficient homes that aren't on a noisy road?",
faqEnv1A: 'Filter by EPC rating (A to C), then layer on road noise data to rule out anything above your threshold. Colour-code by either feature to spot quiet, efficient streets at a glance.',
faqEnv2Q: 'Does it show flood or subsidence risk?',
faqEnv2A: "We include ground stability data so you can check for subsidence, shrink-swell clay, and other geological hazards before committing to a property. Filter out risky areas early.",
faqEnv3Q: 'Can I find areas with fast broadband that are actually quiet?',
faqEnv3A: 'Layer the broadband speed filter with road noise data to find streets with great connectivity and low traffic noise. Colour-code by either metric to compare areas at a glance.',
// FAQ items — Why Perfect Postcode
faqWhy1Q: 'I already use Rightmove. What does this add?',
faqWhy1A: "Rightmove shows you houses. We show you areas. Crime rates, school ratings, broadband speeds, noise levels, deprivation scores, and more, all filterable on one map. You can judge a neighbourhood before you even look at listings.",
faqWhy2Q: "Can't I just research all this myself for free?",
faqWhy2A: 'You could cross-reference police data, Ofsted reports, EPC registers, Land Registry records, and ONS statistics one postcode at a time. Or you could have it all filterable and colour-coded on one map in seconds.',
faqWhy3Q: 'Where does the data actually come from?',
faqWhy3A: "Every dataset comes from official UK government sources: Land Registry, the EPC register, ONS, Ofsted, Ofcom, data.police.uk, and Defra. We don't scrape estate agents or make anything up. You can verify any record against the original source.",
// FAQ items — Pricing and Access
faqPricing1Q: 'Is it really worth paying for a property search tool?',
faqPricing1A: "Buying a home is likely the biggest purchase you'll make. Spotting one red flag (a noisy road, poor broadband, rising crime) before committing could save you years of regret. This costs less than a tank of petrol.",
faqPricing2Q: 'Is this a subscription?',
faqPricing2A: "No. One-time payment, yours forever. Use it intensively during your search, come back whenever you're curious about a new area, and it's still there if you ever move again.",
faqPricing3Q: 'What can I access on the free tier?',
faqPricing3A: 'Free users can explore all features within the demo area (inner London, roughly zones 1 to 2). To access data for the rest of England, you need lifetime access.',
faqPricing4Q: 'Can I get a refund?',
faqPricing4A: 'Absolutely. We offer a 30-day money-back guarantee. If you\u2019re not satisfied, email support@perfect-postcode.co.uk within 30 days for a full refund.',
// FAQ items — Tips and Tricks
faqTips1Q: 'How do I use the AI filter instead of adding filters one by one?',
faqTips1A: 'Type what you want in plain English, something like "quiet area near good schools with fast broadband under \u00A3400k", and it\'ll set up all the relevant filters in one go. Tweak any of them manually afterwards.',
faqTips2Q: 'Can I save a search and come back to it later?',
faqTips2A: 'Hit the save button and everything is captured: your filters, zoom level, and which data layer you\u2019re colouring by. Pick up exactly where you left off or share the link with your partner.',
faqTips3Q: "Can I export the data I'm looking at?",
faqTips3A: 'Use the export button to download the currently filtered properties as a spreadsheet. The export respects all your active filters, so you get exactly the data you want.',
},
// ── Account Page ───────────────────────────────────
accountPage: {
emailLabel: 'Email',
subscriptionLabel: 'Subscription',
upgrade: 'Upgrade',
redirecting: 'Redirecting\u2026',
receiveNewsletter: 'Receive newsletter emails',
needHelp: 'Need help? Email us at',
responseTime: 'We typically respond within 24 hours.',
},
// ── Saved Page ─────────────────────────────────────
savedPage: {
searches: 'Searches',
noSavedSearches: 'No saved searches yet',
noSavedSearchesDesc: 'Save your filters and map view so you can pick up exactly where you left off.',
noSavedProperties: 'No saved properties yet',
noSavedPropertiesDesc: 'Bookmark properties as you explore and build your shortlist without losing track.',
openPostcode: 'Open postcode',
viewListing: 'View listing',
clickToRename: 'Click to rename',
notesPlaceholder: 'Jot down your thoughts...',
deleteSearch: 'Delete search',
deleteSearchConfirm: 'Are you sure you want to delete this saved search? This cannot be undone.',
deleteProperty: 'Delete property',
deletePropertyConfirm: 'Are you sure you want to delete this saved property? This cannot be undone.',
bed: 'bed',
epc: 'EPC',
},
// ── Invites Page ───────────────────────────────────
invitesPage: {
inviteLinksLicensed: 'Invite links are available for licensed users.',
inviteAdminLabel: 'Invite friends (100% off)',
inviteReferralLabel: 'Invite friends (30% off)',
generateFreeInvite: 'Generate free invite link',
generateReferralLink: 'Generate referral link',
copyInviteLink: 'Copy invite link',
adminInvitesTitle: 'Admin invites (100% off)',
referralInvitesTitle: 'Referral invites (30% off)',
yourInviteLinks: 'Your invite links',
noInvitesYet: 'No invites generated yet',
link: 'Link',
status: 'Status',
created: 'Created',
redeemed: 'Redeemed',
pending: 'Pending',
},
// ── Invite Page ────────────────────────────────────
invitePage: {
youreInvited: "You're invited!",
specialOffer: 'Special offer!',
invitedByFree: '{{name}} has invited you to get free lifetime access.',
invitedByDiscount: '{{name}} has shared a 30% discount on lifetime access.',
genericFreeInvite: 'You have been invited to get free lifetime access.',
genericDiscount: 'A friend has shared a 30% discount on lifetime access.',
exploreEvery: 'Explore every neighbourhood in England',
propertyInfo: 'Property prices, energy ratings, crime stats, school ratings and more',
invalidInvite: 'Invalid invite',
inviteAlreadyUsed: 'Invite already used',
inviteAlreadyUsedDesc: 'This invite link has already been redeemed.',
invalidInviteLink: 'Invalid invite link',
invalidInviteLinkDesc: 'This invite link is invalid or has expired.',
licenseActivated: 'License activated!',
fullAccessGranted: 'You now have full access to Perfect Postcode.',
activating: 'Activating...',
activateLicense: 'Activate license',
claimDiscount: 'Claim discount',
registerToClaim: 'Register to claim',
youAlreadyHaveLicense: 'You already have a license',
accountHasFullAccess: 'Your account already has full access.',
failedToValidate: 'Failed to validate invite link',
},
// ── Map Page ───────────────────────────────────────
mapPage: {
unsavedProperty: 'Unsave',
savedProperty: 'Saved',
},
// ── Format / Time ──────────────────────────────────
format: {
justNow: 'just now',
minutesAgo: '{{count}}m ago',
hoursAgo: '{{count}}h ago',
daysAgo: '{{count}}d ago',
nFilters: '{{count}} filters',
noFilters: 'No filters',
poiCategory: '{{count}} POI category',
poiCategories: '{{count}} POI categories',
travelDestination: '{{count}} travel time destination',
travelDestinations: '{{count}} travel time destinations',
propertiesMatch: '{{count}} properties match',
setFilters: 'Set {{count}} filter(s): {{list}}',
noFiltersSet: 'No filters set',
toDestination: '{{mode}} to {{label}} {{bounds}}',
lessThanMin: '< {{max}} min',
moreThanMin: '> {{min}} min',
},
// ── Tutorial ──────────────────────────────────────
tutorial: {
step1Title: 'Tell the map what matters',
step1Content: 'Set your budget, commute limit, school quality, crime threshold. Whatever matters to you. Only areas that qualify stay lit. Use the eye icon to colour by any feature.',
step2Title: 'Or just describe it',
step2Content: 'Type what you want in plain English, like "quiet area near good schools under \u00A3400k", and we\u2019ll set up the filters for you.',
step3Title: 'Explore what\u2019s out there',
step3Content: 'Pan and zoom across England. Click any coloured area to see crime, schools, prices, broadband, noise, and more about that neighbourhood.',
step4Title: 'Jump to a location',
step4Content: 'Search for any place or postcode to fly straight there.',
step5Title: 'Dig into the details',
step5Content: 'See area statistics, histograms, and individual property records: prices, floor area, energy ratings, and more.',
step6Title: 'What\u2019s nearby?',
step6Content: 'Toggle schools, shops, stations, parks, and restaurants on the map to see what\u2019s within reach.',
},
// ── Server-derived values ──────────────────────────
// Keyed by the English server value. ts() looks up translations at display time.
// The English keys MUST match exactly what the API returns.
server: {
// ─ Feature group names ─
'Properties': 'Properties',
'Transport': 'Transport',
'Education': 'Education',
'Deprivation': 'Deprivation',
'Crime': 'Crime',
'Demographics': 'Demographics',
'Amenities': 'Amenities',
// ─ Feature names (Properties) ─
'Listing status': 'Listing status',
'Property type': 'Property type',
'Leasehold/Freehold': 'Leasehold/Freehold',
'Last known price': 'Last known price',
'Estimated current price': 'Estimated current price',
'Asking price': 'Asking price',
'Price per sqm': 'Price per sqm',
'Est. price per sqm': 'Est. price per sqm',
'Asking price per sqm': 'Asking price per sqm',
'Estimated monthly rent': 'Estimated monthly rent',
'Asking rent (monthly)': 'Asking rent (monthly)',
'Total floor area (sqm)': 'Total floor area (sqm)',
'Number of bedrooms & living rooms': 'Number of bedrooms & living rooms',
'Bedrooms': 'Bedrooms',
'Bathrooms': 'Bathrooms',
'Construction year': 'Construction year',
'Date of last transaction': 'Date of last transaction',
'Listing date': 'Listing date',
'Former council house': 'Former council house',
'Current energy rating': 'Current energy rating',
'Potential energy rating': 'Potential energy rating',
'Interior height (m)': 'Interior height (m)',
// ─ Feature names (Transport) ─
'Distance to nearest train or tube station (km)': 'Distance to nearest train or tube station (km)',
'Train or tube stations within 1km': 'Train or tube stations within 1km',
// ─ Feature names (Education) ─
'Good+ primary schools within 2km': 'Good+ primary schools within 2km',
'Good+ secondary schools within 2km': 'Good+ secondary schools within 2km',
'Good+ primary schools within 5km': 'Good+ primary schools within 5km',
'Good+ secondary schools within 5km': 'Good+ secondary schools within 5km',
'Education, Skills and Training Score': 'Education, Skills and Training Score',
// ─ Feature names (Deprivation) ─
'Income Score (rate)': 'Income Score (rate)',
'Employment Score (rate)': 'Employment Score (rate)',
'Health Deprivation and Disability Score': 'Health Deprivation and Disability Score',
'Living Environment Score': 'Living Environment Score',
'Indoors Sub-domain Score': 'Indoors Sub-domain Score',
'Outdoors Sub-domain Score': 'Outdoors Sub-domain Score',
// ─ Feature names (Crime) ─
'Serious crime per 1k residents (avg/yr)': 'Serious crime per 1k residents (avg/yr)',
'Minor crime per 1k residents (avg/yr)': 'Minor crime per 1k residents (avg/yr)',
'Serious crime (avg/yr)': 'Serious crime (avg/yr)',
'Minor crime (avg/yr)': 'Minor crime (avg/yr)',
'Violence and sexual offences (avg/yr)': 'Violence and sexual offences (avg/yr)',
'Burglary (avg/yr)': 'Burglary (avg/yr)',
'Robbery (avg/yr)': 'Robbery (avg/yr)',
'Vehicle crime (avg/yr)': 'Vehicle crime (avg/yr)',
'Anti-social behaviour (avg/yr)': 'Anti-social behaviour (avg/yr)',
'Criminal damage and arson (avg/yr)': 'Criminal damage and arson (avg/yr)',
'Other theft (avg/yr)': 'Other theft (avg/yr)',
'Theft from the person (avg/yr)': 'Theft from the person (avg/yr)',
'Shoplifting (avg/yr)': 'Shoplifting (avg/yr)',
'Bicycle theft (avg/yr)': 'Bicycle theft (avg/yr)',
'Drugs (avg/yr)': 'Drugs (avg/yr)',
'Possession of weapons (avg/yr)': 'Possession of weapons (avg/yr)',
'Public order (avg/yr)': 'Public order (avg/yr)',
'Other crime (avg/yr)': 'Other crime (avg/yr)',
// ─ Feature names (Demographics) ─
'Median age': 'Median age',
'% White': '% White',
'% South Asian': '% South Asian',
'% Black': '% Black',
'% East Asian': '% East Asian',
'% Mixed': '% Mixed',
'% Other': '% Other',
// ─ Feature names (Amenities) ─
'Distance to nearest park (km)': 'Distance to nearest park (km)',
'Number of parks within 2km': 'Number of parks within 2km',
'Number of restaurants within 2km': 'Number of restaurants within 2km',
'Number of grocery shops and supermarkets within 2km': 'Number of grocery shops and supermarkets within 2km',
'Noise (dB)': 'Noise (dB)',
'Max available download speed (Mbps)': 'Max available download speed (Mbps)',
// ─ Enum values ─
'Historical sale': 'Historical sale',
'For sale': 'For sale',
'For rent': 'For rent',
'Detached': 'Detached',
'Semi-Detached': 'Semi-Detached',
'Terraced': 'Terraced',
'Flats/Maisonettes': 'Flats/Maisonettes',
'Other': 'Other',
'Freehold': 'Freehold',
'Leasehold': 'Leasehold',
'Yes': 'Yes',
'No': 'No',
// ─ Stacked chart labels ─
'Serious crime': 'Serious crime',
'Minor crime': 'Minor crime',
'Ethnic composition': 'Ethnic composition',
// ─ POI group names ─
'Public Transport': 'Public Transport',
'Leisure': 'Leisure',
'Health': 'Health',
'Emergency Services': 'Emergency Services',
'Groceries': 'Groceries',
'Local Businesses': 'Local Businesses',
'Culture': 'Culture',
'Services': 'Services',
'Shops': 'Shops',
// ─ POI categories ─
'Airport': 'Airport',
'Ferry': 'Ferry',
'Rail station': 'Rail station',
'Bus stop': 'Bus stop',
'Bus station': 'Bus station',
'Taxi rank': 'Taxi rank',
'Metro or Tram stop': 'Metro or Tram stop',
'Caf\u00E9': 'Caf\u00E9',
'Restaurant': 'Restaurant',
'Pub': 'Pub',
'Bar': 'Bar',
'Fast Food': 'Fast Food',
'Nightclub': 'Nightclub',
'Cinema': 'Cinema',
'Theatre': 'Theatre',
'Live Music & Events': 'Live Music & Events',
'Park': 'Park',
'Playground': 'Playground',
'Sports Centre': 'Sports Centre',
'Entertainment': 'Entertainment',
'Supermarket': 'Supermarket',
'Convenience Store': 'Convenience Store',
'Bakery': 'Bakery',
'Butcher & Fishmonger': 'Butcher & Fishmonger',
'Greengrocer': 'Greengrocer',
'Off-Licence': 'Off-Licence',
'Deli & Specialty': 'Deli & Specialty',
'Fashion & Clothing': 'Fashion & Clothing',
'Electronics': 'Electronics',
'Charity Shop': 'Charity Shop',
'DIY & Hardware': 'DIY & Hardware',
'Home & Garden': 'Home & Garden',
'Bookshop': 'Bookshop',
'Pet Shop': 'Pet Shop',
'Sports & Outdoor': 'Sports & Outdoor',
'Newsagent': 'Newsagent',
'Department Store': 'Department Store',
'Gift & Hobby': 'Gift & Hobby',
'Specialist Shop': 'Specialist Shop',
'Hairdresser & Beauty': 'Hairdresser & Beauty',
'Gym & Fitness': 'Gym & Fitness',
'Dry Cleaner & Laundry': 'Dry Cleaner & Laundry',
'Car Services': 'Car Services',
'Post Office': 'Post Office',
'Vet & Pet Care': 'Vet & Pet Care',
'Bank': 'Bank',
'Travel Agent': 'Travel Agent',
'Police': 'Police',
'Fire Station': 'Fire Station',
'Ambulance Station': 'Ambulance Station',
'GP Surgery': 'GP Surgery',
'Dentist': 'Dentist',
'Pharmacy': 'Pharmacy',
'Hospital & Clinic': 'Hospital & Clinic',
'Optician': 'Optician',
'Physiotherapy': 'Physiotherapy',
'Counselling & Therapy': 'Counselling & Therapy',
'Care Home': 'Care Home',
'Medical & Mobility': 'Medical & Mobility',
'Museum': 'Museum',
'Gallery': 'Gallery',
'Library': 'Library',
'Place of Worship': 'Place of Worship',
'Arts Centre': 'Arts Centre',
'Zoo': 'Zoo',
'Tourist Attraction': 'Tourist Attraction',
'School': 'School',
'Hotel': 'Hotel',
'Local Business': 'Local Business',
'Offices': 'Offices',
'EV Charging': 'EV Charging',
'Fuel Station': 'Fuel Station',
'Community Centre': 'Community Centre',
// ─ Suffixes (used in formatters) ─
'/mo': '/mo',
'/yr': '/yr',
' sqm': ' sqm',
' km': ' km',
' m': ' m',
' dB': ' dB',
' years': ' years',
' rooms': ' rooms',
},
} as const;
export default en;
/**
* Recursively maps a translation object's leaf values to `string`,
* preserving the nested key structure. Used to type-check non-English
* locale files: they must have exactly the same keys as English,
* but with their own string values.
*
* Missing key -> TS error. Extra key -> TS error.
*/
export type DeepStringify<T> = {
[K in keyof T]: T[K] extends Record<string, unknown> ? DeepStringify<T[K]> : string;
};
export type Translations = DeepStringify<typeof en>;