| @@ -1,7 +1,10 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace FluentCart\App\Services\Translations; |
| 4 | + | |
| 5 | +use FluentCart\App\Services\DateTime\DateFormatter; | |
| 6 | + | |
| 4 | 7 | class TransStrings |
| 5 | 8 | { |
| 6 | 9 | public static function getStrings(): array |
| 7 | 10 | { |
| @@ -89,10 +92,14 @@ | ||
| 89 | 92 | |
| 90 | 93 | public static function singleProductPageString(): array |
| 91 | 94 | { |
| 92 | 95 | return [ |
| 93 | - 'In Stock' => _x('In Stock', 'Single Product Page', 'fluent-cart'), | |
| 94 | - 'Out Of Stock' => _x('Out Of Stock', 'Single Product Page', 'fluent-cart'), | |
| 96 | + 'In Stock' => _x('In Stock', 'Single Product Page', 'fluent-cart'), | |
| 97 | + 'Out Of Stock' => _x('Out Of Stock', 'Single Product Page', 'fluent-cart'), | |
| 98 | + 'Package' => _x('Package', 'Single Product Page', 'fluent-cart'), | |
| 99 | + 'Dimensions' => _x('Dimensions', 'Single Product Page', 'fluent-cart'), | |
| 100 | + 'Weight' => _x('Weight', 'Single Product Page', 'fluent-cart'), | |
| 101 | + 'Shipping Weight' => _x('Shipping Weight', 'Single Product Page', 'fluent-cart'), | |
| 95 | 102 | ]; |
| 96 | 103 | } |
| 97 | 104 | |
| 98 | 105 | public static function checkoutPageString() |
| @@ -295,11 +302,51 @@ | ||
| 295 | 302 | ], |
| 296 | 303 | ]; |
| 297 | 304 | } |
| 298 | 305 | |
| 299 | - public static function dateTimeStrings(): array | |
| 306 | + /** | |
| 307 | + * Names and formats for every date FluentCart renders, PHP and JS alike. | |
| 308 | + * | |
| 309 | + * One filter covers the whole map: the month/weekday names, and the | |
| 310 | + * formats derived from the store's WordPress date/time settings. Formats | |
| 311 | + * are PHP date() strings -- the dialect Settings > General shows and | |
| 312 | + * get_option() returns; the SPA payload converts them to Day.js tokens. | |
| 313 | + * | |
| 314 | + * The NAMES follow the same source setting as the formats. On 'wordpress' | |
| 315 | + * they come from $wp_locale -- the exact arrays wp_date() renders with -- | |
| 316 | + * so the PHP side and the SPA cannot disagree about what August is called. | |
| 317 | + * They used to come from FluentCart's own text domain while PHP rendered | |
| 318 | + * through wp_date(), so one locale produced two answers: | |
| 319 | + * | |
| 320 | + * PHP wp_date('M') -> 'Aout' (WP core pack) | |
| 321 | + * JS _x('Aug', 'monthsShort', ...) -> 'Aug' (untranslated) | |
| 322 | + * | |
| 323 | + * and fluent-cart ships only a .pot, so the JS side was English on any | |
| 324 | + * store that had not sourced its own catalogue. $wp_locale also gets the | |
| 325 | + * hard cases right for free: German abbreviations keep their trailing | |
| 326 | + * period ('Aug.') and the four months German never abbreviates come back | |
| 327 | + * in full ('Marz', 'Mai', 'Juni', 'Juli'). | |
| 328 | + * | |
| 329 | + * On 'fluent_cart' the text-domain names stay, because that source exists | |
| 330 | + * to reproduce what each side rendered before these settings landed. | |
| 331 | + * | |
| 332 | + * Cached per request AND per locale: DateFormatter reads this for every | |
| 333 | + * date it renders, and rebuilding ~30 gettext lookups inside a table loop | |
| 334 | + * is wasted work -- but the names now vary with the locale, so a request | |
| 335 | + * that switches locale (switch_to_locale() in a per-recipient mailer, a | |
| 336 | + * multilingual plugin) must not be served the first locale's month names. | |
| 337 | + */ | |
| 338 | + public static function dateTimeStrings(bool $refresh = false): array | |
| 300 | 339 | { |
| 301 | - return [ | |
| 340 | + static $cached = []; | |
| 341 | + | |
| 342 | + $locale = determine_locale(); | |
| 343 | + | |
| 344 | + if (!$refresh && isset($cached[$locale])) { | |
| 345 | + return $cached[$locale]; | |
| 346 | + } | |
| 347 | + | |
| 348 | + $strings = [ | |
| 302 | 349 | 'weekdays' => array( |
| 303 | 350 | 'sunday' => _x('Sunday', 'weekdays', 'fluent-cart'), |
| 304 | 351 | 'monday' => _x('Monday', 'weekdays', 'fluent-cart'), |
| 305 | 352 | 'tuesday' => _x('Tuesday', 'weekdays', 'fluent-cart'), |
| @@ -347,7 +394,89 @@ | ||
| 347 | 394 | ), |
| 348 | 395 | 'am' => __('AM', 'fluent-cart'), |
| 349 | 396 | 'pm' => __('PM', 'fluent-cart'), |
| 350 | 397 | 'numericSystem' => _x('0_1_2_3_4_5_6_7_8_9', 'numeric system - Sequence must need to maintained', 'fluent-cart'), |
| 398 | + 'formats' => DateFormatter::defaultFormats(), | |
| 399 | + // The bundle carries its own legacy literals, which are not the same | |
| 400 | + // as PHP's ('h:mm A' against 'h:i A' -- unpadded against padded). On | |
| 401 | + // the FluentCart source each side must reproduce its OWN past output, | |
| 402 | + // so the bundle needs to know which source is active rather than just | |
| 403 | + // reading the converted formats above. | |
| 404 | + 'formats_source' => DateFormatter::usesWordPressFormats() ? 'wordpress' : 'fluent_cart', | |
| 405 | + // Read by the admin bundle and the customer dashboard, which pick a | |
| 406 | + // Day.js timezone from this rather than always rendering in the | |
| 407 | + // browser's. 'site' is only consulted when source is 'wordpress'. | |
| 408 | + 'timezone' => array( | |
| 409 | + 'source' => DateFormatter::usesWordPressTimezone() ? 'wordpress' : 'fluent_cart', | |
| 410 | + 'site' => wp_timezone_string(), | |
| 411 | + ), | |
| 351 | 412 | ]; |
| 413 | + | |
| 414 | + if (DateFormatter::usesWordPressFormats()) { | |
| 415 | + $strings = static::applyWordPressLocaleNames($strings); | |
| 416 | + } | |
| 417 | + | |
| 418 | + return ($cached[$locale] = apply_filters('fluent_cart/date_time_strings', $strings)); | |
| 419 | + } | |
| 420 | + | |
| 421 | + /** | |
| 422 | + * Swap the text-domain month/weekday names for WordPress core's own. | |
| 423 | + * | |
| 424 | + * $wp_locale is what wp_date() renders with, so taking the names from the | |
| 425 | + * same object is what makes the PHP and JS sides agree by construction | |
| 426 | + * rather than by two catalogues happening to match. | |
| 427 | + * | |
| 428 | + * The KEYS are left exactly as built above. The SPA reads these maps by | |
| 429 | + * position (Object.values() against an English fallback list, see | |
| 430 | + * localeNames() in utils/dateFormats.js), and other consumers read them by | |
| 431 | + * key, so only the values may change here. | |
| 432 | + * | |
| 433 | + * Degrades to the text-domain names if $wp_locale is missing or shaped | |
| 434 | + * unexpectedly -- this runs on every rendered date and must never fatal. | |
| 435 | + * | |
| 436 | + * @param array<string, mixed> $strings | |
| 437 | + * @return array<string, mixed> | |
| 438 | + */ | |
| 439 | + protected static function applyWordPressLocaleNames(array $strings): array | |
| 440 | + { | |
| 441 | + global $wp_locale; | |
| 442 | + | |
| 443 | + if (!($wp_locale instanceof \WP_Locale)) { | |
| 444 | + return $strings; | |
| 445 | + } | |
| 446 | + | |
| 447 | + // get_weekday() is 0-indexed from Sunday; get_month() takes '01'-'12'. | |
| 448 | + $weekdayIndex = 0; | |
| 449 | + foreach ($strings['weekdays'] as $key => $unused) { | |
| 450 | + $name = $wp_locale->get_weekday($weekdayIndex); | |
| 451 | + if ($name !== '') { | |
| 452 | + $strings['weekdays'][$key] = $name; | |
| 453 | + // The abbreviation is looked up BY the full name, so it has to | |
| 454 | + // be resolved from $wp_locale's own name, not from our key. | |
| 455 | + if (isset($strings['weekdaysShort'][substr($key, 0, 3)])) { | |
| 456 | + $strings['weekdaysShort'][substr($key, 0, 3)] = $wp_locale->get_weekday_abbrev($name); | |
| 457 | + } | |
| 458 | + } | |
| 459 | + $weekdayIndex++; | |
| 460 | + } | |
| 461 | + | |
| 462 | + $monthIndex = 1; | |
| 463 | + foreach ($strings['months'] as $key => $unused) { | |
| 464 | + $name = $wp_locale->get_month(str_pad((string)$monthIndex, 2, '0', STR_PAD_LEFT)); | |
| 465 | + if ($name !== '') { | |
| 466 | + $strings['months'][$key] = $name; | |
| 467 | + $shortKey = strtolower(substr($key, 0, 3)); | |
| 468 | + if (isset($strings['monthsShort'][$shortKey])) { | |
| 469 | + $strings['monthsShort'][$shortKey] = $wp_locale->get_month_abbrev($name); | |
| 470 | + } | |
| 471 | + } | |
| 472 | + $monthIndex++; | |
| 473 | + } | |
| 474 | + | |
| 475 | + $am = $wp_locale->get_meridiem('AM'); | |
| 476 | + $pm = $wp_locale->get_meridiem('PM'); | |
| 477 | + $strings['am'] = $am !== '' ? $am : $strings['am']; | |
| 478 | + $strings['pm'] = $pm !== '' ? $pm : $strings['pm']; | |
| 479 | + | |
| 480 | + return $strings; | |
| 352 | 481 | } |
| 353 | 482 | } |