PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.17
Yatra – Travel Booking & Tour Operator Software v3.0.17
3.0.17 3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 All 85 releases
← All changes | app/Providers/AdminAssetsProvider.php +133 -1 3.0.9 → 3.0.17 View file →
@@ -408,9 +408,17 @@
408 408 // Use built assets in production
409 409 $appJs = YATRA_PLUGIN_PATH . 'assets/admin/dist/js/app.js';
410 410
411 411 if (file_exists($appJs)) {
412 - $jsVersion = YATRA_VERSION . '.' . filemtime($appJs) . '.view-icon-fix.' . time() . '.' . microtime(true);
412 + // Version on the plugin version + the bundle's own mtime. That
413 + // already changes on every update or rebuild, which is exactly
414 + // when the cache must be busted.
415 + //
416 + // This previously appended time() . microtime(true), making the
417 + // URL unique on every single request — so the ~3 MB admin bundle
418 + // was re-downloaded on every admin page view and could never be
419 + // cached by the browser.
420 + $jsVersion = YATRA_VERSION . '.' . filemtime($appJs);
413 421
414 422 $localized_data = $this->buildAdminLocalizedData();
415 423
416 424 // Enqueue our script with media library as dependency
@@ -431,8 +439,21 @@
431 439 $jsVersion,
432 440 true
433 441 );
434 442
443 + // The bundle calls the global wp.i18n.__() (it never ships its
444 + // own copy), and scripts/extract-js-pot.mjs writes every admin
445 + // string's `#:` reference as this bundle's path precisely so
446 + // WordPress's md5(handle src) JSON lookup matches. This call is
447 + // the missing last link: it tells WordPress to load
448 + // i18n/languages/yatra-{locale}-{md5}.json (or the copy under
449 + // WP_LANG_DIR/plugins) for the admin UI. Without it, translated
450 + // admin strings never reach the SPA. Mirrors FrontendAssetsProvider.
451 + if (function_exists('wp_set_script_translations')) {
452 + wp_set_script_translations('yatra-admin', 'yatra', YATRA_PLUGIN_PATH . 'i18n/languages');
453 + $this->backfillScriptTranslations('yatra-admin');
454 + }
455 +
435 456 // Localize script data
436 457 wp_localize_script('yatra-admin', 'yatraAdmin', $localized_data);
437 458
438 459 // Phone dataset for admin displays (flag + dial-code detection of
@@ -527,8 +548,119 @@
527 548 * Enqueue setup wizard assets
528 549 *
529 550 * @return void
530 551 */
552 + /**
553 + * Feed the admin bundle its translations when no JSON file exists.
554 + *
555 + * wp_set_script_translations() can only read a `yatra-{locale}-{md5}.json`
556 + * file. WordPress.org language packs ship one, so translations from
557 + * translate.wordpress.org simply work — but a site translated by hand, with
558 + * Loco Translate or a .po/.mo dropped into wp-content/languages, has only
559 + * the PHP catalogue. For those sites every string in the React admin stayed
560 + * in English no matter how complete the translation was, which looked like
561 + * the plugin ignoring the translation altogether.
562 + *
563 + * The strings are the same ones the PHP catalogue already holds, so they are
564 + * handed to wp.i18n directly. Core is asked first and left in charge
565 + * whenever it can find a JSON file: that path is cached by the browser as a
566 + * separate request, and this one is not.
567 + *
568 + * Nothing is emitted on an untranslated site — the map comes back empty and
569 + * English sites carry no extra weight.
570 + */
571 + private function backfillScriptTranslations(string $handle): void
572 + {
573 + if (!function_exists('load_script_textdomain')) {
574 + return;
575 + }
576 +
577 + // A real JSON file beats this: let WordPress load it as it normally would.
578 + if (load_script_textdomain($handle, 'yatra', YATRA_PLUGIN_PATH . 'i18n/languages')) {
579 + return;
580 + }
581 +
582 + $localeData = $this->localeDataFromTextdomain('yatra');
583 + if ($localeData === []) {
584 + return;
585 + }
586 +
587 + wp_add_inline_script(
588 + $handle,
589 + 'wp.i18n.setLocaleData(' . wp_json_encode($localeData) . ', "yatra");',
590 + 'before'
591 + );
592 + }
593 +
594 + /**
595 + * The loaded PHP catalogue, in the shape wp.i18n.setLocaleData() expects.
596 + *
597 + * Entries that were never translated are left out: they would only restate
598 + * the English the bundle already carries. Contexts use the same NUL
599 + * separator Jed and gettext use, so _x() resolves too.
600 + *
601 + * @return array<string, mixed>
602 + */
603 + private function localeDataFromTextdomain(string $domain): array
604 + {
605 + $translations = get_translations_for_domain($domain);
606 + if (!is_object($translations)) {
607 + return [];
608 + }
609 + // Deliberately duck-typed, and deliberately not isset()/??. WordPress
610 + // returns Translations, NOOP_Translations or — since the performant
611 + // translations work — WP_Translations, which neither extends
612 + // Translations nor declares `entries`: it serves that property through
613 + // __get() and defines no __isset(), so both instanceof and isset()
614 + // report nothing is there and quietly disable this fallback. Only a
615 + // direct read reaches the magic getter.
616 + if (!property_exists($translations, 'entries') && !method_exists($translations, '__get')) {
617 + return [];
618 + }
619 +
620 + $entries = $translations->entries;
621 + if (!is_array($entries) || $entries === []) {
622 + return [];
623 + }
624 +
625 + $data = [];
626 + foreach ($entries as $entry) {
627 + if (!is_object($entry) || (string) $entry->singular === '') {
628 + continue;
629 + }
630 +
631 + $forms = array_values(array_filter(
632 + (array) $entry->translations,
633 + static fn($t) => is_string($t) && $t !== ''
634 + ));
635 + if ($forms === []) {
636 + continue;
637 + }
638 + // Untranslated entries come back as the original string.
639 + if (count($forms) === 1 && $forms[0] === $entry->singular) {
640 + continue;
641 + }
642 +
643 + $key = ($entry->context !== null && $entry->context !== '')
644 + ? $entry->context . "\u{0004}" . $entry->singular
645 + : $entry->singular;
646 +
647 + $data[$key] = $forms;
648 + }
649 +
650 + if ($data === []) {
651 + return [];
652 + }
653 +
654 + $data[''] = [
655 + 'domain' => $domain,
656 + 'lang' => determine_locale(),
657 + 'plural-forms' => 'nplurals=2; plural=(n != 1);',
658 + ];
659 +
660 + return $data;
661 + }
662 +
531 663 public function enqueueSetupWizardAssets(): void
532 664 {
533 665 // Enqueue setup wizard CSS
534 666 $cssPath = YATRA_PLUGIN_PATH . 'assets/admin/css/setup-wizard.css';