| @@ -9,53 +9,232 @@ | ||
| 9 | 9 | * |
| 10 | 10 | * @package Yatra |
| 11 | 11 | */ |
| 12 | 12 | |
| 13 | +// Translation helpers — idempotent shim matching booking.js / trip.js so | |
| 14 | +// `__()` resolves through wp.i18n when wp-i18n is enqueued, otherwise it | |
| 15 | +// falls back to the source string. | |
| 16 | +// Parse a numeric min/max attribute while preserving a legitimate 0. | |
| 17 | +// `parseInt(el.getAttribute('max')) || 99` treated max="0" — what a sold-out | |
| 18 | +// departure renders — as "unset" and fell back to the placeholder ceiling, so | |
| 19 | +// the guest selector stayed usable and ignored the departure's real capacity. | |
| 20 | +// Only an absent or non-numeric value may fall back. Installed once and shared | |
| 21 | +// by every frontend script, so load order does not matter. | |
| 22 | +if (typeof window.yatraNumOr !== 'function') { | |
| 23 | + window.yatraNumOr = function (raw, fallback) { | |
| 24 | + var parsed = parseInt(raw, 10); | |
| 25 | + return isNaN(parsed) ? fallback : parsed; | |
| 26 | + }; | |
| 27 | +} | |
| 28 | + | |
| 29 | +(function () { | |
| 30 | + var i18n = (window.wp && window.wp.i18n) ? window.wp.i18n : null; | |
| 31 | + if (i18n && typeof i18n.__ === 'function') { | |
| 32 | + window.__ = function (text, domain) { return i18n.__(text, domain || 'yatra'); }; | |
| 33 | + window._n = function (s, p, n, domain) { return i18n._n(s, p, n, domain || 'yatra'); }; | |
| 34 | + window._x = function (text, ctx, domain) { return i18n._x(text, ctx, domain || 'yatra'); }; | |
| 35 | + // Always bind WordPress's own sprintf (it substitutes %d/%s/%1$s). | |
| 36 | + // Do NOT defer to a pre-existing window.sprintf: a theme or other | |
| 37 | + // plugin can define an incompatible global that leaves placeholders | |
| 38 | + // unsubstituted (the cause of literal "%d"/"%s" in the UI). | |
| 39 | + if (typeof i18n.sprintf === 'function') { | |
| 40 | + window.sprintf = function () { return i18n.sprintf.apply(i18n, arguments); }; | |
| 41 | + } else if (typeof window.sprintf !== 'function') { | |
| 42 | + window.sprintf = function (fmt) { return fmt; }; | |
| 43 | + } | |
| 44 | + } else { | |
| 45 | + // wp.i18n not present — install passthroughs only if nothing usable | |
| 46 | + // exists yet (don't clobber a real binding installed earlier). | |
| 47 | + if (typeof window.__ !== 'function') window.__ = function (text) { return text; }; | |
| 48 | + if (typeof window._n !== 'function') window._n = function (s, p, n) { return n === 1 ? s : p; }; | |
| 49 | + if (typeof window._x !== 'function') window._x = function (text) { return text; }; | |
| 50 | + if (typeof window.sprintf !== 'function') window.sprintf = function (fmt) { return fmt; }; | |
| 51 | + } | |
| 52 | +})(); | |
| 53 | + | |
| 13 | 54 | document.addEventListener('DOMContentLoaded', function() { |
| 14 | - // Initialize date pickers when DOM is ready | |
| 55 | + function yatraGetFlatpickrLocale() { | |
| 56 | + var l = window.yatraTripData && window.yatraTripData.flatpickrLocale; | |
| 57 | + if (!l || typeof l !== 'object') { | |
| 58 | + return { firstDayOfWeek: 1 }; | |
| 59 | + } | |
| 60 | + var out = Object.assign({}, l); | |
| 61 | + if (typeof out.firstDayOfWeek !== 'number' || isNaN(out.firstDayOfWeek)) { | |
| 62 | + out.firstDayOfWeek = 1; | |
| 63 | + } | |
| 64 | + return out; | |
| 65 | + } | |
| 66 | + | |
| 67 | + /** | |
| 68 | + * Convert a PHP date() format string to its Flatpickr equivalent. | |
| 69 | + * | |
| 70 | + * The two format spec tables overlap heavily — d/j/D/l/m/n/M/F/Y/y | |
| 71 | + * mean the same thing in both — but a few PHP tokens map to a | |
| 72 | + * different Flatpickr token (e.g. PHP `s` = seconds → Flatpickr | |
| 73 | + * `S`; PHP `a`/`A` AM-PM → Flatpickr `K`). Anything outside the | |
| 74 | + * map (literal punctuation, slashes, spaces) is passed through. | |
| 75 | + * | |
| 76 | + * Used to populate flatpickr's `altFormat`. We keep `dateFormat` | |
| 77 | + * itself locked to `Y-m-d` so the value submitted to the backend | |
| 78 | + * stays stable regardless of the operator's display preference; | |
| 79 | + * only the user-visible input swaps to their configured format. | |
| 80 | + * | |
| 81 | + * Kept in lockstep with the helper of the same shape in trip.js | |
| 82 | + * and the inline mapping in single-trip-sidebar.js — three call | |
| 83 | + * sites because each file ships as its own enqueued bundle and | |
| 84 | + * sharing across them would require a separate dep file. | |
| 85 | + * | |
| 86 | + * @param {string} php PHP date() format from Settings → General → Date format | |
| 87 | + * @returns {string} Flatpickr-compatible format string | |
| 88 | + */ | |
| 89 | + function yatraPhpDateFormatToFlatpickr(php) { | |
| 90 | + if (!php || typeof php !== 'string') return 'F j, Y'; | |
| 91 | + var map = { | |
| 92 | + // Day | |
| 93 | + d: 'd', j: 'j', D: 'D', l: 'l', | |
| 94 | + // Month | |
| 95 | + m: 'm', n: 'n', M: 'M', F: 'F', | |
| 96 | + // Year | |
| 97 | + Y: 'Y', y: 'y', | |
| 98 | + // Time | |
| 99 | + H: 'H', G: 'H', h: 'h', g: 'h', | |
| 100 | + i: 'i', s: 'S', a: 'K', A: 'K' | |
| 101 | + }; | |
| 102 | + var out = ''; | |
| 103 | + var esc = false; | |
| 104 | + for (var i = 0; i < php.length; i++) { | |
| 105 | + var ch = php[i]; | |
| 106 | + if (esc) { out += ch; esc = false; continue; } | |
| 107 | + if (ch === '\\') { esc = true; continue; } | |
| 108 | + out += map[ch] || ch; | |
| 109 | + } | |
| 110 | + return out || 'F j, Y'; | |
| 111 | + } | |
| 112 | + | |
| 113 | + /** | |
| 114 | + * Resolve the operator's configured display format and convert it | |
| 115 | + * for Flatpickr. Falls back to `F j, Y` (e.g. "Jan 5, 2026") when | |
| 116 | + * settings haven't shipped a value. | |
| 117 | + */ | |
| 118 | + function yatraResolveAltFormat() { | |
| 119 | + var cfg = window.yatraTripData || {}; | |
| 120 | + // Pre-converted value wins (set server-side in some bundles); a | |
| 121 | + // raw PHP string is converted on the fly. | |
| 122 | + if (cfg.flatpickrAltFormat || cfg.altDateFormat) { | |
| 123 | + return cfg.flatpickrAltFormat || cfg.altDateFormat; | |
| 124 | + } | |
| 125 | + var php = cfg.dateFormat || cfg.date_format || ''; | |
| 126 | + return yatraPhpDateFormatToFlatpickr(php); | |
| 127 | + } | |
| 128 | + | |
| 129 | + var fpLocale = yatraGetFlatpickrLocale(); | |
| 130 | + var fpAltFormat = yatraResolveAltFormat(); | |
| 131 | + | |
| 132 | + // Initialize date pickers when DOM is ready. | |
| 133 | + // | |
| 134 | + // Priority order applied here MUST mirror the server-side | |
| 135 | + // AvailabilityResolutionService: | |
| 136 | + // 1. Specific Availability Dates (per-date rows) ← highest | |
| 137 | + // 2. Recurring Rules (pattern-generated dates) | |
| 138 | + // 3. Trip default flexible window (available_from / available_to) ← lowest | |
| 139 | + // | |
| 140 | + // Server has already merged (1) and (2) into yatraTripData.availabilityDates. | |
| 141 | + // When the trip is in date-specific mode (or any availability-restricted | |
| 142 | + // mode the template might use) we lock the date picker to that merged list. | |
| 143 | + // Otherwise we fall back to the flexible window encoded in data-min-date / | |
| 144 | + // data-max-date. | |
| 15 | 145 | const bookingDateInput = document.getElementById('travel_date'); |
| 16 | 146 | if (bookingDateInput) { |
| 17 | 147 | const form = bookingDateInput.closest('.yatra-booking-form'); |
| 18 | 148 | const bookingMode = form ? form.getAttribute('data-booking-mode') : 'regular'; |
| 19 | - | |
| 20 | - if (bookingMode === 'availability') { | |
| 21 | - // Availability-based: Only specific dates | |
| 22 | - const availabilityDates = window.yatraTripData?.availabilityDates || []; | |
| 23 | - | |
| 149 | + | |
| 150 | + // Accept any of the historic / current values that signal | |
| 151 | + // "restrict to specific availability dates". The PHP template now | |
| 152 | + // emits "date_specific"; older builds used "availability". Both | |
| 153 | + // must trigger the restricted picker so the priority chain holds. | |
| 154 | + const isDateSpecific = ( | |
| 155 | + bookingMode === 'date_specific' | |
| 156 | + || bookingMode === 'availability' | |
| 157 | + || bookingMode === 'date-specific' | |
| 158 | + ); | |
| 159 | + | |
| 160 | + if (isDateSpecific) { | |
| 161 | + // Availability-based: only the dates resolved by the priority | |
| 162 | + // chain (specific dates first, then recurring rules) are pickable. | |
| 163 | + const availabilityDates = (window.yatraTripData && Array.isArray(window.yatraTripData.availabilityDates)) | |
| 164 | + ? window.yatraTripData.availabilityDates | |
| 165 | + : []; | |
| 166 | + | |
| 24 | 167 | if (availabilityDates.length > 0) { |
| 25 | 168 | flatpickr(bookingDateInput, { |
| 169 | + // Backend value stays as Y-m-d (what the booking | |
| 170 | + // form submits); only the user-visible input flips | |
| 171 | + // to the operator's configured Date Format via | |
| 172 | + // altInput/altFormat. Previously hardcoded which | |
| 173 | + // ignored Settings → General → Date format. | |
| 26 | 174 | dateFormat: 'Y-m-d', |
| 175 | + altInput: true, | |
| 176 | + altFormat: fpAltFormat, | |
| 27 | 177 | minDate: 'today', |
| 28 | 178 | enable: availabilityDates, |
| 29 | - disableMobile: false | |
| 179 | + disableMobile: false, | |
| 180 | + locale: fpLocale | |
| 30 | 181 | }); |
| 182 | + } else { | |
| 183 | + // Edge case: template believes the trip is date-specific but | |
| 184 | + // the merged date list is empty (e.g. all rules expired). | |
| 185 | + // Disable the input rather than silently falling back to | |
| 186 | + // "any date", which would violate the configured priority. | |
| 187 | + bookingDateInput.setAttribute('disabled', 'disabled'); | |
| 188 | + // Fallback placeholder when the template hasn't supplied | |
| 189 | + // `data-empty-text`. Wrapped in __() so the literal string | |
| 190 | + // can be translated; the data-attribute fallback is a | |
| 191 | + // server-rendered string that's already localised. | |
| 192 | + bookingDateInput.setAttribute( | |
| 193 | + 'placeholder', | |
| 194 | + bookingDateInput.getAttribute('data-empty-text') || __('No departure dates available', 'yatra') | |
| 195 | + ); | |
| 31 | 196 | } |
| 32 | 197 | } else { |
| 33 | - // Flexible booking: Any future date with constraints | |
| 198 | + // Flexible booking: any future date inside the trip's available | |
| 199 | + // window (available_from / available_to are surfaced as | |
| 200 | + // data-min-date / data-max-date by the sidebar template). | |
| 34 | 201 | const minDate = bookingDateInput.getAttribute('data-min-date') || 'today'; |
| 35 | 202 | const maxDate = bookingDateInput.getAttribute('data-max-date'); |
| 36 | - | |
| 203 | + | |
| 204 | + // Flexible-date picker. Same altInput/altFormat pattern | |
| 205 | + // as the availability-restricted branch above — backend | |
| 206 | + // value stable, display matches operator preference. | |
| 37 | 207 | const config = { |
| 38 | 208 | dateFormat: 'Y-m-d', |
| 209 | + altInput: true, | |
| 210 | + altFormat: fpAltFormat, | |
| 39 | 211 | minDate: minDate, |
| 40 | - disableMobile: false | |
| 212 | + disableMobile: false, | |
| 213 | + locale: fpLocale | |
| 41 | 214 | }; |
| 42 | - | |
| 215 | + | |
| 43 | 216 | if (maxDate) { |
| 44 | 217 | config.maxDate = maxDate; |
| 45 | 218 | } |
| 46 | - | |
| 219 | + | |
| 47 | 220 | flatpickr(bookingDateInput, config); |
| 48 | 221 | } |
| 49 | 222 | } |
| 50 | - | |
| 51 | - // Enquiry modal date picker (always flexible) | |
| 223 | + | |
| 224 | + // Enquiry modal date picker (always flexible). Same altFormat | |
| 225 | + // treatment so the enquiry form respects the operator's date | |
| 226 | + // format too — previously hardcoded to Y-m-d which jarred with | |
| 227 | + // any non-ISO display preference. | |
| 52 | 228 | const enquiryDateInput = document.getElementById('enquiry-travel-date'); |
| 53 | 229 | if (enquiryDateInput) { |
| 54 | 230 | flatpickr(enquiryDateInput, { |
| 55 | 231 | dateFormat: 'Y-m-d', |
| 232 | + altInput: true, | |
| 233 | + altFormat: fpAltFormat, | |
| 56 | 234 | minDate: 'today', |
| 57 | - disableMobile: false | |
| 235 | + disableMobile: false, | |
| 236 | + locale: fpLocale | |
| 58 | 237 | }); |
| 59 | 238 | } |
| 60 | 239 | }); |
| 61 | 240 | |
| @@ -165,61 +344,55 @@ | ||
| 165 | 344 | } |
| 166 | 345 | |
| 167 | 346 | return discountAmount; |
| 168 | 347 | } |
| 348 | + | |
| 349 | + function getSidebarAvailabilities() { | |
| 350 | + const raw = window.yatraTripData && window.yatraTripData.sidebarAvailability; | |
| 351 | + return Array.isArray(raw) ? raw : []; | |
| 352 | + } | |
| 169 | 353 | |
| 170 | 354 | // Update pricing when date changes |
| 171 | 355 | if (dateInput) { |
| 172 | 356 | dateInput.addEventListener('change', function() { |
| 173 | 357 | const selectedDate = this.value; |
| 174 | - const bookingCard = document.querySelector('.yatra-booking-card'); | |
| 175 | - | |
| 176 | - if (bookingCard) { | |
| 177 | - const availabilityData = bookingCard.getAttribute('data-availability'); | |
| 178 | - if (availabilityData) { | |
| 179 | - try { | |
| 180 | - const availabilities = JSON.parse(availabilityData); | |
| 181 | - selectedAvailability = availabilities.find(a => a.departure_date === selectedDate); | |
| 182 | - | |
| 183 | - // Update traveler input prices if traveler-based pricing | |
| 184 | - if (selectedAvailability && selectedAvailability.pricing_type === 'traveler_based' && selectedAvailability.price_types) { | |
| 185 | - selectedAvailability.price_types.forEach((pt, index) => { | |
| 186 | - const input = document.querySelector(`input[name="travelers[${pt.category_id}]"]`); | |
| 187 | - if (input) { | |
| 188 | - const price = pt.discounted_price || pt.original_price || 0; | |
| 189 | - input.setAttribute('data-price', price); | |
| 190 | - } | |
| 191 | - }); | |
| 358 | + const availabilities = getSidebarAvailabilities(); | |
| 359 | + if (availabilities.length > 0) { | |
| 360 | + selectedAvailability = availabilities.find(a => a.departure_date === selectedDate); | |
| 361 | + | |
| 362 | + if (selectedAvailability && selectedAvailability.pricing_type === 'traveler_based' && selectedAvailability.price_types) { | |
| 363 | + selectedAvailability.price_types.forEach((pt) => { | |
| 364 | + const input = document.querySelector(`input[name="travelers[${pt.category_id}]"]`); | |
| 365 | + if (input) { | |
| 366 | + const price = pt.discounted_price || pt.original_price || 0; | |
| 367 | + input.setAttribute('data-price', price); | |
| 192 | 368 | } |
| 193 | - | |
| 194 | - // Recalculate total | |
| 195 | - updatePricing(); | |
| 196 | - } catch (e) { | |
| 197 | - console.error('Error parsing availability data:', e); | |
| 198 | - } | |
| 369 | + }); | |
| 199 | 370 | } |
| 371 | + } else { | |
| 372 | + selectedAvailability = null; | |
| 200 | 373 | } |
| 374 | + | |
| 375 | + updatePricing(); | |
| 201 | 376 | }); |
| 202 | 377 | |
| 203 | - // Set today's date by default if available, otherwise use trip fallback pricing | |
| 204 | - const today = new Date().toISOString().split('T')[0]; | |
| 205 | - const bookingCard = document.querySelector('.yatra-booking-card'); | |
| 206 | - if (bookingCard && !dateInput.value) { | |
| 207 | - const availabilityData = bookingCard.getAttribute('data-availability'); | |
| 208 | - if (availabilityData) { | |
| 209 | - try { | |
| 210 | - const availabilities = JSON.parse(availabilityData); | |
| 211 | - const todayAvailability = availabilities.find(a => a.departure_date === today); | |
| 212 | - if (todayAvailability) { | |
| 213 | - dateInput.value = today; | |
| 214 | - dateInput.dispatchEvent(new Event('change')); | |
| 215 | - } else { | |
| 216 | - // No availability for today - use trip's default pricing (already set in HTML) | |
| 217 | - // Just trigger initial calculation with trip prices | |
| 218 | - updatePricing(); | |
| 219 | - } | |
| 220 | - } catch (e) { | |
| 221 | - console.error('Error setting default date:', e); | |
| 378 | + // Set today's date by default if available, otherwise use trip fallback pricing. | |
| 379 | + // Build LOCAL YYYY-MM-DD, not toISOString() (which is UTC and can land on | |
| 380 | + // the wrong calendar day — mis-matching the availability's departure_date | |
| 381 | + // and pre-selecting the wrong date near midnight). | |
| 382 | + const _now = new Date(); | |
| 383 | + const today = _now.getFullYear() + '-' | |
| 384 | + + String(_now.getMonth() + 1).padStart(2, '0') + '-' | |
| 385 | + + String(_now.getDate()).padStart(2, '0'); | |
| 386 | + if (!dateInput.value) { | |
| 387 | + const availabilities = getSidebarAvailabilities(); | |
| 388 | + if (availabilities.length > 0) { | |
| 389 | + const todayAvailability = availabilities.find(a => a.departure_date === today); | |
| 390 | + if (todayAvailability) { | |
| 391 | + dateInput.value = today; | |
| 392 | + dateInput.dispatchEvent(new Event('change')); | |
| 393 | + } else { | |
| 394 | + updatePricing(); | |
| 222 | 395 | } |
| 223 | 396 | } |
| 224 | 397 | } |
| 225 | 398 | } |
| @@ -238,13 +411,18 @@ | ||
| 238 | 411 | categoryInputs.forEach(input => { |
| 239 | 412 | const count = parseInt(input.value) || 0; |
| 240 | 413 | const price = parseFloat(input.dataset.price) || 0; |
| 241 | 414 | const pricingMode = input.dataset.pricingMode || 'per_person'; |
| 242 | - | |
| 415 | + const groupOverflow = input.dataset.groupOverflow || 'block'; | |
| 416 | + const maxPax = parseInt(input.dataset.maxPax) || 0; | |
| 417 | + | |
| 243 | 418 | if (pricingMode === 'per_group') { |
| 244 | - // Per group: charge flat price once if any travelers | |
| 419 | + // Per group: one flat price, or per block of maxPax people | |
| 420 | + // when overflow is "per_block". | |
| 245 | 421 | if (count > 0) { |
| 246 | - subtotal += price; | |
| 422 | + subtotal += (groupOverflow === 'per_block' && maxPax > 0) | |
| 423 | + ? price * Math.ceil(count / maxPax) | |
| 424 | + : price; | |
| 247 | 425 | } |
| 248 | 426 | } else { |
| 249 | 427 | // Per person: charge per traveler |
| 250 | 428 | subtotal += price * count; |
| @@ -292,9 +470,9 @@ | ||
| 292 | 470 | <div class="yatra-group-discount-badge"> |
| 293 | 471 | <svg width="14" height="14" fill="currentColor" viewBox="0 0 20 20"> |
| 294 | 472 | <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/> |
| 295 | 473 | </svg> |
| 296 | - Group discount applied: -${window.yatraTripData?.currencySymbol || '$'}${discountAmount.toFixed(2)} | |
| 474 | + Group discount applied: -${typeof window.yatra_format_price_js === 'function' ? window.yatra_format_price_js(discountAmount) : (window.yatraTripData?.currencySymbol || '$') + discountAmount.toFixed(2)} | |
| 297 | 475 | </div> |
| 298 | 476 | `; |
| 299 | 477 | } else if (indicator) { |
| 300 | 478 | indicator.remove(); |
| @@ -335,12 +513,19 @@ | ||
| 335 | 513 | // Traveler-based pricing - set adults to group size |
| 336 | 514 | const adultInput = document.querySelector('input[name*="travelers[adults]"]'); |
| 337 | 515 | if (adultInput) { |
| 338 | 516 | adultInput.value = groupSize; |
| 339 | - // Update display | |
| 517 | + // Update display. The literal "Adult x %d" | |
| 518 | + // is kept as the msgid so translators can | |
| 519 | + // localise both the noun and the format | |
| 520 | + // (some locales put the multiplier first). | |
| 521 | + // Comment phrasing matches the other call | |
| 522 | + // sites in trip.js so WP-CLI sees ONE | |
| 523 | + // canonical translators-comment per msgid. | |
| 340 | 524 | const display = document.querySelector('.yatra-participants-display'); |
| 341 | 525 | if (display) { |
| 342 | - display.textContent = `Adult x ${groupSize}`; | |
| 526 | + // translators: %d: number of adults | |
| 527 | + display.textContent = __('Adult x %d', 'yatra').replace('%d', String(groupSize)); | |
| 343 | 528 | } |
| 344 | 529 | } |
| 345 | 530 | } |
| 346 | 531 | |
| @@ -387,10 +572,10 @@ | ||
| 387 | 572 | } |
| 388 | 573 | |
| 389 | 574 | if (!input) return; |
| 390 | 575 | |
| 391 | - const min = parseInt(input.getAttribute('min')) || 0; | |
| 392 | - const max = parseInt(input.getAttribute('max')) || 99; | |
| 576 | + const min = window.yatraNumOr(input.getAttribute('min'), 0); | |
| 577 | + const max = window.yatraNumOr(input.getAttribute('max'), 99); | |
| 393 | 578 | let value = parseInt(input.value) || 0; |
| 394 | 579 | |
| 395 | 580 | // Update value based on button type |
| 396 | 581 | if (btn.classList.contains('yatra-quantity-plus')) { |
| @@ -446,9 +631,9 @@ | ||
| 446 | 631 | // Regular pricing - sync all num_travelers and enquiry_adults inputs |
| 447 | 632 | const regularInputs = document.querySelectorAll('#num_travelers, #enquiry_adults'); |
| 448 | 633 | regularInputs.forEach(input => { |
| 449 | 634 | if (input !== sourceInput) { |
| 450 | - const max = parseInt(input.getAttribute('max')) || 99; | |
| 635 | + const max = window.yatraNumOr(input.getAttribute('max'), 99); | |
| 451 | 636 | input.value = Math.min(sourceValue, max); |
| 452 | 637 | } |
| 453 | 638 | }); |
| 454 | 639 | |
| @@ -455,9 +640,9 @@ | ||
| 455 | 640 | // Also sync to availability section inputs |
| 456 | 641 | const availabilityInputs = document.querySelectorAll('input[id^="num-travelers-"]'); |
| 457 | 642 | availabilityInputs.forEach(input => { |
| 458 | 643 | if (input !== sourceInput) { |
| 459 | - const max = parseInt(input.getAttribute('max')) || 99; | |
| 644 | + const max = window.yatraNumOr(input.getAttribute('max'), 99); | |
| 460 | 645 | input.value = Math.min(sourceValue, max); |
| 461 | 646 | } |
| 462 | 647 | }); |
| 463 | 648 | |
| @@ -482,15 +667,15 @@ | ||
| 482 | 667 | ); |
| 483 | 668 | |
| 484 | 669 | categoryInputs.forEach(input => { |
| 485 | 670 | if (input !== sourceInput) { |
| 486 | - const max = parseInt(input.getAttribute('max')) || 99; | |
| 671 | + const max = window.yatraNumOr(input.getAttribute('max'), 99); | |
| 487 | 672 | input.value = Math.min(sourceValue, max); |
| 488 | 673 | |
| 489 | 674 | // Update button states for this input |
| 490 | 675 | const row = input.closest('.yatra-quantity-row'); |
| 491 | 676 | if (row) { |
| 492 | - const min = parseInt(input.getAttribute('min')) || 0; | |
| 677 | + const min = window.yatraNumOr(input.getAttribute('min'), 0); | |
| 493 | 678 | const minusBtn = row.querySelector('.yatra-quantity-minus'); |
| 494 | 679 | const plusBtn = row.querySelector('.yatra-quantity-plus'); |
| 495 | 680 | if (minusBtn) minusBtn.disabled = input.value <= min; |
| 496 | 681 | if (plusBtn) plusBtn.disabled = input.value >= max; |
| @@ -524,10 +709,15 @@ | ||
| 524 | 709 | |
| 525 | 710 | inputs.forEach(input => { |
| 526 | 711 | const value = parseInt(input.value) || 0; |
| 527 | 712 | if (value > 0) { |
| 528 | - const label = input.getAttribute('data-category-label') || 'Traveler'; | |
| 529 | - parts.push(`${label} x ${value}`); | |
| 713 | + const label = input.getAttribute('data-category-label') || __('Traveler', 'yatra'); | |
| 714 | + parts.push(sprintf( | |
| 715 | + /* translators: 1: traveler category label, 2: count. */ | |
| 716 | + __('%1$s x %2$d', 'yatra'), | |
| 717 | + label, | |
| 718 | + value | |
| 719 | + )); | |
| 530 | 720 | total += value; |
| 531 | 721 | } |
| 532 | 722 | }); |
| 533 | 723 | |
| @@ -533,9 +723,9 @@ | ||
| 533 | 723 | |
| 534 | 724 | if (parts.length > 0) { |
| 535 | 725 | display.textContent = parts.join(', '); |
| 536 | 726 | } else { |
| 537 | - display.textContent = 'Select travelers'; | |
| 727 | + display.textContent = __('Select travelers', 'yatra'); | |
| 538 | 728 | } |
| 539 | 729 | }); |
| 540 | 730 | } |
| 541 | 731 | }); |