diff --git a/frontend/public/video/poster.jpg b/frontend/public/video/poster.jpg new file mode 100644 index 0000000..b759748 Binary files /dev/null and b/frontend/public/video/poster.jpg differ diff --git a/frontend/public/video/recording.mp4 b/frontend/public/video/recording.mp4 new file mode 100644 index 0000000..2afa0ce Binary files /dev/null and b/frontend/public/video/recording.mp4 differ diff --git a/frontend/src/hooks/useMapData.ts b/frontend/src/hooks/useMapData.ts index 687e3a0..3d36b38 100644 --- a/frontend/src/hooks/useMapData.ts +++ b/frontend/src/hooks/useMapData.ts @@ -492,6 +492,32 @@ export function useMapData({ return liveColorRange; }, [dataViewFeature, frozenPreviewRange, isEyePreviewingPinnedFeature, liveColorRange]); + const canResetPreviewScale = useMemo(() => { + if ( + !isEyePreviewingPinnedFeature || + !pinnedDataViewFeature || + !liveColorRange || + loadedDataKey !== dataRequestKey + ) { + return false; + } + + if (pinnedDataViewFeature.startsWith('tt_')) return true; + return features.find((f) => f.name === pinnedDataViewFeature)?.type !== 'enum'; + }, [ + dataRequestKey, + features, + isEyePreviewingPinnedFeature, + liveColorRange, + loadedDataKey, + pinnedDataViewFeature, + ]); + + const handleResetPreviewScale = useCallback(() => { + if (!canResetPreviewScale || !pinnedDataViewFeature || !liveColorRange) return; + setFrozenPreviewRange({ feature: pinnedDataViewFeature, range: liveColorRange }); + }, [canResetPreviewScale, liveColorRange, pinnedDataViewFeature]); + const handleViewChange = useCallback( ({ resolution: newRes, @@ -530,6 +556,8 @@ export function useMapData({ currentView, usePostcodeView, colorRange, + canResetPreviewScale, + handleResetPreviewScale, handleViewChange, setInitialView, licenseRequired, diff --git a/frontend/src/i18n/descriptions.ts b/frontend/src/i18n/descriptions.ts index d55fab4..5fc65c1 100644 --- a/frontend/src/i18n/descriptions.ts +++ b/frontend/src/i18n/descriptions.ts @@ -1,4 +1,5 @@ import i18n from 'i18next'; +import { details } from './details'; /** * Feature description translations, keyed by feature name. @@ -268,6 +269,80 @@ const descriptions: Record> = { 'Noise (dB)': '该邮编的道路噪音水平(分贝,Lden)', 'Max available download speed (Mbps)': '该邮编可用的最大宽带下载速度', }, + hi: { + 'Property type': 'संपत्ति प्रकार: अलग, अर्ध-स्वतंत्र, कतारबद्ध, फ्लैट या अन्य', + 'Leasehold/Freehold': 'बताता है कि संपत्ति लीजहोल्ड है या फ्रीहोल्ड', + 'Last known price': 'Land Registry में दर्ज अंतिम बिक्री कीमत', + 'Estimated current price': 'महंगाई और स्थानीय कीमत बदलाव से समायोजित मौजूदा अनुमानित मूल्य', + 'Price per sqm': 'बिक्री कीमत को कुल फर्श क्षेत्र से विभाजित किया गया', + 'Est. price per sqm': 'मौजूदा अनुमानित कीमत को कुल फर्श क्षेत्र से विभाजित किया गया', + 'Estimated monthly rent': 'क्षेत्र का औसत निजी मासिक किराया', + 'Total floor area (sqm)': 'EPC सर्वेक्षण से लिया गया आंतरिक फर्श क्षेत्र', + 'Number of bedrooms & living rooms': 'EPC सर्वेक्षण के अनुसार रहने योग्य कमरों की संख्या', + 'Construction year': 'EPC के अनुसार अनुमानित निर्माण वर्ष', + 'Date of last transaction': 'Land Registry में दर्ज अंतिम बिक्री की तारीख', + 'Former council house': 'बताता है कि संपत्ति कभी सामाजिक आवास के रूप में दर्ज थी या नहीं', + 'Current energy rating': 'मौजूदा EPC ऊर्जा रेटिंग (A = सबसे अच्छी, G = सबसे खराब)', + 'Potential energy rating': 'सभी सुझाए गए सुधार होने पर संभावित EPC रेटिंग', + 'Interior height (m)': 'EPC सर्वेक्षण के अनुसार औसत अंदरूनी ऊंचाई', + 'Distance to nearest train or tube station (km)': 'निकटतम ट्रेन या ट्यूब स्टेशन तक दूरी', + 'Good+ primary schools within 2km': '2 किमी के भीतर Ofsted Good या Outstanding प्राइमरी स्कूल', + 'Good+ secondary schools within 2km': + '2 किमी के भीतर Ofsted Good या Outstanding सेकेंडरी स्कूल', + 'Good+ primary schools within 5km': '5 किमी के भीतर Ofsted Good या Outstanding प्राइमरी स्कूल', + 'Good+ secondary schools within 5km': + '5 किमी के भीतर Ofsted Good या Outstanding सेकेंडरी स्कूल', + 'Outstanding primary schools within 2km': '2 किमी के भीतर Ofsted Outstanding प्राइमरी स्कूल', + 'Outstanding secondary schools within 2km': '2 किमी के भीतर Ofsted Outstanding सेकेंडरी स्कूल', + 'Outstanding primary schools within 5km': '5 किमी के भीतर Ofsted Outstanding प्राइमरी स्कूल', + 'Outstanding secondary schools within 5km': '5 किमी के भीतर Ofsted Outstanding सेकेंडरी स्कूल', + 'Education, Skills and Training Score': 'स्थानीय शिक्षा गुणवत्ता स्कोर (अधिक = बेहतर)', + 'Income Score (rate)': 'आय वंचना दर, उलटी की गई (अधिक = कम वंचना)', + 'Employment Score (rate)': 'रोजगार वंचना दर, उलटी की गई (अधिक = कम वंचना)', + 'Health Deprivation and Disability Score': 'स्वास्थ्य और विकलांगता स्कोर (अधिक = बेहतर परिणाम)', + 'Living Environment Score': 'घर और बाहरी वातावरण की गुणवत्ता (अधिक = बेहतर)', + 'Indoors Sub-domain Score': 'आवास गुणवत्ता और स्थिति (अधिक = बेहतर)', + 'Outdoors Sub-domain Score': 'हवा की गुणवत्ता और सड़क सुरक्षा (अधिक = बेहतर)', + 'Serious crime per 1k residents (avg/yr)': 'प्रति 1,000 निवासियों सालाना गंभीर अपराध दर', + 'Minor crime per 1k residents (avg/yr)': 'प्रति 1,000 निवासियों सालाना मामूली अपराध दर', + 'Serious crime (avg/yr)': 'गंभीर अपराध श्रेणियों का सालाना कुल', + 'Minor crime (avg/yr)': 'मामूली अपराध श्रेणियों का सालाना कुल', + 'Violence and sexual offences (avg/yr)': 'क्षेत्र में हिंसा और यौन अपराधों का सालाना औसत', + 'Burglary (avg/yr)': 'क्षेत्र में सेंधमारी का सालाना औसत', + 'Robbery (avg/yr)': 'क्षेत्र में लूट का सालाना औसत', + 'Vehicle crime (avg/yr)': 'क्षेत्र में वाहन अपराधों का सालाना औसत', + 'Anti-social behaviour (avg/yr)': 'क्षेत्र में असामाजिक व्यवहार का सालाना औसत', + 'Criminal damage and arson (avg/yr)': 'क्षेत्र में आपराधिक क्षति और आगजनी का सालाना औसत', + 'Other theft (avg/yr)': 'क्षेत्र में अन्य चोरी का सालाना औसत', + 'Theft from the person (avg/yr)': 'क्षेत्र में व्यक्ति से चोरी का सालाना औसत', + 'Shoplifting (avg/yr)': 'क्षेत्र में दुकान से चोरी का सालाना औसत', + 'Bicycle theft (avg/yr)': 'क्षेत्र में साइकिल चोरी का सालाना औसत', + 'Drugs (avg/yr)': 'क्षेत्र में ड्रग अपराधों का सालाना औसत', + 'Possession of weapons (avg/yr)': 'क्षेत्र में हथियार रखने के अपराधों का सालाना औसत', + 'Public order (avg/yr)': 'क्षेत्र में सार्वजनिक व्यवस्था अपराधों का सालाना औसत', + 'Other crime (avg/yr)': 'क्षेत्र में अन्य अपराधों का सालाना औसत', + 'Median age': 'स्थानीय आबादी की मीडियन आयु', + '% White': 'श्वेत के रूप में पहचान करने वाली आबादी का प्रतिशत', + '% South Asian': 'दक्षिण एशियाई के रूप में पहचान करने वाली आबादी का प्रतिशत', + '% Black': 'अश्वेत के रूप में पहचान करने वाली आबादी का प्रतिशत', + '% East Asian': 'पूर्वी एशियाई के रूप में पहचान करने वाली आबादी का प्रतिशत', + '% Mixed': 'मिश्रित या कई जातीय समूहों से पहचान करने वाली आबादी का प्रतिशत', + '% Other': 'अन्य जातीय समूह के रूप में पहचान करने वाली आबादी का प्रतिशत', + 'Voter turnout (%)': '2024 आम चुनाव में मतदान करने वाले पंजीकृत मतदाताओं का प्रतिशत', + '% Labour': '2024 आम चुनाव में लेबर का मत-प्रतिशत', + '% Conservative': '2024 आम चुनाव में कंज़र्वेटिव का मत-प्रतिशत', + '% Liberal Democrat': '2024 आम चुनाव में लिबरल डेमोक्रेट का मत-प्रतिशत', + '% Reform UK': '2024 आम चुनाव में Reform UK का मत-प्रतिशत', + '% Green': '2024 आम चुनाव में ग्रीन पार्टी का मत-प्रतिशत', + '% Other parties': 'बाकी सभी पार्टियों और निर्दलीयों का संयुक्त मत-प्रतिशत', + 'Distance to nearest park (km)': 'निकटतम पार्क या हरित क्षेत्र तक दूरी', + 'Number of parks within 1km': '1 किमी के भीतर पार्कों और हरित क्षेत्रों की संख्या', + 'Number of restaurants within 2km': '2 किमी के भीतर रेस्तरां और कैफे की संख्या', + 'Number of grocery shops and supermarkets within 2km': + '2 किमी के भीतर किराना दुकानों और सुपरमार्केट की संख्या', + 'Noise (dB)': 'पोस्टकोड पर सड़क शोर स्तर, डेसीबल (Lden) में', + 'Max available download speed (Mbps)': 'पोस्टकोड पर उपलब्ध अधिकतम डाउनलोड स्पीड', + }, hu: { 'Property type': 'Ingatlantípus: különálló, ikerház, sorház, lakás vagy egyéb', 'Leasehold/Freehold': 'Az ingatlan bérleti jogú vagy teljes tulajdonú', @@ -377,5 +452,5 @@ export function tsDesc(featureName: string, englishFromServer: string): string { export function tsDetail(featureName: string, englishFromServer: string): string { const lang = i18n.language; if (lang === 'en') return englishFromServer; - return descriptions[lang]?.[featureName] ?? englishFromServer; + return details[lang]?.[featureName] ?? englishFromServer; } diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts index 05e5e12..4318d1e 100644 --- a/frontend/src/i18n/index.ts +++ b/frontend/src/i18n/index.ts @@ -1,56 +1,124 @@ import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import en from './locales/en'; -import de from './locales/de'; -import fr from './locales/fr'; -import hu from './locales/hu'; -import zh from './locales/zh'; export const SUPPORTED_LANGUAGES = [ { code: 'en', label: 'English', flag: '\uD83C\uDDEC\uD83C\uDDE7' }, { code: 'fr', label: 'Fran\u00E7ais', flag: '\uD83C\uDDEB\uD83C\uDDF7' }, { code: 'de', label: 'Deutsch', flag: '\uD83C\uDDE9\uD83C\uDDEA' }, - { code: 'hu', label: 'Magyar', flag: '\uD83C\uDDED\uD83C\uDDFA' }, { code: 'zh', label: '\u4E2D\u6587', flag: '\uD83C\uDDE8\uD83C\uDDF3' }, + { code: 'hi', label: '\u0939\u093F\u0928\u094D\u0926\u0940', flag: '\uD83C\uDDEE\uD83C\uDDF3' }, + { code: 'hu', label: 'Magyar', flag: '\uD83C\uDDED\uD83C\uDDFA' }, ] as const; export type LanguageCode = (typeof SUPPORTED_LANGUAGES)[number]['code']; const supportedCodes: Set = new Set(SUPPORTED_LANGUAGES.map((l) => l.code)); -function detectLanguage(): string { +function toSupportedLanguage(value: string): LanguageCode | null { + const lower = value.toLowerCase(); + if (supportedCodes.has(lower)) return lower as LanguageCode; + + const prefix = lower.split('-')[0]; + if (supportedCodes.has(prefix)) return prefix as LanguageCode; + + return null; +} + +function getStoredLanguage(): LanguageCode | null { + try { + const stored = localStorage.getItem('language'); + return stored ? toSupportedLanguage(stored) : null; + } catch { + return null; + } +} + +function getBrowserLanguages(): readonly string[] { + if (typeof navigator === 'undefined') return []; + return navigator.languages?.length ? navigator.languages : [navigator.language]; +} + +function detectLanguage(): LanguageCode { // 1. Explicit user choice (persisted from the language dropdown) - const stored = localStorage.getItem('language'); - if (stored && supportedCodes.has(stored)) return stored; + const stored = getStoredLanguage(); + if (stored) return stored; // 2. Browser preference (navigator.languages falls back to navigator.language) - for (const tag of navigator.languages ?? [navigator.language]) { - // Match full tag first (e.g. "zh-CN" → "zh"), then just the prefix - const lower = tag.toLowerCase(); - if (supportedCodes.has(lower)) return lower; - const prefix = lower.split('-')[0]; - if (supportedCodes.has(prefix)) return prefix; + for (const tag of getBrowserLanguages()) { + const language = toSupportedLanguage(tag); + if (language) return language; } return 'en'; } -const initialLang = detectLanguage(); +export const INITIAL_LANGUAGE = detectLanguage(); -i18n.use(initReactI18next).init({ - resources: { +type TranslationResource = Record; + +const localeLoaders: Record< + Exclude, + () => Promise<{ default: TranslationResource }> +> = { + fr: () => import('./locales/fr'), + de: () => import('./locales/de'), + hu: () => import('./locales/hu'), + zh: () => import('./locales/zh'), + hi: () => import('./locales/hi'), +}; + +async function getLanguageResource( + code: Exclude +): Promise { + const module = await localeLoaders[code](); + return module.default; +} + +function setDocumentLanguage(code: LanguageCode) { + if (typeof document !== 'undefined') { + document.documentElement.lang = code; + } +} + +export async function loadLanguage(code: LanguageCode): Promise { + if (code === 'en' || i18n.hasResourceBundle(code, 'translation')) return; + const resource = await getLanguageResource(code); + i18n.addResourceBundle(code, 'translation', resource, true, true); +} + +export async function changeLanguage(code: LanguageCode): Promise { + await loadLanguage(code); + await i18n.changeLanguage(code); + setDocumentLanguage(code); +} + +export const i18nReady = (async () => { + const resources: Record = { en: { translation: en }, - fr: { translation: fr }, - de: { translation: de }, - hu: { translation: hu }, - zh: { translation: zh }, - }, - lng: initialLang, - fallbackLng: 'en', - interpolation: { - escapeValue: false, // React already escapes - }, -}); + }; + let language = INITIAL_LANGUAGE; + + if (language !== 'en') { + try { + resources[language] = { translation: await getLanguageResource(language) }; + } catch (error) { + console.error(`Failed to load ${language} translations`, error); + language = 'en'; + } + } + + await i18n.use(initReactI18next).init({ + resources, + lng: language, + fallbackLng: 'en', + interpolation: { + escapeValue: false, // React already escapes + }, + }); + + setDocumentLanguage(language); +})(); /** * Translate a key that is computed at runtime (not a literal). diff --git a/frontend/src/i18n/locales/hi.ts b/frontend/src/i18n/locales/hi.ts new file mode 100644 index 0000000..d202e72 --- /dev/null +++ b/frontend/src/i18n/locales/hi.ts @@ -0,0 +1,914 @@ +import type { Translations } from './en'; + +const hi: Translations = { + common: { + save: 'सहेजें', + cancel: 'रद्द करें', + close: 'बंद करें', + delete: 'हटाएं', + open: 'खोलें', + share: 'साझा करें', + copy: 'कॉपी करें', + copied: 'कॉपी हो गया!', + copiedToClipboard: 'क्लिपबोर्ड पर कॉपी किया गया', + loading: 'लोड हो रहा है...', + loadMore: 'और लोड करें', + remaining: '{{count}} बाकी', + search: 'खोजें', + all: 'सभी', + none: 'कोई नहीं', + viewDataSource: 'डेटा स्रोत देखें', + total: 'कुल', + min: 'मिनट', + or: 'या', + area: 'क्षेत्र', + properties: 'संपत्तियां', + postcode: 'पोस्टकोड', + noAreaSelected: 'कोई क्षेत्र चुना नहीं गया', + noAreaSelectedDesc: + 'अपराध, स्कूल, कीमतें और अधिक देखने के लिए मानचित्र पर किसी भी रंगीन क्षेत्र पर क्लिक करें', + clickForDetails: 'विवरण के लिए क्लिक करें', + property: 'संपत्ति', + propertiesPlural: 'संपत्तियां', + }, + + header: { + appName: 'Perfect Postcode', + dashboard: 'डैशबोर्ड', + learn: 'जानें', + pricing: 'कीमतें', + inviteFriends: 'दोस्तों को आमंत्रित करें', + saved: 'सहेजे गए', + logIn: 'लॉग इन', + createAccount: 'खाता बनाएं', + sharing: 'साझा किया जा रहा है...', + exportLabel: 'निर्यात', + exporting: 'निर्यात हो रहा है...', + exportToExcel: 'Excel में निर्यात करें', + openMenu: 'मेनू खोलें', + closeMenu: 'मेनू बंद करें', + }, + + userMenu: { + fullAccess: 'पूर्ण एक्सेस', + demo: 'डेमो', + themeLight: 'थीम: हल्की', + themeDark: 'थीम: गहरी', + account: 'खाता', + logOut: 'लॉग आउट', + }, + + mobileMenu: { + menu: 'मेनू', + home: 'होम', + }, + + auth: { + logIn: 'लॉग इन', + createAccount: 'खाता बनाएं', + resetPassword: 'पासवर्ड रीसेट करें', + valueProp: + 'खोजें सहेजें, संपत्तियों को बुकमार्क करें और अपनी जरूरतों से मेल खाने वाले क्षेत्रों की शॉर्टलिस्ट बनाएं.', + continueWithGoogle: 'Google से जारी रखें', + email: 'ईमेल', + emailPlaceholder: 'you@example.com', + password: 'पासवर्ड', + passwordPlaceholderRegister: 'कम से कम 8 अक्षर', + passwordPlaceholderLogin: 'आपका पासवर्ड', + forgotPassword: 'पासवर्ड भूल गए?', + resetSent: 'रीसेट लिंक के लिए अपना ईमेल देखें.', + pleaseWait: 'कृपया प्रतीक्षा करें...', + sendResetLink: 'रीसेट लिंक भेजें', + backToLogin: 'लॉग इन पर वापस जाएं', + }, + + upgrade: { + title: 'हर मेल खाने वाला पोस्टकोड खोजें', + description: + 'आप अभी डेमो क्षेत्र देख रहे हैं. इंग्लैंड के हर पोस्टकोड, हर फिल्टर और हर पड़ोस का लाइफटाइम एक्सेस पाएं. एक भुगतान, हमेशा के लिए.', + free: 'मुफ्त', + freeForEarly: 'शुरुआती उपयोगकर्ताओं के लिए मुफ्त. क्रेडिट कार्ड की जरूरत नहीं.', + oneTimePayment: 'एक बार भुगतान. लाइफटाइम एक्सेस.', + redirecting: 'रीडायरेक्ट किया जा रहा है...', + claimFreeAccess: 'मुफ्त एक्सेस लें', + upgradeFor: '{{price}} में अपग्रेड करें', + registerAndUpgrade: 'रजिस्टर करें और अपग्रेड करें', + alreadyHaveAccount: 'पहले से खाता है? लॉग इन करें', + continueWithDemo: 'डेमो जारी रखें', + backToSharedArea: 'साझा क्षेत्र पर वापस जाएं', + sharedAreaDescription: + 'आप एक साझा क्षेत्र देख रहे हैं. इससे आगे देखने के लिए इंग्लैंड के हर पोस्टकोड, हर फिल्टर और हर पड़ोस का लाइफटाइम एक्सेस लें.', + checkoutFailed: 'चेकआउट विफल रहा', + }, + + saveSearch: { + title: 'खोज सहेजें', + saved: 'खोज सहेजी गई', + savedSuccess: 'आपकी खोज सफलतापूर्वक सहेज ली गई है.', + viewSavedSearches: 'सहेजी गई खोजें देखें', + name: 'नाम', + namePlaceholder: 'मेरी खोज', + saving: 'सहेजा जा रहा है...', + }, + + licenseSuccess: { + title: 'आप अंदर हैं.', + subtitle: 'आपका लाइफटाइम एक्सेस अब सक्रिय है.', + description: 'पूरे इंग्लैंड में हर फीचर और हर पोस्टकोड का पूरा एक्सेस.', + startExploring: 'खोजना शुरू करें', + }, + + filters: { + activeFilters: 'सक्रिय फिल्टर', + addFilter: 'फिल्टर जोड़ें', + findingPerfectPostcode: 'Perfect Postcode खोजा जा रहा है', + addFiltersHint: 'अपनी शर्तों से मेल खाने वाले क्षेत्र पाने के लिए नीचे फिल्टर जोड़ें', + upgradePrompt: + 'इंग्लैंड भर में अपराध, स्कूल, शोर, ब्रॉडबैंड, कीमतें और 50+ अन्य फिल्टर से मेल खाने वाले पोस्टकोड खोजें.', + oneTimeLifetime: 'एक बार भुगतान, लाइफटाइम एक्सेस.', + upgradeToFullMap: 'पूरा मानचित्र अपग्रेड करें', + chooseFilters: 'जो फिल्टर आपके लिए मायने रखते हैं उन्हें चुनें. मानचित्र तुरंत अपडेट होता है.', + searchFeatures: 'फीचर खोजें...', + noMatchingFeatures: 'कोई मेल खाता फीचर नहीं', + tryDifferentSearch: 'कोई दूसरा खोज शब्द आजमाएं', + allFeaturesActive: 'सभी फीचर सक्रिय हैं', + removeFilterHint: 'उपलब्ध फीचर देखने के लिए कोई फिल्टर हटाएं', + featureInfo: 'फीचर जानकारी', + replayTutorial: 'इंटरैक्टिव ट्यूटोरियल फिर चलाएं', + clearAll: 'सभी साफ करें', + clearAllTitle: 'सभी फिल्टर साफ करें?', + clearAllSavePrompt: 'क्या साफ करने से पहले आप अपने मौजूदा फिल्टर सहेजना चाहेंगे?', + saveAndClear: 'सहेजें और साफ करें', + clearWithoutSaving: 'बिना सहेजे साफ करें', + }, + + philosophy: { + intro: + 'अपनी अनिवार्य जरूरतों से शुरू करें, फिर अच्छी लगने वाली चीजें जोड़ें. जैसे-जैसे आप फिल्टर जोड़ते हैं, मानचित्र संकरा होता जाता है. बचे हुए क्षेत्र आपके सबसे अच्छे मेल हैं.', + step1Title: 'बजट और मूल बातें', + step1Desc: '(कीमत सीमा, फर्श क्षेत्र, संपत्ति प्रकार)', + step2Title: 'आवागमन', + step2Desc: '(कार, साइकिल या सार्वजनिक परिवहन से कार्यस्थल तक यात्रा समय)', + step3Title: 'सुरक्षा', + step3Desc: '(अपराध दरें, शोर स्तर, जमीन की स्थिरता)', + step4Title: 'स्कूल', + step4Desc: '(नजदीकी Ofsted-rated Good या Outstanding स्कूल)', + step5Title: 'जीवनशैली', + step5Desc: '(रेस्तरां, पार्क, ब्रॉडबैंड स्पीड)', + step6Title: 'ऊर्जा', + step6Desc: '(EPC रेटिंग, इंसुलेशन, हीटिंग लागत)', + tip: 'टिप: अगर कुछ भी मेल नहीं खाता, तो एक समय में एक शर्त ढीली करें और देखें कौन सा समझौता सबसे अधिक विकल्प खोलता है.', + }, + + travel: { + travelTime: 'यात्रा समय ({{mode}})', + maxTime: 'अधिकतम समय', + selectDestination: 'गंतव्य चुनें...', + bestCase: 'सर्वश्रेष्ठ स्थिति', + bestCaseTitle: 'सर्वश्रेष्ठ स्थिति यात्रा समय', + bestCaseDesc: + 'सबसे तेज यथार्थवादी यात्रा समय का उपयोग करता है (अगर आप प्रस्थान का समय सही रखें और अच्छे कनेक्शन मिलें). डिफॉल्ट मीडियन का उपयोग करता है, जो आपके निकलने के समय से स्वतंत्र एक सामान्य यात्रा दिखाता है.', + previewOnMap: 'मानचित्र पर पूर्वावलोकन', + stopPreviewing: 'पूर्वावलोकन रोकें', + removeTravelTime: 'यात्रा समय हटाएं', + addTravelTime: '{{mode}} यात्रा समय जोड़ें', + clearDestination: 'गंतव्य साफ करें', + typeToFilter: 'फिल्टर करने के लिए टाइप करें...', + noDestinations: 'कोई गंतव्य नहीं मिला', + modeCar: 'कार', + modeBicycle: 'साइकिल', + modeWalking: 'पैदल', + modeTransit: 'सार्वजनिक परिवहन', + modeCarDesc: 'सबसे तेज सड़क मार्ग से ड्राइव समय', + modeBicycleDesc: 'साइकिल-अनुकूल मार्गों से साइकिल समय', + modeWalkingDesc: 'पैदल रास्तों और फुटपाथों से पैदल समय', + modeTransitDesc: 'ट्रेन, ट्यूब और बस से यात्रा समय', + }, + + travelInfo: { + transitDesc: + ' सार्वजनिक परिवहन से (बस, रेल, ट्यूब). समय सामान्य कार्यदिवस सुबह की अवधि में गणना किया जाता है.', + carDesc: ' कार से, सामान्य सड़क गति और सड़क नेटवर्क के आधार पर.', + bicycleDesc: ' साइकिल से, साइकिल-अनुकूल मार्गों का उपयोग करके.', + walkingDesc: ' पैदल, पैदल रास्तों और फुटपाथों का उपयोग करके.', + mainDesc: 'दिखाता है कि हर क्षेत्र से चुने गए गंतव्य तक पहुंचने में कितना समय लगता है', + sliderHint: 'अपना अधिकतम आवागमन समय सेट करने के लिए स्लाइडर का उपयोग करें.', + }, + + aiFilter: { + describeIdealArea: 'बताएं आप कहां रहना चाहते हैं', + aiSearch: 'AI खोज', + describeHint: 'बताएं आप क्या खोज रहे हैं', + placeholder: 'जैसे 2-bed £525k से कम, काम तक 45 मिनट, शांत...', + example1: '2-bed £525k से कम, काम तक 45 मिनट', + example2: '£650k से कम अच्छे स्कूलों के पास परिवारों वाले क्षेत्र', + example3: 'समझदारी वाले आवागमन के साथ ज्यादा जगह', + analysing: 'आपकी क्वेरी का विश्लेषण हो रहा है...', + searchingDestinations: 'गंतव्य खोजे जा रहे हैं...', + generatingFilters: 'फिल्टर बनाए जा रहे हैं...', + refiningResults: 'परिणाम सुधारे जा रहे हैं...', + weeklyLimitReached: + 'आप साप्ताहिक AI उपयोग सीमा तक पहुंच गए हैं. यह अगले सप्ताह अपने आप रीसेट हो जाएगी.', + }, + + mapLegend: { + clearColourView: 'रंग दृश्य साफ करें', + resetColourScale: 'रंग स्केल रीसेट करें', + historicalMatches: 'ऐतिहासिक संपत्ति मेल', + numberOfProperties: 'संपत्तियों की संख्या', + previewing: '“{{name}}” का पूर्वावलोकन', + }, + + propertyCard: { + unknownAddress: 'अज्ञात पता', + unsaveProperty: 'संपत्ति को असहेजें', + saveProperty: 'संपत्ति सहेजें', + estValue: 'अनु. मूल्य:', + type: 'प्रकार:', + builtForm: 'निर्माण रूप:', + tenure: 'टेन्योर:', + floorArea: 'फर्श क्षेत्र:', + rooms: 'कमरे:', + built: 'निर्माण:', + formerCouncil: 'पूर्व काउंसिल:', + exCouncilBadge: 'पूर्व काउंसिल', + epcRating: 'EPC रेटिंग:', + epcPotential: 'EPC संभावित:', + renovations: 'नवीनीकरण', + perSqm: '/वर्ग मी', + searchPlaceholder: 'पते या पोस्टकोड से खोजें...', + propertyData: 'संपत्ति डेटा', + propertyDataDesc: + 'कीमतें HM Land Registry से आती हैं (खरीदारों ने वास्तव में क्या भुगतान किया). फर्श क्षेत्र, ऊर्जा रेटिंग, निर्माण वर्ष और टेन्योर आधिकारिक EPC सर्वेक्षणों से आते हैं. दोनों स्रोत हर पोस्टकोड के अंदर पते से मिलाए जाते हैं.', + }, + + areaPane: { + areaStatistics: 'क्षेत्र आंकड़े', + statsFor: 'इस {{type}} की सभी संपत्तियों के आंकड़े', + matchingFilters: ' सभी सक्रिय फिल्टर से मेल खाते हुए', + filtersAffectStats: + 'बाएं पैनल के फिल्टर यहां लागू होते हैं: मान, चार्ट और संपत्ति संख्या {{count}} सक्रिय फिल्टर का उपयोग करते हैं.', + noFiltersAffectStats: + 'बाएं पैनल के फिल्टर इस पैनल को अपडेट करते हैं: मेल खाने वाली संपत्तियों के लिए ये मान फिर गणना करने हेतु फिल्टर जोड़ें.', + noFilteredMatches: 'इस क्षेत्र में कोई संपत्ति आपके फिल्टर से मेल नहीं खाती.', + unfilteredAreaCount: + 'फिल्टर से पहले यहां {{count}} संपत्तियां हैं, इसलिए स्थान वैध है लेकिन फिल्टर से बाहर हो गया है.', + noUnfilteredAreaProperties: 'फिल्टर से पहले इस चुने हुए क्षेत्र में कोई संपत्ति नहीं मिली.', + relaxFiltersHint: 'इस क्षेत्र की संपत्तियां देखने के लिए फिल्टर ढीले करें या साफ करें.', + viewProperties: '{{count}} संपत्तियां देखें', + priceHistory: 'कीमत इतिहास', + journeysFrom: '{{label}} से यात्राएं', + to: '{{destination}} तक', + noJourneyData: 'कोई यात्रा डेटा उपलब्ध नहीं', + viewOnGoogleMaps: 'Google Maps पर देखें', + walk: 'पैदल', + cycle: 'साइकिल', + nationalAvg: 'राष्ट्रीय औसत', + }, + + histogramLegend: { + tealBars: 'टील बार', + tealBarsDesc: 'इस चुने गए क्षेत्र का वितरण दिखाते हैं', + greyBars: 'ग्रे बार', + greyBarsDesc: 'सभी क्षेत्रों का कुल वितरण दिखाते हैं', + dashedLine: 'डैश्ड लाइन', + dashedLineDesc: 'राष्ट्रीय औसत दर्शाती है', + }, + + streetView: { + title: 'Street View', + openLarge: 'Street View बड़ा खोलें', + expandedTitle: 'विस्तारित Street View', + }, + + poiPane: { + pois: 'POI', + pointsOfInterest: 'रुचि के स्थान', + poiDescription: + 'OpenStreetMap, NaPTAN और GEOLYTIX Grocery Retail Points से स्रोतित. परिवहन स्टॉप, दुकानें, चेन सुपरमार्केट, रेस्तरां, स्वास्थ्य सेवा, अवकाश और अधिक शामिल हैं.', + searchCategories: 'श्रेणियां खोजें...', + dataSourceInfo: 'डेटा स्रोत जानकारी', + }, + + externalSearch: { + searchOn: '{{radius}} पर खोजें', + exact: 'सटीक', + outcodeNotRecognised: 'आउटकोड पहचाना नहीं गया', + }, + + locationSearch: { + placeholder: 'स्थान या पोस्टकोड खोजें...', + postcodeNotFound: 'पोस्टकोड नहीं मिला', + lookupFailed: 'लुकअप विफल रहा', + searchLabel: 'स्थान या पोस्टकोड खोजें', + locateMe: 'मेरे स्थान पर जाएं', + geolocationUnsupported: 'आपका ब्राउजर जियोलोकेशन का समर्थन नहीं करता', + geolocationFailed: 'आपका स्थान निर्धारित नहीं किया जा सका', + }, + + mobileDrawer: { + closeDrawer: 'ड्रॉअर बंद करें', + }, + + home: { + heroEyebrow: 'उन खरीदारों के लिए जो पूछते हैं “मुझे देखना कहां शुरू करना चाहिए?”', + heroTitle1: 'वे पोस्टकोड खोजें जो', + heroTitle2: 'आपकी जिंदगी से मेल खाते हैं', + heroTitle3: 'सिर्फ वे क्षेत्र नहीं जिन्हें आप पहले से जानते हैं.', + heroSubtitle: + 'लंदन बरो से लेकर कम्यूटर टाउन और क्षेत्रीय शहरों तक, इंग्लैंड में एक-एक जगह रिसर्च करने के लिए बहुत अधिक स्थान हैं.', + heroDescription: + 'अपना बजट, आवागमन, स्कूल, सुरक्षा, शोर, ब्रॉडबैंड और जीवनशैली की जरूरतें सेट करें. Perfect Postcode इंग्लैंड के पोस्टकोड स्कैन करता है और वे जगहें दिखाता है जो सच में मेल खाती हैं, उन क्षेत्रों सहित जिन्हें आप किसी प्रॉपर्टी पोर्टल में कभी नहीं खोजते.', + exploreTheMap: 'मेरे मेल खाते पोस्टकोड खोजें', + seeTheDifference: 'देखें यह कैसे काम करता है', + showcaseHeader: 'यह कैसे काम करता है', + showcaseContext: 'Perfect Postcode कैसे काम करता है', + showcaseStep1Tab: 'फिल्टर', + showcaseStep1Title: 'अस्पष्ट जरूरतों को सटीक खोज में बदलें', + showcaseStep1Body: + 'जो मायने रखता है उसे सेट करें और देखें कि हर जरूरत कितने गलत-फिट पोस्टकोड को आपकी खोज से बाहर रखती है.', + showcaseStep1Chip1: 'शांत सड़कें', + showcaseStep1Chip2: 'शीर्ष-रेटेड प्राइमरी', + showcaseStep1Chip3: '£500k से कम', + showcaseStep1VennCenter: 'तीनों शर्तों को पूरा करने वाले पोस्टकोड', + showcaseStep2Tab: 'मिलान', + showcaseStep2Title: 'मानचित्र को वे जगहें दिखाने दें जिन्हें आप टाइप नहीं करते', + showcaseStep2Body: + 'परिचित इलाकों के नामों से शुरू करने के बजाय अपनी जरूरतों से मेल के आधार पर इंग्लैंड देखें. प्रॉपर्टी पोर्टल आपकी सोच सीमित करें, उससे पहले छिपे हुए अच्छे इलाके सामने आ जाते हैं.', + showcaseStep2Region: 'ग्रेटर लंदन', + showcaseStep2Sources: 'Land Registry · ONS · Ofsted · DfT', + showcaseStep2ClustersLabel: 'मेल खाते क्लस्टर', + showcaseStep3Tab: 'जांचें', + showcaseStep3Title: 'जांचें कि कोई पोस्टकोड क्यों चुना गया', + showcaseStep3Body: + 'किसी भी मेल खाते क्षेत्र को खोलें और वहां सप्ताहांत बिताने से पहले कीमतें, सुरक्षा, स्कूल, ब्रॉडबैंड और समझौते एक ही पैनल में जांचें.', + showcaseStep3HeaderArea: 'आपका परफेक्ट पोस्टकोड', + showcaseStep3HeaderFit: 'पड़ोस का प्रमाण', + showcaseStep3Stat1Label: 'बेची गई कीमत का रुझान', + showcaseStep3Stat2Label: 'अपराध दर', + showcaseStep3Stat2Value: 'बरो औसत से कम', + showcaseStep3Stat3Label: 'मीडियन आयु', + showcaseStep3Stat4Label: 'ब्रॉडबैंड', + showcaseStep3Stat4Value: '1 Gbps उपलब्ध', + showcaseStep3Stat5Label: 'प्राइमरी स्कूल', + showcaseStep3Stat5Value: '1 मील में 3 outstanding', + showcaseStep4Tab: 'स्काउट', + showcaseStep4Title: 'खुद जाकर देखें', + showcaseStep4Body: + 'तीन ठोस शुरुआती बिंदुओं को वास्तविक दुनिया में ले जाएं. सड़कें चलकर देखें, आवागमन आजमाएं और संदर्भ के साथ मकानों की देखने-समझने की यात्राओं की तुलना करें.', + showcaseStep4FileName: 'areas-to-scout.xlsx', + showcaseStep4ExportLabel: 'Excel में निर्यात करें', + showcaseStep4ColPostcode: 'पोस्टकोड', + showcaseStep4ColScore: 'फिट', + showcaseStep4ColCommute: 'आवागमन', + showcaseStep4ColPrice: 'मीडियन बिक्री', + showcaseStep4Conclusion: 'आप अपनी यात्रा यहां से शुरू कर सकते हैं. अब आप भटके हुए नहीं हैं.', + statProperties: 'ऐतिहासिक बिक्री', + statFilters: 'जोड़े जा सकने वाले फिल्टर', + statEvery: 'हर', + statPostcodeInEngland: 'इंग्लैंड का पोस्टकोड', + ourPhilosophy: 'पोस्टकोड नहीं, अपनी जिंदगी से शुरू करें', + philosophyP1: + 'अधिकांश संपत्ति साइटें पूछती हैं कि आप कहां रहना चाहते हैं. लंदन में यह बहुत कठिन है, लेकिन यही समस्या पूरे इंग्लैंड में आती है: खरीदार उन कुछ जगहों में से चुनते हैं जिन्हें वे जानते हैं, फिर आवागमन टूल, Ofsted, पुलिस डेटा, Street View, ब्रॉडबैंड जांच और बेचे गए दामों को अलग-अलग टैब में मिलाते हैं.', + philosophyP2: + 'Perfect Postcode खोज को उलट देता है. मानचित्र को बताएं कि क्या मायने रखता है और यह वे पोस्टकोड दिखाता है जो योग्य हैं, साथ में प्रमाण भी कि वे देखने लायक क्यों हैं. पहले डेटा, फिर माहौल खुद परखें.', + streetTitle: 'जगहें सड़क-दर-सड़क बदलती हैं', + streetIntro: + 'बड़े क्षेत्र नाम वे विवरण छिपा देते हैं जो मायने रखते हैं: स्टेशन किस तरफ है, सड़क का शोर, स्कूल मिश्रण, सटीक आवागमन और समान घर वास्तव में किस कीमत पर बिके.', + streetCard1Title: 'वे क्षेत्र खोजें जो आपसे छूट सकते थे', + streetCard1Body: + 'परिचित नामों, दोस्तों की सिफारिशों या “उभरते इलाके” की चर्चा पर निर्भर रहने के बजाय अपनी जरूरतों से मेल खाते पोस्टकोड सामने लाएं.', + streetCard2Title: 'मकान देखने से पहले समझौते समझें', + streetCard2Body: + 'सप्ताहांत मकान देखने में बिताने से पहले कीमत, जगह, आवागमन, सुरक्षा, स्कूल, ब्रॉडबैंड, शोर और ऊर्जा रेटिंग की तुलना करें.', + othersVs: 'दूसरे बनाम', + checkMyPostcode: 'प्रॉपर्टी पोर्टल', + areaGuides: 'पोस्टकोड रिपोर्ट', + compSearchWithout: 'नाम जाने बिना क्षेत्र खोजें', + compSearchWithoutSub: '(पहले जरूरतें, बाद में स्थान)', + compAreaData: 'पोस्टकोड-स्तर पड़ोस प्रमाण', + compAreaDataSub: '(अपराध, स्कूल, शोर, ब्रॉडबैंड, सुविधाएं)', + compPropertyData: 'संपत्ति-स्तर इतिहास', + compPropertyDataSub: '(बेची कीमतें, EPC, फर्श क्षेत्र, अनुमानित मूल्य)', + compFilters: '56 फिल्टर साथ काम करते हुए', + compFiltersSub: '(एक समय में केवल एक पोस्टकोड या एक लिस्टिंग नहीं)', + ctaTitle: 'कहां खरीदना है, इसका अनुमान लगाना बंद करें.', + ctaDescription: + 'उन पोस्टकोड की शॉर्टलिस्ट बनाएं जो आपकी वास्तविक जिंदगी से मेल खाते हैं, फिर उन्हें खुद जांचें.', + }, + + pricingPage: { + title: 'बेहतर खोज क्षेत्र के साथ खरीदें', + subtitle: + 'उस मानचित्र का लाइफटाइम एक्सेस जो मकान देखने की बुकिंग से पहले यह पता लगाने में मदद करता है कि कहां देखना है.', + costContext: + 'खरीदार अक्सर शामें लिस्टिंग, आवागमन जांच, स्कूल रिपोर्ट, अपराध मानचित्र, Street View और बेचे गए दामों को जोड़ने में बिताते हैं. लंदन में यह लगातार होता है, लेकिन यही शोध-समस्या पूरे इंग्लैंड में दिखाई देती है. Perfect Postcode आपके सप्ताहांत, फीस और ध्यान लगाने से पहले क्षेत्र-शोध को एक मानचित्र पर रखता है.', + lessThanSurvey: 'एक survey से कम. आपके चुनावों को दिशा देने में कहीं अधिक असरदार.', + currentTier: 'मौजूदा स्तर', + firstNUsers: 'पहले {{count}} उपयोगकर्ता', + everyoneAfter: 'उसके बाद सभी', + nextNUsers: 'अगले {{count}} उपयोगकर्ता', + lifetime: '/लाइफटाइम', + spotsRemaining: '{{count}} स्थान बाकी', + spotsRemainingPlural: '{{count}} स्थान बाकी', + filled: 'भरा हुआ', + openDashboard: 'डैशबोर्ड खोलें', + getStarted: 'शुरू करें', + getStartedPrice: 'शुरू करें - {{price}}', + noCreditCard: 'क्रेडिट कार्ड की जरूरत नहीं', + soldOut: 'बिक गया', + upcoming: 'आने वाला', + failedToLoad: 'कीमतें लोड नहीं हो सकीं. कृपया बाद में फिर कोशिश करें.', + feat1: 'इंग्लैंड भर में 56 फिल्टर', + feat2: 'आपकी जरूरतों से हर पोस्टकोड खोजने योग्य', + feat3: 'असीमित मानचित्र खोज, सहेजी गई खोजें और निर्यात', + feat4: '13M ऐतिहासिक लेनदेन और कीमत संदर्भ', + feat5: 'आवागमन, स्कूल, अपराध, शोर, ब्रॉडबैंड और अधिक', + feat6: 'भविष्य के सभी डेटा अपडेट शामिल', + }, + + learnPage: { + faq: 'FAQ', + dataSources: 'डेटा स्रोत', + support: 'सहायता', + dataSourcesIntro: + 'यह एप्लिकेशन संपत्ति कीमतों, ऊर्जा प्रदर्शन, परिवहन, जनसांख्यिकी, अपराध, पर्यावरण और अधिक को कवर करने वाले {{count}} खुले डेटासेट को जोड़ता है.', + faqIntro: + 'चाहे आप पहली बार खरीदारी की खोज संकरी कर रहे हों, किसी अनजान पोस्टकोड की जांच कर रहे हों या viewing shortlist बना रहे हों, यहां बताया गया है कि Perfect Postcode आपको कहां देखना है यह तय करने में कैसे मदद करता है.', + supportIntro: 'कोई सवाल है? हमारा FAQ देखें या सीधे संपर्क करें.', + source: 'स्रोत:', + optOut: 'सार्वजनिक प्रकटीकरण से बाहर निकलें', + attribution: 'श्रेय', + attrLandRegistry: 'HM Land Registry डेटा शामिल है © Crown copyright and database right 2025.', + attrOgl: 'सार्वजनिक क्षेत्र की जानकारी शामिल है, जो इसके अंतर्गत लाइसेंस प्राप्त है:', + attrOglLink: 'Open Government Licence v3.0', + attrOs: 'OS data शामिल है © Crown copyright and database rights 2025.', + attrTfl: 'TfL Open Data द्वारा संचालित.', + attrOsm: 'डेटा शामिल है:', + attrOsmContrib: '© OpenStreetMap contributors', + attrOsmLicense: 'इसके अंतर्गत उपलब्ध:', + attrOsmLicenseLink: 'Open Data Commons Open Database License (ODbL)', + dsPricePaidName: 'Price Paid Data', + dsPricePaidOrigin: 'HM Land Registry', + dsPricePaidUse: 'इंग्लैंड के लिए पूर्ण ऐतिहासिक संपत्ति बिक्री कीमतें.', + dsEpcName: 'Energy Performance Certificates (EPC)', + dsEpcOrigin: 'Ministry of Housing, Communities & Local Government', + dsEpcUse: + 'घरेलू Energy Performance Certificates जो फर्श क्षेत्र, कमरों की संख्या, निर्माण वर्ष, ऊर्जा रेटिंग, संपत्ति प्रकार और built form देते हैं. हर पोस्टकोड के अंदर पते से Price Paid records के साथ मिलाए गए. संपत्ति मालिक सार्वजनिक प्रकटीकरण से opt out कर सकते हैं.', + dsNsplName: 'National Statistics Postcode Lookup (NSPL)', + dsNsplOrigin: 'ONS / ArcGIS', + dsNsplUse: + 'पोस्टकोड को coordinates और statistical area codes से जोड़ता है, जिससे सभी area-level datasets को individual properties से लिंक किया जाता है.', + dsIodName: 'English Indices of Deprivation 2025', + dsIodOrigin: 'Ministry of Housing, Communities & Local Government', + dsIodUse: + 'इंग्लैंड के हर neighbourhood के लिए income, employment, education, health, crime और living environment में relative deprivation scores.', + dsEthnicityName: 'Population by Ethnicity (2021 Census)', + dsEthnicityOrigin: 'ONS', + dsEthnicityUse: + 'Local authority के अनुसार ethnic group (South Asian, East Asian, Black, Mixed, White, Other) के population percentages.', + dsCrimeName: 'Street-level Crime Data', + dsCrimeOrigin: 'data.police.uk', + dsCrimeUse: + '2023 से 2025 तक सड़क-स्तर अपराध डेटा, LSOA और अपराध प्रकार (हिंसा, सेंधमारी, असामाजिक व्यवहार, ड्रग्स, वाहन अपराध आदि) के अनुसार वार्षिक औसत में समेकित.', + dsOsmName: 'OpenStreetMap POIs', + dsOsmOrigin: 'OpenStreetMap contributors / Geofabrik', + dsOsmUse: + 'Great Britain भर में shops, restaurants, healthcare, leisure, tourism और अधिक को cover करने वाले points of interest.', + dsGeolytixRetailName: 'GEOLYTIX Grocery Retail Points', + dsGeolytixRetailOrigin: 'GEOLYTIX', + dsGeolytixRetailUse: + 'UK भर में supermarket और convenience store locations, जिनमें Waitrose, Tesco, Sainsbury’s, Asda, Morrisons, Aldi, Lidl, Co-op, M&S, Iceland और Spar जैसे chain retailers शामिल हैं.', + dsGreenspaceName: 'OS Open Greenspace', + dsGreenspaceOrigin: 'Ordnance Survey', + dsGreenspaceUse: + 'Great Britain के लिए authoritative green space boundaries, जिनमें public parks, gardens, playing fields और play spaces शामिल हैं. Park proximity counts और nearest-park distance calculations के लिए polygon centroids उपयोग होते हैं.', + dsNaptanName: 'NaPTAN (Public Transport Stops)', + dsNaptanOrigin: 'Department for Transport', + dsNaptanUse: + 'इंग्लैंड भर में rail, bus, metro/tram, ferry और airports के station और stop locations.', + dsNoiseName: 'Defra Noise Mapping', + dsNoiseOrigin: 'Defra / Environment Agency', + dsNoiseUse: + '2022 strategic noise mapping से road noise levels (24-hour weighted average), high resolution पर modelled और हर postcode पर sampled.', + dsOfstedName: 'Ofsted School Inspections', + dsOfstedOrigin: 'Ofsted', + dsOfstedUse: + 'State-funded schools के latest inspection outcomes (April 2025 तक). Local school quality score देने के लिए प्रति postcode averaged (1=Outstanding से 4=Inadequate).', + dsBroadbandName: 'Ofcom Broadband Performance', + dsBroadbandOrigin: 'Ofcom', + dsBroadbandUse: + 'Ofcom Connected Nations 2025 से area के अनुसार fixed broadband coverage और maximum download speeds.', + dsCouncilTaxName: 'Council Tax Levels 2025-26', + dsCouncilTaxOrigin: 'Ministry of Housing, Communities & Local Government', + dsCouncilTaxUse: + 'England की सभी 296 billing authorities के लिए Bands A-H की annual council tax rates, दो वयस्कों द्वारा occupied dwelling के लिए. NSPL postcode lookup से local authority district code के जरिए properties से joined.', + dsRentalName: 'Private Rental Market Statistics', + dsRentalOrigin: 'ONS / Valuation Office Agency', + dsRentalUse: + 'Local authority और bedroom category के अनुसार median monthly private rental prices (Oct 2022 - Sep 2023). Local authority district code और estimated bedroom count से properties से joined.', + dsElectionName: '2024 General Election Results', + dsElectionOrigin: 'UK Parliament', + dsElectionUse: + 'July 2024 UK General Election के candidate-level results. Constituency level पर aggregated: voter turnout (%) और party vote shares (%). NSPL postcode lookup से parliamentary constituency code (pcon) के जरिए properties से joined.', + faqFindingTitle: 'खोज रणनीति', + faqCommuteTitle: 'यात्रा-समय रूटिंग', + faqBudgetTitle: 'अनुमानित कीमतें', + faqSafetyTitle: 'सुरक्षा और पड़ोस', + faqFamiliesTitle: 'परिवार और स्कूल', + faqEnvironmentTitle: 'पर्यावरण और जीवन गुणवत्ता', + faqDueDiligenceTitle: 'दायरा और Due Diligence', + faqPrivacyTitle: 'गोपनीयता और डेटा संरक्षण', + faqWhyTitle: 'Perfect Postcode क्यों', + faqPricingTitle: 'एक्सेस', + faqTipsTitle: 'टिप्स और ट्रिक्स', + faqFinding1Q: 'जब स्पष्ट क्षेत्र बहुत महंगे हों तो मुझे कहां देखना चाहिए?', + faqFinding1A: + 'अपना बजट, संपत्ति प्रकार, फर्श क्षेत्र, आवागमन, स्कूल, अपराध, शोर, ब्रॉडबैंड, पार्क और अन्य अनिवार्य जरूरतें सेट करें. मानचित्र उन पोस्टकोड को हटाता है जो इन कसौटियों पर खरे नहीं उतरते, इसलिए उपेक्षित क्षेत्र लिस्टिंग खोजना शुरू करने से पहले सामने आ सकते हैं.', + faqFinding2Q: 'जिन जगहों को मैं अच्छी तरह नहीं जानता, वहां अच्छे पोस्टकोड कैसे खोजूं?', + faqFinding2A: + 'पूरे मानचित्र को अपनी कठोर जरूरतों से फिल्टर करें, फिर बचे हुए समूहों को जांचें. आप प्रतिष्ठा पर निर्भर रहने के बजाय अनजान पोस्टकोड की आवागमन, बेचे गए दाम, स्कूल, अपराध, ब्रॉडबैंड, शोर और सुविधाओं से तुलना कर सकते हैं.', + faqFinding3Q: 'जब मेरी खोज बहुत ज्यादा या बहुत कम क्षेत्र लौटाए तो क्या करूं?', + faqFinding3A: + 'कठोर सीमाओं से शुरू करें, फिर प्रति वर्ग मी कीमत, सड़क शोर, स्कूल स्कोर या आवागमन समय जैसे समझौते से मानचित्र को रंगें. अगर मानचित्र बहुत संकरा हो जाए, एक स्लाइडर ढीला करें और आप देख सकते हैं कौन सा समझौता ज्यादा विकल्प खोलता है.', + faqCommute1Q: 'यात्रा समय कैसे गणना किए जाते हैं?', + faqCommute1A: + 'यात्रा समय Conveyal R5 से पहले से गणना किए गए हैं, जो परिवहन विश्लेषण में इस्तेमाल होने वाला रूटिंग इंजन है. हर समर्थित गंतव्य के लिए हम सड़क और सार्वजनिक परिवहन नेटवर्क पर पहुंचने योग्य पोस्टकोड तक मार्ग निकालते हैं, फिर कार, साइकिल, पैदल और सार्वजनिक परिवहन के लिए संक्षिप्त पोस्टकोड यात्रा-समय फाइलें रखते हैं. इससे मानचित्र हजारों पोस्टकोड को एक-एक रूट API कॉल करने के बजाय तेजी से फिल्टर कर सकता है.', + faqCommute2Q: 'Travel-time numbers के बारे में क्या जानना चाहिए?', + faqCommute2A: + 'सार्वजनिक परिवहन समय सुबह के व्यस्त प्रस्थान समय, 07:30 से 08:30, का उपयोग करते हैं. डिफॉल्ट मीडियन यात्रा समय है, जो उस अवधि का सामान्य परिणाम है; best-case टॉगल सही समय पर निकलने पर 5वें पर्सेंटाइल का उपयोग करता है. ये मॉडल किए गए तुलनात्मक समय हैं, लाइव बाधा, ट्रैफिक या प्लेटफॉर्म-परिवर्तन की भविष्यवाणी नहीं.', + faqBudget1Q: 'Estimated current price algorithm कैसे काम करता है?', + faqBudget1A: + 'Proprietary estimate अंतिम HM Land Registry sale price से शुरू होता है. यह repeat-sales index से कीमत को आज तक adjust करता है, जिसे postcode sector और property type के अनुसार सीखा गया है. Sparse areas को district, area, national और hedonic fallback models की ओर shrink किया जाता है, फिर spatially smooth किया जाता है. अंत में result को nearby, recently sold, same-type homes से adjusted price per sqm और EPC floor area का उपयोग करके nearest-neighbour estimate के साथ blend किया जाता है.', + faqBudget2Q: 'Last sold price के बजाय estimated current price क्यों उपयोग करें?', + faqBudget2A: + 'अंतिम बिक्री कीमत कई साल या दशकों पुरानी हो सकती है, और लाइव मांग कीमतें केवल आज सूचीबद्ध घरों को कवर करती हैं. अनुमानित मौजूदा कीमत पुराने सौदों को मौजूदा बाजार के संदर्भ में रखती है ताकि आप अधिक संपत्तियों की तुलना कर सकें, अनुमानित प्रति वर्ग मी कीमत निकाल सकें और लिस्टिंग आने से पहले अच्छी वैल्यू वाले क्षेत्रों को पहचान सकें. यह छांटने के लिए अनुमान है, औपचारिक मूल्यांकन नहीं.', + faqSafety1Q: 'इस पोस्टकोड के आसपास किस तरह का अपराध आम है?', + faqSafety1A: + 'पुलिस-रिकॉर्ड अपराध प्रकार के अनुसार बंटा होता है, जिसमें हिंसा, सेंधमारी, लूट, वाहन अपराध, असामाजिक व्यवहार, दुकान से चोरी, ड्रग्स और सार्वजनिक व्यवस्था शामिल हैं. आप अस्पष्ट सुरक्षा स्कोर पर निर्भर रहने के बजाय खास जोखिमों से फिल्टर कर सकते हैं.', + faqSafety2Q: 'किसी अनजान सड़क पर viewing से पहले क्या जांचना चाहिए?', + faqSafety2A: + 'मकान देखने की बुकिंग से पहले अपराध, सड़क शोर, वंचना, ब्रॉडबैंड, पार्क, किराना, स्कूल और आवागमन जांचें. लिस्टिंग फोटो उपयोगी हो सकती हैं, पर सड़क कैसी है यह पहली बार उनसे नहीं पता चलना चाहिए.', + faqFamilies1Q: 'किन क्षेत्रों में schools, space, safety और commute का सही मिश्रण है?', + faqFamilies1A: + 'Ofsted रेटिंग, अपराध, पार्क, आवागमन, फर्श क्षेत्र, संपत्ति प्रकार और बजट को एक मानचित्र पर साथ रखें. परिणाम अलग-अलग स्कूल, अपराध, लिस्टिंग और परिवहन खोजों के ढेर के बजाय व्यावहारिक पारिवारिक शॉर्टलिस्ट है.', + faqFamilies2Q: 'क्या यह साबित करता है कि मैं school catchment के अंदर हूं?', + faqFamilies2A: + 'नहीं. हम नजदीकी स्कूल गुणवत्ता और क्षेत्र-स्तर शिक्षा डेटा दिखाते हैं, लेकिन प्रवेश सीमाएं और प्राथमिकता नियम बदल सकते हैं. Perfect Postcode को शॉर्टलिस्ट टूल मानें, फिर स्कूल या स्थानीय प्राधिकरण से कैचमेंट और प्रवेश सत्यापित करें.', + faqEnv1Q: 'Commute या broadband quality खोए बिना noisy road से कैसे बचूं?', + faqEnv1A: + 'सड़क शोर से फिल्टर करें, फिर आवागमन समय, ब्रॉडबैंड स्पीड, कीमत और संपत्ति फिल्टर सक्रिय रखें. आप एक फीचर से मानचित्र को रंग सकते हैं जबकि बाकी शॉर्टलिस्ट को वास्तविक बनाए रखते हैं.', + faqEnv2Q: 'क्या आप flood risk, subsidence या survey issues दिखाते हैं?', + faqEnv2A: + 'आज लाइव फिल्टर के रूप में नहीं. हम सड़क शोर, EPC, निर्माण आयु और स्थानीय पर्यावरण संकेतकों जैसे डेटा दिखाते हैं, पर बाढ़ खोज, टाइटल जांच, संरचनात्मक समस्याएं और मॉर्गेज योग्यता के लिए अभी भी सही कन्वेयंसिंग, ऋणदाता जांच और survey चाहिए.', + faqEnv3Q: 'Viewing से पहले running-cost checks क्या कर सकता हूं?', + faqEnv3A: + 'आप मकान देखने से पहले EPC रेटिंग, कुल फर्श क्षेत्र, निर्माण आयु, council tax authority, ब्रॉडबैंड और शोर की छंटाई कर सकते हैं. यह आपके सटीक बिलों की भविष्यवाणी नहीं करेगा, पर साफ तौर पर गलत विकल्पों से जल्दी बचने में मदद करेगा.', + faqDueDiligence1Q: 'क्या मुझे Rightmove देखने से पहले या बाद में इसका उपयोग करना चाहिए?', + faqDueDiligence1A: + 'Perfect Postcode का उपयोग प्रॉपर्टी पोर्टल से पहले और साथ-साथ करें. Rightmove, Zoopla और OnTheMarket अभी भी लाइव उपलब्धता, फोटो, एजेंट संपर्क, देखने की बुकिंग और अलर्ट जांचने की जगह हैं. Perfect Postcode आपको पहले यह समझने में मदद करता है कि किन पोस्टकोड में खोज करना सार्थक है.', + faqDueDiligence2Q: 'मैं garden, garage या layout से filter क्यों नहीं कर सकता?', + faqDueDiligence2A: + 'केवल वहां जहां जानकारी संरचित आधिकारिक डेटा में मौजूद है. Perfect Postcode फर्श क्षेत्र, संपत्ति प्रकार, टेन्योर, EPC, बेचे गए दाम और स्थानीय डेटा जैसी चीजों से फिल्टर कर सकता है. बगीचे, गैरेज, दिशा, कमरों की बनावट और एजेंट की भाषा अभी भी लिस्टिंग और मकान देखने के दौरान जांचनी होगी.', + faqDueDiligence3Q: 'क्या आप listing price cuts और time on market track करते हैं?', + faqDueDiligence3A: + 'अभी नहीं. Perfect Postcode लाइव लिस्टिंग फीड के बजाय आधिकारिक बेचे गए दाम, EPC, पोस्टकोड, यात्रा-समय और पड़ोस डेटा पर बना है. आप फिर भी अंतिम लेनदेन की तारीख, बिक्री इतिहास, अनुमानित मौजूदा मूल्य और प्रति वर्ग मी कीमत से अंदाजा लगा सकते हैं कि लाइव मांग कीमत ज्यादा तो नहीं लगती.', + faqDueDiligence4Q: 'Offer करने से पहले मुझे क्या verify करना चाहिए?', + faqDueDiligence4A: + 'क्षेत्र और मूल्य की समझदारी से जांच के लिए Perfect Postcode का उपयोग करें, फिर सामान्य पेशेवर प्रक्रिया से लाइव लिस्टिंग विवरण, टेन्योर, लीज शर्तें, सर्विस चार्ज, योजना इतिहास, बाढ़ जोखिम, टाइटल समस्याएं, ऋणदाता आवश्यकताएं और survey निष्कर्ष सत्यापित करें.', + faqPrivacy1Q: 'क्या आप मेरे बारे में personal data store करते हैं?', + faqPrivacy1A: + 'हम संपत्ति और पड़ोस डेटासेट में व्यक्तिगत डेटा नहीं रखते. ये डेटासेट पोस्टकोड और संपत्ति शोध के लिए आधिकारिक और सार्वजनिक स्रोतों से बने हैं. अगर आप खाता बनाते हैं, तो सेवा चलाने के लिए जरूरी डेटा ही रखते हैं, जैसे ईमेल पता, लाइसेंस स्थिति, newsletter पसंद, सहेजी गई खोजें, सहेजी गई संपत्तियां और Stripe द्वारा संभाले गए भुगतान पहचानकर्ता. हम खाता डेटा को UK GDPR और Data Protection Act 2018 के तहत संभालते हैं.', + faqWhy1Q: 'यह क्या दिखाता है जो listing portals आमतौर पर नहीं दिखाते?', + faqWhy1A: + 'प्रॉपर्टी पोर्टल आज बिक्री पर मौजूद घरों से शुरू करते हैं. Perfect Postcode उन जगहों से शुरू करता है जो आपके जीवन और बजट से मेल खाती हैं, बेचे गए दाम, फर्श क्षेत्र, आवागमन, स्कूल, अपराध, शोर, ब्रॉडबैंड, EPC, टेन्योर और सुविधाओं के साथ, लिस्टिंग खोलने से पहले.', + faqWhy2Q: 'यह कितनी manual research बचाता है?', + faqWhy2A: + 'आप कर सकते हैं, लेकिन इसका मतलब Land Registry, EPC, पुलिस, Ofsted, Ofcom, ONS, Defra, यात्रा-समय और मानचित्र डेटा को एक-एक पोस्टकोड से जोड़ना है. Perfect Postcode इन स्रोतों को पूरे इंग्लैंड में एक जगह फिल्टर करने योग्य बनाता है.', + faqWhy3Q: 'Underlying sources कितने reliable हैं?', + faqWhy3A: + 'मुख्य डेटासेट HM Land Registry, EPC रिकॉर्ड, ONS, Ofsted, Ofcom, data.police.uk, Defra, Ordnance Survey और OpenStreetMap जैसे आधिकारिक या विश्वसनीय स्रोतों से आते हैं. वे शॉर्टलिस्ट और तुलना के लिए बहुत अच्छे हैं, लेकिन किसी भी खरीद निर्णय के लिए मौजूदा जांच और पेशेवर सलाह जरूरी है.', + faqPricing1Q: 'जब postcode reports free हैं तो भुगतान क्यों करें?', + faqPricing1A: + 'मुफ्त पोस्टकोड टूल तब उपयोगी हैं जब आप पहले से जानते हैं कि क्या जांचना है. Perfect Postcode इंग्लैंड के हर पोस्टकोड को आपकी कसौटियों पर स्कैन करने, फिल्टर जोड़ने, समझौतों की तुलना करने, खोजें सहेजने और देखने में सप्ताहांत लगाने से पहले शॉर्टलिस्ट निर्यात करने के लिए है.', + faqPricing2Q: 'Lifetime access का क्या मतलब है?', + faqPricing2A: + 'लाइफटाइम एक्सेस का मतलब है कि एक भुगतान आपके खाते को सेवा की अवधि तक paid Perfect Postcode मानचित्र का लगातार एक्सेस देता है. यह मासिक या वार्षिक सदस्यता नहीं है, और सामान्य डेटा अपडेट शामिल हैं. आप इसे इस खोज में उपयोग कर सकते हैं, बाद में लौट सकते हैं और फिर भी एक्सेस रहेगा अगर आप फिर स्थान बदलते हैं.', + faqPricing3Q: 'Free tier पर मैं क्या access कर सकता हूं?', + faqPricing3A: + 'मुफ्त उपयोगकर्ता डेमो क्षेत्र (inner London, लगभग zones 1 to 2) के अंदर सभी फीचर देख सकते हैं. England के बाकी डेटा के लिए लाइफटाइम एक्सेस चाहिए.', + faqTips1Q: 'Plain English में search कैसे describe करूं?', + faqTips1A: + 'कुछ ऐसा लिखें: "freehold 3-bed under £550k, 45 minutes to work, quiet, good broadband", और AI फिल्टर समझे गए मिलान वाले फिल्टर सेट करेगा. यह आपको यह भी बताएगा जब कोई मांग, जैसे बगीचे का आकार, संरचित फिल्टर के रूप में उपलब्ध नहीं है.', + faqTips2Q: 'क्या मैं search save करके बाद में वापस आ सकता हूं?', + faqTips2A: + 'सेव बटन दबाएं और सब दर्ज हो जाता है: आपके फिल्टर, zoom स्तर और कौन सी डेटा लेयर रंगी हुई है. जहां छोड़ा था वहीं से शुरू करें या लिंक अपने साथी के साथ साझा करें.', + faqTips3Q: 'क्या मैं जो data देख रहा हूं उसे export कर सकता हूं?', + faqTips3A: + 'मौजूदा फिल्टर की गई संपत्तियों को स्प्रेडशीट में डाउनलोड करने के लिए export बटन उपयोग करें. Export आपके सक्रिय फिल्टर का पालन करता है, इसलिए आप पोर्टल, मकान देखने, स्प्रेडशीट या साथ में खरीद रहे किसी व्यक्ति से बातचीत के लिए साफ शॉर्टलिस्ट ले जा सकते हैं.', + }, + + accountPage: { + emailLabel: 'ईमेल', + subscriptionLabel: 'सदस्यता', + upgrade: 'अपग्रेड', + redirecting: 'रीडायरेक्ट किया जा रहा है...', + receiveNewsletter: 'न्यूज़लेटर ईमेल प्राप्त करें', + needHelp: 'मदद चाहिए? हमें ईमेल करें', + responseTime: 'हम आमतौर पर 24 घंटे के भीतर जवाब देते हैं.', + }, + + savedPage: { + searches: 'खोजें', + noSavedSearches: 'अभी कोई सहेजी गई खोज नहीं', + noSavedSearchesDesc: + 'अपने फिल्टर और मानचित्र दृश्य सहेजें ताकि आप ठीक वहीं से फिर शुरू कर सकें जहां छोड़ा था.', + noSavedProperties: 'अभी कोई सहेजी गई संपत्ति नहीं', + noSavedPropertiesDesc: + 'खोजते समय संपत्तियां बुकमार्क करें और बिना ट्रैक खोए अपनी शॉर्टलिस्ट बनाएं.', + openPostcode: 'पोस्टकोड खोलें', + clickToRename: 'नाम बदलने के लिए क्लिक करें', + notesPlaceholder: 'अपने विचार लिखें...', + deleteSearch: 'खोज हटाएं', + deleteSearchConfirm: + 'क्या आप वाकई यह सहेजी गई खोज हटाना चाहते हैं? इसे वापस नहीं किया जा सकता.', + deleteProperty: 'संपत्ति हटाएं', + deletePropertyConfirm: + 'क्या आप वाकई यह सहेजी गई संपत्ति हटाना चाहते हैं? इसे वापस नहीं किया जा सकता.', + bed: 'bed', + epc: 'EPC', + }, + + invitesPage: { + inviteLinksLicensed: 'आमंत्रण लिंक लाइसेंस प्राप्त उपयोगकर्ताओं के लिए उपलब्ध हैं.', + inviteAdminLabel: 'दोस्तों को आमंत्रित करें (100% छूट)', + inviteReferralLabel: 'दोस्तों को आमंत्रित करें (30% छूट)', + generateFreeInvite: 'मुफ्त आमंत्रण लिंक बनाएं', + generateReferralLink: 'रेफरल लिंक बनाएं', + copyInviteLink: 'आमंत्रण लिंक कॉपी करें', + adminInvitesTitle: 'एडमिन आमंत्रण (100% छूट)', + referralInvitesTitle: 'रेफरल आमंत्रण (30% छूट)', + yourInviteLinks: 'आपके आमंत्रण लिंक', + noInvitesYet: 'अभी कोई आमंत्रण नहीं बनाया गया', + link: 'लिंक', + status: 'स्थिति', + created: 'बनाया गया', + redeemed: 'भुनाया गया', + pending: 'लंबित', + }, + + invitePage: { + youreInvited: 'आप आमंत्रित हैं!', + specialOffer: 'विशेष ऑफर!', + invitedByFree: '{{name}} ने आपको मुफ्त लाइफटाइम एक्सेस लेने के लिए आमंत्रित किया है.', + invitedByDiscount: '{{name}} ने लाइफटाइम एक्सेस पर 30% छूट साझा की है.', + genericFreeInvite: 'आपको मुफ्त लाइफटाइम एक्सेस लेने के लिए आमंत्रित किया गया है.', + genericDiscount: 'किसी दोस्त ने लाइफटाइम एक्सेस पर 30% छूट साझा की है.', + exploreEvery: 'वे पोस्टकोड खोजें जो आपकी जिंदगी से मेल खाते हैं', + propertyInfo: 'कीमतें, आवागमन, स्कूल, अपराध, शोर, ब्रॉडबैंड, EPC और अधिक', + invalidInvite: 'अमान्य आमंत्रण', + inviteAlreadyUsed: 'Invite पहले ही उपयोग हो चुका है', + inviteAlreadyUsedDesc: 'यह आमंत्रण लिंक पहले ही भुनाया जा चुका है.', + invalidInviteLink: 'अमान्य आमंत्रण लिंक', + invalidInviteLinkDesc: 'यह आमंत्रण लिंक अमान्य है या समाप्त हो चुका है.', + licenseActivated: 'लाइसेंस सक्रिय हो गया!', + fullAccessGranted: 'अब आपके पास Perfect Postcode का पूरा एक्सेस है.', + activating: 'सक्रिय किया जा रहा है...', + activateLicense: 'लाइसेंस सक्रिय करें', + claimDiscount: 'छूट लें', + registerToClaim: 'लेने के लिए रजिस्टर करें', + youAlreadyHaveLicense: 'आपके पास पहले से लाइसेंस है', + accountHasFullAccess: 'आपके खाते में पहले से पूरा एक्सेस है.', + failedToValidate: 'आमंत्रण लिंक सत्यापित नहीं हो सका', + }, + + mapPage: { + unsavedProperty: 'असहेजें', + savedProperty: 'सहेजा गया', + }, + + format: { + justNow: 'अभी', + minutesAgo: '{{count}}m पहले', + hoursAgo: '{{count}}h पहले', + daysAgo: '{{count}}d पहले', + nFilters: '{{count}} फिल्टर', + noFilters: 'कोई फिल्टर नहीं', + poiCategory: '{{count}} POI श्रेणी', + poiCategories: '{{count}} POI श्रेणियां', + travelDestination: '{{count}} यात्रा-समय गंतव्य', + travelDestinations: '{{count}} यात्रा-समय गंतव्य', + propertiesMatch: '{{count}} संपत्तियां मेल खाती हैं', + setFilters: '{{count}} फिल्टर सेट करें: {{list}}', + noFiltersSet: 'कोई फिल्टर सेट नहीं', + toDestination: '{{mode}} से {{label}} तक {{bounds}}', + lessThanMin: '< {{max}} मिनट', + moreThanMin: '> {{min}} मिनट', + }, + + tutorial: { + step1Title: 'मानचित्र को बताएं क्या मायने रखता है', + step1Content: + 'अपना बजट, आवागमन सीमा, स्कूल गुणवत्ता, अपराध सीमा, शोर सहनशीलता, ब्रॉडबैंड जरूरतें या जो भी आपके लिए मायने रखता है सेट करें. केवल मेल खाते क्षेत्र रोशन रहते हैं. किसी भी फीचर से रंगने के लिए आंख वाले आइकन का उपयोग करें.', + step2Title: 'या बस वर्णन करें', + step2Content: + 'साधारण भाषा में लिखें, जैसे "quiet area near good schools under £400k", और हम आपके लिए फिल्टर सेट कर देंगे.', + step3Title: 'देखें क्या उपलब्ध है', + step3Content: + 'इंग्लैंड भर में पैन और जूम करें. किसी भी रंगीन क्षेत्र पर क्लिक करें और देखें यह क्यों मेल खाता है: अपराध, स्कूल, कीमतें, ब्रॉडबैंड, शोर और अधिक.', + step4Title: 'किसी स्थान पर जाएं', + step4Content: 'सीधे वहां जाने के लिए कोई भी जगह या पोस्टकोड खोजें.', + step5Title: 'विवरण में जाएं', + step5Content: + 'क्षेत्रीय आंकड़े, हिस्टोग्राम और अलग-अलग संपत्ति रिकॉर्ड देखें: कीमतें, फर्श क्षेत्र, ऊर्जा रेटिंग और अधिक.', + step6Title: 'पास में क्या है?', + step6Content: + 'मानचित्र पर स्कूल, दुकानें, स्टेशन, पार्क और रेस्तरां चालू करें और देखें क्या पहुंच में है.', + }, + + server: { + Properties: 'संपत्तियां', + Transport: 'परिवहन', + Education: 'शिक्षा', + Deprivation: 'वंचना', + Crime: 'अपराध', + Demographics: 'जनसांख्यिकी', + Politics: 'राजनीति', + Amenities: 'सुविधाएं', + 'Property type': 'संपत्ति प्रकार', + 'Leasehold/Freehold': 'लीजहोल्ड/फ्रीहोल्ड', + 'Last known price': 'अंतिम ज्ञात कीमत', + 'Estimated current price': 'अनुमानित मौजूदा कीमत', + 'Price per sqm': 'प्रति वर्ग मी कीमत', + 'Est. price per sqm': 'अनु. प्रति वर्ग मी कीमत', + 'Estimated monthly rent': 'अनुमानित मासिक किराया', + 'Total floor area (sqm)': 'कुल फर्श क्षेत्र (वर्ग मी)', + 'Number of bedrooms & living rooms': 'बेडरूम और लिविंग रूम की संख्या', + 'Construction year': 'निर्माण वर्ष', + 'Date of last transaction': 'अंतिम लेनदेन की तारीख', + 'Former council house': 'पूर्व काउंसिल घर', + 'Current energy rating': 'मौजूदा ऊर्जा रेटिंग', + 'Potential energy rating': 'संभावित ऊर्जा रेटिंग', + 'Interior height (m)': 'भीतरी ऊंचाई (मी)', + 'Distance to nearest train or tube station (km)': 'निकटतम ट्रेन या ट्यूब स्टेशन तक दूरी (किमी)', + 'Good+ primary schools within 2km': '2 किमी के अंदर Good+ प्राथमिक स्कूल', + 'Good+ secondary schools within 2km': '2 किमी के अंदर Good+ सेकेंडरी स्कूल', + 'Good+ primary schools within 5km': '5 किमी के अंदर Good+ प्राथमिक स्कूल', + 'Good+ secondary schools within 5km': '5 किमी के अंदर Good+ सेकेंडरी स्कूल', + 'Outstanding primary schools within 2km': '2 किमी के अंदर Outstanding प्राथमिक स्कूल', + 'Outstanding secondary schools within 2km': '2 किमी के अंदर Outstanding सेकेंडरी स्कूल', + 'Outstanding primary schools within 5km': '5 किमी के अंदर Outstanding प्राथमिक स्कूल', + 'Outstanding secondary schools within 5km': '5 किमी के अंदर Outstanding सेकेंडरी स्कूल', + 'Education, Skills and Training Score': 'शिक्षा, कौशल और प्रशिक्षण स्कोर', + 'Income Score (rate)': 'आय स्कोर (दर)', + 'Employment Score (rate)': 'रोजगार स्कोर (दर)', + 'Health Deprivation and Disability Score': 'स्वास्थ्य वंचना और विकलांगता स्कोर', + 'Living Environment Score': 'रहने के वातावरण का स्कोर', + 'Indoors Sub-domain Score': 'इनडोर उप-क्षेत्र स्कोर', + 'Outdoors Sub-domain Score': 'आउटडोर उप-क्षेत्र स्कोर', + 'Serious crime per 1k residents (avg/yr)': 'प्रति 1k निवासियों गंभीर अपराध (औसत/वर्ष)', + 'Minor crime per 1k residents (avg/yr)': 'प्रति 1k निवासियों मामूली अपराध (औसत/वर्ष)', + 'Serious crime (avg/yr)': 'गंभीर अपराध (औसत/वर्ष)', + 'Minor crime (avg/yr)': 'मामूली अपराध (औसत/वर्ष)', + 'Violence and sexual offences (avg/yr)': 'हिंसा और यौन अपराध (औसत/वर्ष)', + 'Burglary (avg/yr)': 'सेंधमारी (औसत/वर्ष)', + 'Robbery (avg/yr)': 'लूट (औसत/वर्ष)', + 'Vehicle crime (avg/yr)': 'वाहन अपराध (औसत/वर्ष)', + 'Anti-social behaviour (avg/yr)': 'असामाजिक व्यवहार (औसत/वर्ष)', + 'Criminal damage and arson (avg/yr)': 'आपराधिक क्षति और आगजनी (औसत/वर्ष)', + 'Other theft (avg/yr)': 'अन्य चोरी (औसत/वर्ष)', + 'Theft from the person (avg/yr)': 'व्यक्ति से चोरी (औसत/वर्ष)', + 'Shoplifting (avg/yr)': 'दुकान से चोरी (औसत/वर्ष)', + 'Bicycle theft (avg/yr)': 'साइकिल चोरी (औसत/वर्ष)', + 'Drugs (avg/yr)': 'ड्रग्स (औसत/वर्ष)', + 'Possession of weapons (avg/yr)': 'हथियार रखने के अपराध (औसत/वर्ष)', + 'Public order (avg/yr)': 'सार्वजनिक व्यवस्था अपराध (औसत/वर्ष)', + 'Other crime (avg/yr)': 'अन्य अपराध (औसत/वर्ष)', + 'Median age': 'मीडियन आयु', + '% White': '% श्वेत', + '% South Asian': '% दक्षिण एशियाई', + '% Black': '% अश्वेत', + '% East Asian': '% पूर्वी एशियाई', + '% Mixed': '% मिश्रित', + '% Other': '% अन्य', + 'Voter turnout (%)': 'मतदाता भागीदारी (%)', + '% Labour': '% लेबर', + '% Conservative': '% कंज़र्वेटिव', + '% Liberal Democrat': '% लिबरल डेमोक्रेट', + '% Reform UK': '% Reform UK', + '% Green': '% ग्रीन', + '% Other parties': '% अन्य पार्टियां', + 'Distance to nearest park (km)': 'निकटतम पार्क तक दूरी (किमी)', + 'Number of parks within 1km': '1 किमी के अंदर पार्कों की संख्या', + 'Number of restaurants within 2km': '2 किमी के अंदर रेस्तरां की संख्या', + 'Number of grocery shops and supermarkets within 2km': + '2 किमी के अंदर किराना दुकानों और सुपरमार्केट की संख्या', + 'Noise (dB)': 'शोर (dB)', + 'Max available download speed (Mbps)': 'अधिकतम उपलब्ध डाउनलोड स्पीड (Mbps)', + Detached: 'अलग मकान', + 'Semi-Detached': 'अर्ध-स्वतंत्र मकान', + Terraced: 'कतारबद्ध मकान', + 'Flats/Maisonettes': 'फ्लैट/मेज़ोनेट', + Other: 'अन्य', + Freehold: 'फ्रीहोल्ड', + Leasehold: 'लीजहोल्ड', + Yes: 'हां', + No: 'नहीं', + 'Serious crime': 'गंभीर अपराध', + 'Minor crime': 'मामूली अपराध', + 'Ethnic composition': 'जातीय संरचना', + 'Political vote share': 'राजनीतिक वोट शेयर', + 'Public Transport': 'सार्वजनिक परिवहन', + Leisure: 'अवकाश', + Health: 'स्वास्थ्य', + 'Emergency Services': 'आपातकालीन सेवाएं', + Groceries: 'किराना', + 'Local Businesses': 'स्थानीय व्यवसाय', + Culture: 'संस्कृति', + Services: 'सेवाएं', + Shops: 'दुकानें', + Airport: 'हवाई अड्डा', + Ferry: 'फेरी', + 'Rail station': 'रेल स्टेशन', + 'Bus stop': 'बस स्टॉप', + 'Bus station': 'बस स्टेशन', + 'Taxi rank': 'टैक्सी स्टैंड', + 'Tube station': 'ट्यूब स्टेशन', + Café: 'कैफे', + Restaurant: 'रेस्तरां', + Pub: 'पब', + Bar: 'बार', + 'Fast Food': 'फास्ट फूड', + Nightclub: 'नाइटक्लब', + Cinema: 'सिनेमा', + Theatre: 'थिएटर', + 'Live Music & Events': 'लाइव संगीत और कार्यक्रम', + Park: 'पार्क', + Playground: 'खेल का मैदान', + 'Sports Centre': 'खेल केंद्र', + Entertainment: 'मनोरंजन', + Supermarket: 'सुपरमार्केट', + 'Convenience Store': 'कन्वीनियंस स्टोर', + Bakery: 'बेकरी', + 'Butcher & Fishmonger': 'कसाई और मछली विक्रेता', + Greengrocer: 'सब्जी-फल विक्रेता', + 'Off-Licence': 'शराब की दुकान', + 'Deli & Specialty': 'डेली और विशेष खाद्य', + 'Fashion & Clothing': 'फैशन और कपड़े', + Electronics: 'इलेक्ट्रॉनिक्स', + 'Charity Shop': 'चैरिटी दुकान', + 'DIY & Hardware': 'DIY और हार्डवेयर', + 'Home & Garden': 'घर और बगीचा', + Bookshop: 'किताबों की दुकान', + 'Pet Shop': 'पालतू पशु दुकान', + 'Sports & Outdoor': 'खेल और आउटडोर', + Newsagent: 'अखबार विक्रेता', + 'Department Store': 'डिपार्टमेंट स्टोर', + 'Gift & Hobby': 'उपहार और शौक', + 'Specialist Shop': 'विशेषज्ञ दुकान', + 'Hairdresser & Beauty': 'हेयरड्रेसर और सौंदर्य', + 'Gym & Fitness': 'जिम और फिटनेस', + 'Dry Cleaner & Laundry': 'ड्राई क्लीनर और लॉन्ड्री', + 'Car Services': 'कार सेवाएं', + 'Post Office': 'डाकघर', + 'Vet & Pet Care': 'पशु चिकित्सक और पालतू देखभाल', + Bank: 'बैंक', + 'Travel Agent': 'ट्रैवल एजेंट', + Police: 'पुलिस', + 'Fire Station': 'फायर स्टेशन', + 'Ambulance Station': 'एम्बुलेंस स्टेशन', + 'GP Surgery': 'GP क्लिनिक', + Dentist: 'दंत चिकित्सक', + Pharmacy: 'फार्मेसी', + 'Hospital & Clinic': 'अस्पताल और क्लिनिक', + Optician: 'ऑप्टिशियन', + Physiotherapy: 'फिजियोथेरेपी', + 'Counselling & Therapy': 'काउंसलिंग और थेरेपी', + 'Care Home': 'देखभाल गृह', + 'Medical & Mobility': 'चिकित्सा और गतिशीलता उपकरण', + Museum: 'संग्रहालय', + Gallery: 'गैलरी', + Library: 'लाइब्रेरी', + 'Place of Worship': 'पूजा स्थल', + 'Arts Centre': 'कला केंद्र', + Zoo: 'चिड़ियाघर', + 'Tourist Attraction': 'पर्यटक आकर्षण', + School: 'स्कूल', + Hotel: 'होटल', + 'Local Business': 'स्थानीय व्यवसाय', + Offices: 'कार्यालय', + 'EV Charging': 'EV चार्जिंग', + 'Fuel Station': 'ईंधन स्टेशन', + 'Community Centre': 'सामुदायिक केंद्र', + '/mo': '/माह', + '/yr': '/वर्ष', + ' sqm': ' वर्ग मी', + ' km': ' किमी', + ' m': ' मी', + ' dB': ' dB', + ' years': ' वर्ष', + ' rooms': ' कमरे', + }, +}; + +export default hi; diff --git a/frontend/src/index.css b/frontend/src/index.css index f38cbab..ca967c9 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -23,6 +23,11 @@ html.dark { color-scheme: dark; } +.home-page-scroll, +.dark .home-page-scroll { + background: linear-gradient(180deg, #080d19 0%, #080d19 50%, #16a34a 50%, #16a34a 100%); +} + /* Smooth theme transitions (scoped to avoid map performance issues) */ body, div, @@ -65,6 +70,50 @@ h3 { } } +.home-content-surface { + isolation: isolate; + background: linear-gradient(180deg, #f3efe8 0%, #fafaf9 36%, #eef7f3 100%); +} + +.home-content-surface::before { + content: ''; + position: absolute; + inset: -120vh -120vw; + z-index: 0; + pointer-events: none; + background: + linear-gradient(rgba(20, 184, 166, 0.11) 1px, transparent 1px), + linear-gradient(90deg, rgba(20, 184, 166, 0.11) 1px, transparent 1px); + background-size: + 56px 56px, + 56px 56px; + transform: skew(20deg, -15deg); + transform-origin: center; +} + +.home-content-surface::after { + content: ''; + position: absolute; + inset: 0; + z-index: 1; + pointer-events: none; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.42), rgba(255, 255, 255, 0)); +} + +.dark .home-content-surface { + background: linear-gradient(180deg, #121827 0%, #0a0e1a 42%, #10211f 100%); +} + +.dark .home-content-surface::before { + background: + linear-gradient(rgba(45, 212, 191, 0.1) 1px, transparent 1px), + linear-gradient(90deg, rgba(45, 212, 191, 0.1) 1px, transparent 1px); +} + +.dark .home-content-surface::after { + background: linear-gradient(180deg, rgba(10, 14, 26, 0.22), rgba(10, 14, 26, 0)); +} + /* Fade-in animation for homepage sections */ .fade-in-section { opacity: 0; @@ -91,76 +140,92 @@ h3 { .showcase-progress { animation-name: showcase-progress; animation-timing-function: linear; + animation-iteration-count: infinite; animation-fill-mode: forwards; } +@keyframes scout-export-click { + 0%, + 52%, + 100% { + transform: translateY(0) scale(1); + box-shadow: 0 10px 18px rgba(15, 118, 110, 0.18); + } + 58% { + transform: translateY(2px) scale(0.985); + box-shadow: 0 4px 8px rgba(15, 118, 110, 0.18); + } + 66% { + transform: translateY(0) scale(1.02); + box-shadow: 0 14px 24px rgba(15, 118, 110, 0.24); + } +} + +@keyframes scout-export-ripple { + 0%, + 54%, + 100% { + opacity: 0; + transform: translate(-50%, -50%) scale(0.25); + } + 60% { + opacity: 0.28; + transform: translate(-50%, -50%) scale(0.8); + } + 78% { + opacity: 0; + transform: translate(-50%, -50%) scale(2.4); + } +} + +@keyframes scout-export-check { + 0%, + 62%, + 100% { + opacity: 0; + transform: scale(0.65); + } + 70%, + 90% { + opacity: 1; + transform: scale(1); + } +} + +.scout-export-action { + animation: scout-export-click 3.2s ease-in-out infinite; +} + +.scout-export-ripple { + position: absolute; + left: 50%; + top: 50%; + width: 7rem; + height: 7rem; + border-radius: 9999px; + background: rgba(255, 255, 255, 0.55); + animation: scout-export-ripple 3.2s ease-out infinite; +} + +.scout-export-check { + animation: scout-export-check 3.2s ease-in-out infinite; +} + @media (prefers-reduced-motion: reduce) { .showcase-progress { animation: none !important; transform: scaleX(1); } -} -/* Aurora gradient animation for pricing hero */ -@keyframes aurora-1 { - 0%, - 100% { - transform: translate(0, 0) scale(1); + .scout-export-action, + .scout-export-ripple, + .scout-export-check { + animation: none !important; } - 33% { - transform: translate(30px, -20px) scale(1.1); - } - 66% { - transform: translate(-20px, 15px) scale(0.9); - } -} -@keyframes aurora-2 { - 0%, - 100% { - transform: translate(0, 0) scale(1); - } - 33% { - transform: translate(-40px, 20px) scale(1.15); - } - 66% { - transform: translate(25px, -30px) scale(0.95); - } -} - -@keyframes aurora-3 { - 0%, - 100% { - transform: translate(0, 0) scale(1) rotate(0deg); - } - 50% { - transform: translate(20px, 25px) scale(1.1) rotate(3deg); - } -} - -@keyframes aurora-4 { - 0%, - 100% { - transform: translate(0, 0) scale(1) rotate(0deg); - } - 40% { - transform: translate(-35px, -15px) scale(1.2) rotate(-2deg); - } - 70% { - transform: translate(15px, 20px) scale(0.9) rotate(1deg); - } -} - -@keyframes aurora-5 { - 0%, - 100% { - transform: translate(0, 0) scale(1); - } - 30% { - transform: translate(25px, 30px) scale(1.15); - } - 60% { - transform: translate(-30px, -10px) scale(0.95); + .scout-export-check { + opacity: 1; + transform: scale(1); } } diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 7ba9ccf..fb1fbc6 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -1,6 +1,6 @@ import { createRoot, hydrateRoot } from 'react-dom/client'; import App from './App'; -import './i18n'; +import { INITIAL_LANGUAGE, i18nReady } from './i18n'; import './index.css'; import './hooks/usePlausible'; @@ -8,9 +8,21 @@ const container = document.getElementById('root'); if (!container) { throw new Error('Root element not found'); } +const root = container; -if (container.children.length > 0) { - hydrateRoot(container, ); -} else { - createRoot(container).render(); +function renderApp() { + const hasPrerenderedMarkup = root.children.length > 0; + + if (hasPrerenderedMarkup && INITIAL_LANGUAGE === 'en') { + hydrateRoot(root, ); + return; + } + + if (hasPrerenderedMarkup) { + root.textContent = ''; + } + + createRoot(root).render(); } + +void i18nReady.then(renderApp); diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 3284fb6..e9718f1 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -110,6 +110,13 @@ export interface PlaceResult { city?: string; } +export interface AddressResult { + address: string; + postcode: string; + lat: number; + lon: number; +} + export interface JourneyLeg { mode: string; from?: string; diff --git a/video/package.json b/video/package.json index fd50735..f158f1b 100644 --- a/video/package.json +++ b/video/package.json @@ -6,10 +6,11 @@ "type": "module", "scripts": { "build": "tsc", + "bootstrap-admin": "tsc && node dist/pb-admin.js", "setup-auth": "tsc && node dist/auth.js", "record": "tsc && node dist/record.js", "record:vertical": "tsc && ASPECT=9x16 node dist/record.js", - "encode": "ffmpeg -y -i output/recording.webm -c:v libx264 -pix_fmt yuv420p -crf 16 -preset slow -movflags +faststart output/recording.mp4", + "encode": "ffmpeg -y -i output/recording.webm -c:v libx264 -pix_fmt yuv420p -crf 14 -preset fast -movflags +faststart output/recording.mp4", "render": "./render.sh" }, "dependencies": { diff --git a/video/render.sh b/video/render.sh index 6585c12..09e2d59 100755 --- a/video/render.sh +++ b/video/render.sh @@ -19,10 +19,24 @@ set -euo pipefail APP_URL="${APP_URL:-http://host.docker.internal:3001}" PB_URL="${PB_URL:-http://host.docker.internal:8090}" API_URL="${API_URL:-http://host.docker.internal:8001}" +PB_ADMIN_EMAIL="${PB_ADMIN_EMAIL:-admin@propertymap.local}" +PB_ADMIN_PASSWORD="${PB_ADMIN_PASSWORD:-propertymap-dev-2024}" PB_EMAIL="${PB_EMAIL:-demo-video@local.test}" PB_PASSWORD="${PB_PASSWORD:-DemoVideoPass123!}" MAX_DURATION_S="${MAX_DURATION_S:-15}" +RECORD_SCALE="${RECORD_SCALE:-2}" # 2x raw capture -> real 50fps after speed-up +OUTPUT_FPS="${OUTPUT_FPS:-50}" # matches RECORD_SCALE=2 output cadence +CAPTURE_SCALE="${CAPTURE_SCALE:-1.5}" # sharper than 1x without the 2x software-GL cost AUTH_TTL_HOURS="${AUTH_TTL_HOURS:-24}" # re-auth if auth.json older than this +# Where the homepage