This commit is contained in:
@@ -290,7 +290,36 @@ const translations = {
|
||||
'booking.base_price': 'Базовая стоимость:',
|
||||
'booking.discount': 'Скидка',
|
||||
'booking.total': 'Итого к оплате:',
|
||||
'booking.submit': 'Отправить заявку'
|
||||
'booking.submit': 'Отправить заявку',
|
||||
'booking.sending': 'Отправка...',
|
||||
'booking.success_sent': 'Заявка отправлена Рауфу Алексеевичу!',
|
||||
'booking.send_error': 'Ошибка отправки',
|
||||
'booking.server_error': 'Ошибка сервера',
|
||||
'booking.checkin_past': 'Дата заезда не может быть в прошлом',
|
||||
'booking.checkout_before_checkin': 'Дата выезда должна быть позже даты заезда',
|
||||
'booking.promocode_invalid': 'Промокод не найден или срок его действия истёк',
|
||||
'booking.promocode_error': 'Ошибка проверки промокода',
|
||||
|
||||
// Rooms JS
|
||||
'rooms.floors': 'Этажи',
|
||||
'rooms.extra_beds': 'Возможно',
|
||||
'rooms.extra_beds_rest': 'доп. мест',
|
||||
'rooms.from': 'от',
|
||||
'rooms.per_night': '/ ночь',
|
||||
'rooms.book': 'Забронировать',
|
||||
'rooms.no_rooms': 'Нет мест',
|
||||
'rooms.rooms_left': 'Мест:',
|
||||
'rooms.available': 'Свободно:',
|
||||
|
||||
// Amenities
|
||||
'amenity.ac': 'Кондиционер',
|
||||
'amenity.tv': 'ТВ',
|
||||
'amenity.fridge': 'Холодильник',
|
||||
'amenity.wifi': 'WiFi',
|
||||
'amenity.kettle': 'Чайник',
|
||||
'amenity.hairdryer': 'Фен',
|
||||
'amenity.shower': 'Душ в номере',
|
||||
'amenity.sea_view': 'Вид на море'
|
||||
},
|
||||
|
||||
en: {
|
||||
@@ -584,7 +613,36 @@ const translations = {
|
||||
'booking.base_price': 'Base price:',
|
||||
'booking.discount': 'Discount',
|
||||
'booking.total': 'Total to pay:',
|
||||
'booking.submit': 'Send Request'
|
||||
'booking.submit': 'Send Request',
|
||||
'booking.sending': 'Sending...',
|
||||
'booking.success_sent': 'Request sent to Rauf Alekseevich!',
|
||||
'booking.send_error': 'Sending Error',
|
||||
'booking.server_error': 'Server Error',
|
||||
'booking.checkin_past': 'Check-in date cannot be in the past',
|
||||
'booking.checkout_before_checkin': 'Check-out date must be after check-in date',
|
||||
'booking.promocode_invalid': 'Promo code not found or expired',
|
||||
'booking.promocode_error': 'Promo code check error',
|
||||
|
||||
// Rooms JS
|
||||
'rooms.floors': 'Floors',
|
||||
'rooms.extra_beds': 'Available',
|
||||
'rooms.extra_beds_rest': 'extra beds',
|
||||
'rooms.from': 'from',
|
||||
'rooms.per_night': '/ night',
|
||||
'rooms.book': 'Book',
|
||||
'rooms.no_rooms': 'No rooms',
|
||||
'rooms.rooms_left': 'Left:',
|
||||
'rooms.available': 'Available:',
|
||||
|
||||
// Amenities
|
||||
'amenity.ac': 'Air Conditioning',
|
||||
'amenity.tv': 'TV',
|
||||
'amenity.fridge': 'Fridge',
|
||||
'amenity.wifi': 'WiFi',
|
||||
'amenity.kettle': 'Kettle',
|
||||
'amenity.hairdryer': 'Hair Dryer',
|
||||
'amenity.shower': 'En-suite Shower',
|
||||
'amenity.sea_view': 'Sea View'
|
||||
},
|
||||
|
||||
ab: {
|
||||
@@ -881,7 +939,36 @@ const translations = {
|
||||
'booking.base_price': 'Абазатә ахә:',
|
||||
'booking.discount': 'Аскидка',
|
||||
'booking.total': 'Зегьы ашәахә:',
|
||||
'booking.submit': 'Азаиака аашьҭра'
|
||||
'booking.submit': 'Азаиака аашьҭра',
|
||||
'booking.sending': 'Иаашьҭуеит...',
|
||||
'booking.success_sent': 'Азаиака Рауф Алексееви иахь иаашьҭын!',
|
||||
'booking.send_error': 'Аашьҭра агха',
|
||||
'booking.server_error': 'Асервер агха',
|
||||
'booking.checkin_past': 'Анеира арыцхә ааигәа иҟаӡам',
|
||||
'booking.checkout_before_checkin': 'Алҵра арыцхә анеира арыцхә иашьҭахь акәзароуп',
|
||||
'booking.promocode_invalid': 'Апромокод иԥшаам ма аамҭа иацәын',
|
||||
'booking.promocode_error': 'Апромокод агәаҭара агха',
|
||||
|
||||
// Rooms JS
|
||||
'rooms.floors': 'Аихақәа',
|
||||
'rooms.extra_beds': 'Иаԥсоит',
|
||||
'rooms.extra_beds_rest': 'доп. тыԥ',
|
||||
'rooms.from': 'иқәынтәи',
|
||||
'rooms.per_night': '/ ахә',
|
||||
'rooms.book': 'Абронира',
|
||||
'rooms.no_rooms': 'Апалаҭа ыҟам',
|
||||
'rooms.rooms_left': 'Иаанхоит:',
|
||||
'rooms.available': 'Иахьазы:',
|
||||
|
||||
// Amenities
|
||||
'amenity.ac': 'Акондиционер',
|
||||
'amenity.tv': 'АТВ',
|
||||
'amenity.fridge': 'Аршәашьика',
|
||||
'amenity.wifi': 'WiFi',
|
||||
'amenity.kettle': 'Ачаиник',
|
||||
'amenity.hairdryer': 'Афен',
|
||||
'amenity.shower': 'Адуш апалаҭаҟны',
|
||||
'amenity.sea_view': 'Амшын ахсаала'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
// Preloader
|
||||
function rt(key, fallback) {
|
||||
return (window.I18n && typeof window.I18n.t === 'function') ? window.I18n.t(key) : fallback;
|
||||
}
|
||||
window.addEventListener('load', () => {
|
||||
setTimeout(() => {
|
||||
document.getElementById('preloader').classList.add('hidden');
|
||||
@@ -83,7 +86,8 @@ function updateGuestOptionsDynamic(roomType, maxGuests) {
|
||||
|
||||
let options = [];
|
||||
for (let i = 1; i <= maxGuests; i++) {
|
||||
let text = i === 1 ? '1 гость' : (i < 5 ? `${i} гостя` : `${i} гостей`);
|
||||
const key = 'booking.guest_' + i;
|
||||
let text = rt(key, i === 1 ? '1 гость' : (i < 5 ? i + ' гостя' : i + ' гостей'));
|
||||
options.push(`<option value="${i}">${text}</option>`);
|
||||
}
|
||||
|
||||
@@ -102,7 +106,8 @@ function updateGuestOptions(room) {
|
||||
const maxGuests = ROOM_MAX_GUESTS[room] || 4;
|
||||
let options = [];
|
||||
for (let i = 1; i <= maxGuests; i++) {
|
||||
let text = i === 1 ? '1 гость' : (i < 5 ? `${i} гостя` : `${i} гостей`);
|
||||
const key = 'booking.guest_' + i;
|
||||
let text = rt(key, i === 1 ? '1 гость' : (i < 5 ? i + ' гостя' : i + ' гостей'));
|
||||
options.push(`<option value="${i}">${text}</option>`);
|
||||
}
|
||||
|
||||
@@ -114,21 +119,21 @@ function updateGuestOptionsFallback(room) {
|
||||
if (!guestsSelect) return;
|
||||
|
||||
const options2x = [
|
||||
{ value: 1, text: '1 гость' },
|
||||
{ value: 2, text: '2 гостя' }
|
||||
{ value: 1, text: rt('booking.guest_1', '1 гость') },
|
||||
{ value: 2, text: rt('booking.guest_2', '2 гостя') }
|
||||
];
|
||||
|
||||
const options3x = [
|
||||
{ value: 1, text: '1 гость' },
|
||||
{ value: 2, text: '2 гостя' },
|
||||
{ value: 3, text: '3 гостя' }
|
||||
{ value: 1, text: rt('booking.guest_1', '1 гость') },
|
||||
{ value: 2, text: rt('booking.guest_2', '2 гостя') },
|
||||
{ value: 3, text: rt('booking.guest_3', '3 гостя') }
|
||||
];
|
||||
|
||||
const optionsDefault = [
|
||||
{ value: 1, text: '1 гость' },
|
||||
{ value: 2, text: '2 гостя' },
|
||||
{ value: 3, text: '3 гостя' },
|
||||
{ value: 4, text: '4 гостя' }
|
||||
{ value: 1, text: rt('booking.guest_1', '1 гость') },
|
||||
{ value: 2, text: rt('booking.guest_2', '2 гостя') },
|
||||
{ value: 3, text: rt('booking.guest_3', '3 гостя') },
|
||||
{ value: 4, text: rt('booking.guest_4', '4 гостя') }
|
||||
];
|
||||
|
||||
let options = optionsDefault;
|
||||
@@ -160,7 +165,7 @@ function validateBookingDates() {
|
||||
if (checkinInput.value) {
|
||||
const checkinDate = new Date(checkinInput.value);
|
||||
if (checkinDate < today) {
|
||||
checkinInput.setCustomValidity('Дата заезда не может быть в прошлом');
|
||||
checkinInput.setCustomValidity(rt('booking.checkin_past', 'Дата заезда не может быть в прошлом'));
|
||||
} else {
|
||||
checkinInput.setCustomValidity('');
|
||||
}
|
||||
@@ -171,7 +176,7 @@ function validateBookingDates() {
|
||||
const checkoutDate = new Date(checkoutInput.value);
|
||||
|
||||
if (checkoutDate <= checkinDate) {
|
||||
checkoutInput.setCustomValidity('Дата выезда должна быть позже даты заезда');
|
||||
checkoutInput.setCustomValidity(rt('booking.checkout_before_checkin', 'Дата выезда должна быть позже даты заезда'));
|
||||
} else {
|
||||
checkoutInput.setCustomValidity('');
|
||||
}
|
||||
@@ -237,11 +242,11 @@ async function checkPromocode() {
|
||||
} else {
|
||||
currentPromocodeData = null;
|
||||
updatePriceDisplay(basePrice, 0, 0, basePrice);
|
||||
showPromocodeError('Промокод не найден или срок его действия истёк');
|
||||
showPromocodeError(rt('booking.promocode_invalid', 'Промокод не найден или срок его действия истёк'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Promocode validation error:', error);
|
||||
showPromocodeError('Ошибка проверки промокода');
|
||||
showPromocodeError(rt('booking.promocode_error', 'Ошибка проверки промокода'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +282,7 @@ document.getElementById('bookingForm').addEventListener('submit', async function
|
||||
const btn = this.querySelector('.btn-submit-booking');
|
||||
const originalText = btn.innerHTML;
|
||||
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Отправка...';
|
||||
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>' + rt('booking.sending', 'Отправка...');
|
||||
btn.disabled = true;
|
||||
|
||||
const formData = {
|
||||
@@ -301,7 +306,7 @@ document.getElementById('bookingForm').addEventListener('submit', async function
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
btn.innerHTML = '<i class="fas fa-check me-2"></i>Заявка отправлена Рауфу Алексеевичу!';
|
||||
btn.innerHTML = '<i class="fas fa-check me-2"></i>' + rt('booking.success_sent', 'Заявка отправлена Рауфу Алексеевичу!');
|
||||
btn.style.background = '#25d366';
|
||||
this.reset();
|
||||
hidePriceInfo();
|
||||
@@ -313,11 +318,11 @@ document.getElementById('bookingForm').addEventListener('submit', async function
|
||||
}, 2500);
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || 'Ошибка сервера');
|
||||
throw new Error(errorData.error || rt('booking.server_error', 'Ошибка сервера'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Booking error:', error);
|
||||
btn.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>Ошибка отправки';
|
||||
btn.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>' + rt('booking.send_error', 'Ошибка отправки');
|
||||
btn.style.background = '#c9302c';
|
||||
setTimeout(() => {
|
||||
btn.innerHTML = originalText;
|
||||
|
||||
@@ -503,6 +503,9 @@ async function switchLang(lang) {
|
||||
populateCountrySelect();
|
||||
}
|
||||
loadReviews();
|
||||
if (typeof loadRoomsPublic === 'function') {
|
||||
loadRoomsPublic();
|
||||
}
|
||||
document.documentElement.lang = lang;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
const AMENITY_ICONS = {
|
||||
has_ac: { icon: 'fa-snowflake', label: 'Кондиционер' },
|
||||
has_tv: { icon: 'fa-tv', label: 'ТВ' },
|
||||
has_fridge: { icon: 'fa-sink', label: 'Холодильник' },
|
||||
has_wifi: { icon: 'fa-wifi', label: 'WiFi' },
|
||||
has_kettle: { icon: 'fa-mug-hot', label: 'Чайник' },
|
||||
has_hairdryer: { icon: 'fa-wind', label: 'Фен' },
|
||||
has_shower: { icon: 'fa-shower', label: 'Душ в номере' },
|
||||
has_sea_view: { icon: 'fa-water', label: 'Вид на море' }
|
||||
const AMENITY_KEYS = {
|
||||
has_ac: { icon: 'fa-snowflake', key: 'amenity.ac' },
|
||||
has_tv: { icon: 'fa-tv', key: 'amenity.tv' },
|
||||
has_fridge: { icon: 'fa-sink', key: 'amenity.fridge' },
|
||||
has_wifi: { icon: 'fa-wifi', key: 'amenity.wifi' },
|
||||
has_kettle: { icon: 'fa-mug-hot', key: 'amenity.kettle' },
|
||||
has_hairdryer: { icon: 'fa-wind', key: 'amenity.hairdryer' },
|
||||
has_shower: { icon: 'fa-shower', key: 'amenity.shower' },
|
||||
has_sea_view: { icon: 'fa-water', key: 'amenity.sea_view' }
|
||||
};
|
||||
|
||||
function rt(key, fallback) {
|
||||
return (window.I18n && typeof window.I18n.t === 'function') ? window.I18n.t(key) : fallback;
|
||||
}
|
||||
|
||||
let cachedRooms = [];
|
||||
|
||||
async function loadRoomsPublic() {
|
||||
@@ -69,15 +73,15 @@ function renderRoomsPublic(rooms) {
|
||||
}
|
||||
|
||||
const amenitiesHtml = amenities.map(a => {
|
||||
const info = AMENITY_ICONS[a];
|
||||
const info = AMENITY_KEYS[a];
|
||||
if (!info) return '';
|
||||
return `<span class="room-feature-tag"><i class="fas ${info.icon}"></i> ${info.label}</span>`;
|
||||
return `<span class="room-feature-tag"><i class="fas ${info.icon}"></i> ${rt(info.key, a)}</span>`;
|
||||
}).join('');
|
||||
|
||||
const floorsStr = floors.length > 0 ? 'Этажи ' + floors.join(', ') : '';
|
||||
const floorsStr = floors.length > 0 ? rt('rooms.floors', 'Этажи') + ' ' + floors.join(', ') : '';
|
||||
|
||||
const extraBedsHtml = room.extra_beds > 0
|
||||
? `<div class="room-extra-hint">Возможно +${room.extra_beds} доп. мест (${room.extra_bed_price} ₽)</div>`
|
||||
? `<div class="room-extra-hint">${rt('rooms.extra_beds', 'Возможно').replace('{n}', room.extra_beds).replace('{price}', room.extra_bed_price)} +${room.extra_beds} ${rt('rooms.extra_beds_rest', 'доп. мест')} (${room.extra_bed_price} ₽)</div>`
|
||||
: '';
|
||||
|
||||
const featuredClass = room.type === 'Семейный' ? ' featured' : '';
|
||||
@@ -96,7 +100,7 @@ function renderRoomsPublic(rooms) {
|
||||
<div class="room-meta">
|
||||
<span><i class="fas fa-door-open"></i> ${room.area_sqm || 20} м²</span>
|
||||
${floorsStr ? `<span><i class="fas fa-layer-group"></i> ${floorsStr}</span>` : ''}
|
||||
${amenities.includes('has_shower') ? '<span><i class="fas fa-shower"></i> Душ в номере</span>' : ''}
|
||||
${amenities.includes('has_shower') ? '<span><i class="fas fa-shower"></i> ' + rt('amenity.shower', 'Душ в номере') + '</span>' : ''}
|
||||
</div>
|
||||
<div class="room-features">
|
||||
${amenitiesHtml}
|
||||
@@ -104,10 +108,10 @@ function renderRoomsPublic(rooms) {
|
||||
${extraBedsHtml}
|
||||
<div class="room-footer">
|
||||
<div class="room-price">
|
||||
<span class="amount">от ${room.price_per_night || 0} ₽</span>
|
||||
<span class="period"> / ночь</span>
|
||||
<span class="amount">${rt('rooms.from', 'от')} ${room.price_per_night || 0} ₽</span>
|
||||
<span class="period"> ${rt('rooms.per_night', '/ ночь')}</span>
|
||||
</div>
|
||||
<button class="btn-book" data-bs-toggle="modal" data-bs-target="#bookingModal" data-room-id="${room.id}" data-room-type="${room.type}" data-room-name="${escapeHtml(room.name)}" data-max-guests="${room.max_guests}">Забронировать</button>
|
||||
<button class="btn-book" data-bs-toggle="modal" data-bs-target="#bookingModal" data-room-id="${room.id}" data-room-type="${room.type}" data-room-name="${escapeHtml(room.name)}" data-max-guests="${room.max_guests}">${rt('rooms.book', 'Забронировать')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -187,7 +191,8 @@ function updateGuestOptionsDynamic(roomType, maxGuests) {
|
||||
|
||||
let options = [];
|
||||
for (let i = 1; i <= maxGuests; i++) {
|
||||
let text = i === 1 ? '1 гость' : (i < 5 ? `${i} гостя` : `${i} гостей`);
|
||||
const key = 'booking.guest_' + i;
|
||||
let text = rt(key, i === 1 ? '1 гость' : (i < 5 ? i + ' гостя' : i + ' гостей'));
|
||||
options.push(`<option value="${i}">${text}</option>`);
|
||||
}
|
||||
|
||||
@@ -209,13 +214,13 @@ async function checkRoomAvailability() {
|
||||
if (el && avail) {
|
||||
el.style.display = 'block';
|
||||
if (avail.available === 0) {
|
||||
el.textContent = 'Нет мест';
|
||||
el.textContent = rt('rooms.no_rooms', 'Нет мест');
|
||||
el.style.background = 'rgba(239,68,68,0.85)';
|
||||
} else if (avail.available <= 2) {
|
||||
el.textContent = 'Мест: ' + avail.available;
|
||||
el.textContent = rt('rooms.rooms_left', 'Мест:') + ' ' + avail.available;
|
||||
el.style.background = 'rgba(245,158,11,0.85)';
|
||||
} else {
|
||||
el.textContent = 'Свободно: ' + avail.available;
|
||||
el.textContent = rt('rooms.available', 'Свободно:') + ' ' + avail.available;
|
||||
el.style.background = 'rgba(22,163,74,0.85)';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user