lib.rs (51728B)
1 #![forbid(unsafe_code)] 2 3 use mf2_i18n::{LanguageTag, negotiate_lookup}; 4 5 mod keys; 6 7 pub use keys::AppTextKey; 8 9 include!(concat!(env!("OUT_DIR"), "/app_i18n/generated_module.rs")); 10 include!(concat!(env!("OUT_DIR"), "/app_i18n/generated_catalog.rs")); 11 12 pub fn app_text(key: AppTextKey) -> String { 13 generated::tr(key.id()) 14 .unwrap_or_else(|error| panic!("missing localized app text for key {}: {error}", key.id())) 15 } 16 17 pub fn default_locale() -> &'static str { 18 DEFAULT_LOCALE_ID 19 } 20 21 pub fn supported_locales() -> &'static [&'static str] { 22 SUPPORTED_LOCALE_IDS 23 } 24 25 pub fn resolve_locale_from_host(host_locale: &str) -> String { 26 let normalized = normalize_host_locale(host_locale); 27 let requested_locale = match LanguageTag::parse(&normalized) { 28 Ok(locale) => locale, 29 Err(_) => return DEFAULT_LOCALE_ID.to_owned(), 30 }; 31 let supported = SUPPORTED_LOCALE_IDS 32 .iter() 33 .map(|locale| LanguageTag::parse(locale).expect("supported locale should parse")) 34 .collect::<Vec<_>>(); 35 let default_locale = 36 LanguageTag::parse(DEFAULT_LOCALE_ID).expect("default locale should parse"); 37 38 negotiate_lookup(&[requested_locale], &supported, &default_locale) 39 .selected 40 .normalized() 41 .to_owned() 42 } 43 44 pub fn select_locale_from_host(host_locale: &str) -> String { 45 let locale = resolve_locale_from_host(host_locale); 46 generated::set_locale(&locale).unwrap_or_else(|_| DEFAULT_LOCALE_ID.to_owned()) 47 } 48 49 fn normalize_host_locale(host_locale: &str) -> String { 50 let trimmed = host_locale.trim(); 51 if trimmed.is_empty() { 52 return DEFAULT_LOCALE_ID.to_owned(); 53 } 54 55 let without_fallbacks = trimmed.split(':').next().unwrap_or(DEFAULT_LOCALE_ID); 56 let without_encoding = without_fallbacks 57 .split('.') 58 .next() 59 .unwrap_or(DEFAULT_LOCALE_ID) 60 .split('@') 61 .next() 62 .unwrap_or(DEFAULT_LOCALE_ID) 63 .trim(); 64 if without_encoding.is_empty() { 65 return DEFAULT_LOCALE_ID.to_owned(); 66 } 67 68 without_encoding.replace('_', "-") 69 } 70 71 #[cfg(test)] 72 mod tests { 73 use std::collections::BTreeSet; 74 75 use super::{ 76 AppTextKey, app_text, default_locale, resolve_locale_from_host, supported_locales, 77 }; 78 79 const RESERVED_PAYMENT_ACTION_TERMS: &[&str] = &[ 80 "checkout", 81 "pay", 82 "refund", 83 "settlement", 84 "wallet", 85 "invoice", 86 "bank", 87 "card", 88 "processor", 89 "provider", 90 "payment-provider", 91 "payment provider", 92 ]; 93 94 const FORBIDDEN_PAYMENT_DEFERRAL_COPY_PATTERNS: &[&str] = &[ 95 "payments are deferred", 96 "payment is deferred", 97 "payment deferred", 98 "payments deferred", 99 "deferred payment", 100 "deferred payments", 101 "checkout unavailable", 102 "figure it out", 103 "payment handling outside the app", 104 "refund outside the app", 105 "handle any refund outside the app", 106 "settle outside the app", 107 ]; 108 109 const FORBIDDEN_TRADE_WORKFLOW_LEAKAGE_PATTERNS: &[&str] = &[ 110 "state machine", 111 "reducer", 112 "event kind", 113 "nostr", 114 "protocol", 115 "checkout", 116 "payment provider", 117 "payment-provider", 118 ]; 119 120 #[test] 121 fn generated_catalog_matches_typed_key_registry() { 122 let catalog_keys = super::DEFAULT_CATALOG_KEY_IDS 123 .iter() 124 .copied() 125 .collect::<BTreeSet<_>>(); 126 let typed_keys = AppTextKey::ALL 127 .iter() 128 .map(|key| key.id()) 129 .collect::<BTreeSet<_>>(); 130 131 assert_eq!(typed_keys, catalog_keys); 132 } 133 134 #[test] 135 fn english_catalog_covers_all_defined_text_keys() { 136 assert_eq!(super::generated::default_locale(), default_locale()); 137 assert_eq!( 138 super::generated::supported_locales(), 139 supported_locales() 140 .iter() 141 .map(|locale| (*locale).to_owned()) 142 .collect::<Vec<_>>() 143 ); 144 145 for key in AppTextKey::ALL { 146 assert!(!app_text(*key).trim().is_empty()); 147 } 148 } 149 150 #[test] 151 fn english_identity_copy_matches_the_macos_menu_contract() { 152 assert_eq!(app_text(AppTextKey::AppName), "Radroots"); 153 assert_eq!(app_text(AppTextKey::MenuAbout), "About Radroots"); 154 assert_eq!(app_text(AppTextKey::MenuQuit), "Quit Radroots"); 155 } 156 157 #[test] 158 fn english_auth_copy_matches_the_local_account_workflow_contract() { 159 assert_eq!(app_text(AppTextKey::AccountTitle), "Account"); 160 assert_eq!(app_text(AppTextKey::AccountTabProfile), "Profile"); 161 assert_eq!(app_text(AppTextKey::AccountTabFarmDetails), "Farm details"); 162 assert_eq!(app_text(AppTextKey::AccountTabPreferences), "Preferences"); 163 assert_eq!(app_text(AppTextKey::AccountTabSettings), "Settings"); 164 assert_eq!( 165 app_text(AppTextKey::AccountNotImplemented), 166 "Not implemented" 167 ); 168 assert_eq!(app_text(AppTextKey::AccountFormSaveAction), "Save"); 169 assert_eq!( 170 app_text(AppTextKey::AccountFormSaveDraftAction), 171 "Save draft" 172 ); 173 assert_eq!( 174 app_text(AppTextKey::AccountProfilePersonalDetailsTitle), 175 "Personal details" 176 ); 177 assert_eq!( 178 app_text(AppTextKey::AccountProfileChangePhotoAction), 179 "Change photo" 180 ); 181 assert_eq!( 182 app_text(AppTextKey::AccountProfileRemovePhotoAction), 183 "Remove" 184 ); 185 assert_eq!( 186 app_text(AppTextKey::AccountProfileFullNameValue), 187 "Tyson Lupul" 188 ); 189 assert_eq!( 190 app_text(AppTextKey::AccountProfileEmailValue), 191 "tyson@tysonsfarm.com" 192 ); 193 assert_eq!( 194 app_text(AppTextKey::AccountProfilePhoneValue), 195 "+1 250 202 3030" 196 ); 197 assert_eq!( 198 app_text(AppTextKey::AccountFarmDetailsTitle), 199 "Farm details" 200 ); 201 assert_eq!( 202 app_text(AppTextKey::AccountFarmDetailsTabProfile), 203 "Profile" 204 ); 205 assert_eq!( 206 app_text(AppTextKey::AccountFarmDetailsTabLocation), 207 "Location" 208 ); 209 assert_eq!( 210 app_text(AppTextKey::AccountFarmDetailsTabOperations), 211 "Operations" 212 ); 213 assert_eq!( 214 app_text(AppTextKey::AccountFarmDetailsTabFulfilment), 215 "Fulfilment" 216 ); 217 assert_eq!( 218 app_text(AppTextKey::AccountFarmDetailsFarmProfileTitle), 219 "Farm profile" 220 ); 221 assert_eq!( 222 app_text(AppTextKey::AccountFarmDetailsContactEmailValue), 223 "tyson@tysonsfarm.com" 224 ); 225 assert_eq!( 226 app_text(AppTextKey::AccountFarmDetailsPublicPhoneValue), 227 "+1 250 202 3030" 228 ); 229 assert_eq!( 230 app_text(AppTextKey::AccountFarmDetailsFarmTypeVegetableFarm), 231 "Vegetable farm" 232 ); 233 assert_eq!( 234 app_text(AppTextKey::AccountFarmDetailsLocationTitle), 235 "Location & service area" 236 ); 237 assert_eq!( 238 app_text(AppTextKey::AccountFarmDetailsMapNotImplemented), 239 "not implemented" 240 ); 241 assert_eq!( 242 app_text(AppTextKey::AccountFarmDetailsOperatingTitle), 243 "Operating details" 244 ); 245 assert_eq!( 246 app_text(AppTextKey::AccountFarmDetailsPickupFulfillmentTitle), 247 "Pickup & fulfilment" 248 ); 249 assert_eq!( 250 app_text(AppTextKey::AccountFarmDetailsOrderCutoffNoonValue), 251 "12:00 PM (Noon)" 252 ); 253 assert_eq!(app_text(AppTextKey::AccountSettingsTitle), "Settings"); 254 assert_eq!( 255 app_text(AppTextKey::AccountSettingsNostrRelaysTitle), 256 "Nostr relays" 257 ); 258 assert_eq!( 259 app_text(AppTextKey::AccountSettingsBlossomServerTitle), 260 "Blossom server" 261 ); 262 assert_eq!( 263 app_text(AppTextKey::HomeTodayEmptySetupBody), 264 "Add a local account to start using Radroots on this device." 265 ); 266 assert_eq!( 267 app_text(AppTextKey::SettingsAccountNoSelectionBody), 268 "Add a local account to start using Radroots on this device." 269 ); 270 assert_eq!( 271 app_text(AppTextKey::SettingsAccountActivationLabel), 272 "Farmer Activation" 273 ); 274 assert_eq!( 275 app_text(AppTextKey::SettingsAccountOpenWorkspaceAction), 276 "Admin Console" 277 ); 278 assert_eq!( 279 app_text(AppTextKey::SettingsAccountImportFileAction), 280 "Import from file" 281 ); 282 assert_eq!( 283 app_text(AppTextKey::SettingsAccountImportDatabaseAction), 284 "Import from database" 285 ); 286 assert_eq!( 287 app_text(AppTextKey::SettingsAccountConnectRemoteBunkerAction), 288 "Connect remote bunker" 289 ); 290 } 291 292 #[test] 293 fn english_shell_reset_copy_matches_setup_and_utility_contract() { 294 assert_eq!( 295 app_text(AppTextKey::HomeSetupCreateAccountAction), 296 "Create account" 297 ); 298 assert_eq!(app_text(AppTextKey::SettingsTitle), "Radroots Settings"); 299 assert_eq!( 300 app_text(AppTextKey::SettingsAccountNoSelectionTitle), 301 "No account selected" 302 ); 303 assert_eq!( 304 app_text(AppTextKey::SettingsAccountNoSelectionBody), 305 "Add a local account to start using Radroots on this device." 306 ); 307 assert_eq!( 308 app_text(AppTextKey::SettingsAccountStatusLoggedOut), 309 "Logged Out" 310 ); 311 assert_eq!( 312 app_text(AppTextKey::SettingsAccountActivationInactive), 313 "Not activated" 314 ); 315 assert_eq!( 316 app_text(AppTextKey::SettingsAccountAddAction), 317 "Add Account..." 318 ); 319 assert_eq!(app_text(AppTextKey::SettingsAccountLogOutAction), "Log Out"); 320 assert_eq!( 321 app_text(AppTextKey::SettingsAccountOpenWorkspaceAction), 322 "Admin Console" 323 ); 324 } 325 326 #[test] 327 fn english_reminder_copy_matches_the_seller_surface_contract() { 328 assert_eq!(app_text(AppTextKey::HomeTodayRemindersTitle), "Coming up"); 329 assert_eq!(app_text(AppTextKey::OrdersRemindersTitle), "Reminders"); 330 assert_eq!( 331 app_text(AppTextKey::OrdersReminderLogTitle), 332 "Reminder activity" 333 ); 334 assert_eq!( 335 app_text(AppTextKey::PackDayRemindersTitle), 336 "Before this window" 337 ); 338 assert_eq!(app_text(AppTextKey::ReminderDeadlineLabel), "Due"); 339 assert_eq!(app_text(AppTextKey::ReminderUrgencyDueSoon), "Due soon"); 340 assert_eq!(app_text(AppTextKey::ReminderUrgencyBlocking), "Blocking"); 341 assert_eq!( 342 app_text(AppTextKey::ReminderPresentationTitle), 343 "Needs attention now" 344 ); 345 assert_eq!( 346 app_text(AppTextKey::ReminderPresentationDismissAction), 347 "Dismiss" 348 ); 349 assert_eq!( 350 app_text(AppTextKey::ReminderDeliveryStatePresented), 351 "Presented" 352 ); 353 assert_eq!( 354 app_text(AppTextKey::ReminderDeliveryStateResolved), 355 "Resolved" 356 ); 357 } 358 359 #[test] 360 fn english_about_copy_matches_the_runtime_status_contract() { 361 assert_eq!( 362 app_text(AppTextKey::SettingsAboutCompanyName), 363 "Radroots, Inc." 364 ); 365 assert_eq!(app_text(AppTextKey::SettingsAboutVersionLabel), "Version"); 366 assert_eq!( 367 app_text(AppTextKey::SettingsAboutVariantLabel), 368 "Standalone local app" 369 ); 370 assert_eq!( 371 app_text(AppTextKey::SettingsAboutAcknowledgementsAction), 372 "Acknowledgements" 373 ); 374 assert_eq!( 375 app_text(AppTextKey::SettingsAboutPrivacyPolicyAction), 376 "Privacy Policy" 377 ); 378 assert_eq!( 379 app_text(AppTextKey::SettingsAboutTermsAction), 380 "Terms of Service" 381 ); 382 assert_eq!( 383 app_text(AppTextKey::SettingsAboutReportIssueAction), 384 "Report an Issue..." 385 ); 386 assert_eq!( 387 app_text(AppTextKey::SettingsAboutCopyrightNotice), 388 "© 2026 Radroots, Inc. All rights reserved." 389 ); 390 assert_eq!( 391 app_text(AppTextKey::SettingsAboutTrademarkNotice), 392 "Radroots is a trademark of Radroots, Inc." 393 ); 394 assert_eq!( 395 app_text(AppTextKey::SettingsAboutStatusSectionLabel), 396 "Status" 397 ); 398 assert_eq!( 399 app_text(AppTextKey::SettingsAboutConflictReviewSectionLabel), 400 "Conflict review" 401 ); 402 assert_eq!( 403 app_text(AppTextKey::SettingsAboutRuntimeSectionLabel), 404 "Runtime" 405 ); 406 assert_eq!( 407 app_text(AppTextKey::SettingsAboutConflictReviewUnavailable), 408 "Conflict review becomes available after you select an account." 409 ); 410 assert_eq!( 411 app_text(AppTextKey::SettingsAboutConflictReviewBlocking), 412 "Blocking conflicts pause sync until you resolve them." 413 ); 414 assert_eq!( 415 app_text(AppTextKey::SettingsAboutRefreshAction), 416 "Refresh sync" 417 ); 418 assert_eq!( 419 app_text(AppTextKey::SettingsAboutConflictAcceptLocalAction), 420 "Accept local" 421 ); 422 assert_eq!( 423 app_text(AppTextKey::SettingsAboutConflictAcceptRemoteAction), 424 "Accept remote" 425 ); 426 assert_eq!( 427 app_text(AppTextKey::SettingsAboutConflictDismissAction), 428 "Dismiss" 429 ); 430 assert_eq!( 431 app_text(AppTextKey::MetadataSyncPendingWriteCount), 432 "pending writes" 433 ); 434 assert_eq!( 435 app_text(AppTextKey::MetadataSyncBlockingConflictCount), 436 "blocking conflict count" 437 ); 438 assert_eq!( 439 app_text(AppTextKey::MetadataSyncConflictAggregate), 440 "aggregate" 441 ); 442 assert_eq!(app_text(AppTextKey::MetadataSyncConflictKind), "kind"); 443 assert_eq!( 444 app_text(AppTextKey::MetadataSyncConflictSeverity), 445 "severity" 446 ); 447 assert_eq!( 448 app_text(AppTextKey::MetadataSyncConflictDetectedAt), 449 "detected" 450 ); 451 assert_eq!( 452 app_text(AppTextKey::MetadataSyncConflictResolution), 453 "resolution" 454 ); 455 assert_eq!( 456 app_text(AppTextKey::ValueSyncConflictAggregateFulfillmentWindow), 457 "Fulfillment window" 458 ); 459 assert_eq!( 460 app_text(AppTextKey::ValueSyncConflictKindRevisionMismatch), 461 "Revision mismatch" 462 ); 463 assert_eq!( 464 app_text(AppTextKey::ValueSyncConflictSeverityBlocking), 465 "Blocking" 466 ); 467 assert_eq!( 468 app_text(AppTextKey::ValueSyncConflictResolutionAcceptedRemote), 469 "Accepted remote" 470 ); 471 } 472 473 #[test] 474 fn english_orders_copy_matches_the_queue_contract() { 475 assert_eq!(app_text(AppTextKey::HomeNavOrders), "Orders"); 476 assert_eq!( 477 app_text(AppTextKey::HomeTodayOpenInOrdersAction), 478 "View all" 479 ); 480 assert_eq!( 481 app_text(AppTextKey::HomeTodayOpenInPackDayAction), 482 "Open pack day" 483 ); 484 assert_eq!(app_text(AppTextKey::OrdersTitle), "Orders"); 485 assert_eq!( 486 app_text(AppTextKey::OrdersStatusNeedsAction), 487 "Needs action" 488 ); 489 assert_eq!(app_text(AppTextKey::OrdersStatusDeclined), "Declined"); 490 assert_eq!(app_text(AppTextKey::OrdersStatusScheduled), "Agreed"); 491 assert_eq!(app_text(AppTextKey::OrdersStatusInHandoff), "Agreed"); 492 assert_eq!(app_text(AppTextKey::OrdersStatusCompleted), "Agreed"); 493 assert_eq!( 494 app_text(AppTextKey::OrdersStatusNeedsReview), 495 "Needs review" 496 ); 497 assert_eq!(app_text(AppTextKey::OrdersDetailTitle), "Order detail"); 498 } 499 500 #[test] 501 fn english_marketplace_detail_copy_matches_the_buyer_detail_contract() { 502 assert_eq!(app_text(AppTextKey::PersonalDetailBackAction), "Back"); 503 assert_eq!( 504 app_text(AppTextKey::PersonalDetailQuantityLabel), 505 "Quantity" 506 ); 507 assert_eq!( 508 app_text(AppTextKey::PersonalDetailAddToCartAction), 509 "Add to cart" 510 ); 511 assert_eq!( 512 app_text(AppTextKey::PersonalDetailReplaceCartAction), 513 "Replace cart" 514 ); 515 assert_eq!( 516 app_text(AppTextKey::PersonalMarketplaceRefreshFailedNotice), 517 "Couldn't refresh marketplace listings. Your saved local state is still here; try again in a moment." 518 ); 519 assert_eq!( 520 app_text(AppTextKey::PersonalDetailOpenFailedNotice), 521 "Couldn't open that listing. Refresh the marketplace and try again." 522 ); 523 } 524 525 #[test] 526 fn english_marketplace_order_review_copy_matches_the_local_order_contract() { 527 assert_eq!( 528 app_text(AppTextKey::PersonalCartReviewOrderAction), 529 "Review order" 530 ); 531 assert_eq!( 532 app_text(AppTextKey::PersonalOrderReviewTitle), 533 "Order review" 534 ); 535 assert_eq!( 536 app_text(AppTextKey::PersonalOrderReviewPlaceOrderAction), 537 "Place order" 538 ); 539 assert_eq!( 540 app_text(AppTextKey::PersonalOrderReviewLocalOnlyBody), 541 "Review the details before placing the order." 542 ); 543 assert_eq!( 544 app_text(AppTextKey::PersonalOrderPlaceFailedNotice), 545 "Couldn't place that order. Nothing was sent; check the order and try again." 546 ); 547 assert_eq!( 548 app_text(AppTextKey::PersonalOrderCoordinationFailedNotice), 549 "Order saved locally. It still needs to be shared with your order tools; open Orders and try again." 550 ); 551 } 552 553 #[test] 554 fn english_payment_action_copy_remains_unspoken_for_reserved_workflow() { 555 let action_keys = AppTextKey::ALL 556 .iter() 557 .copied() 558 .filter(|key| is_visible_action_text_key(*key)) 559 .collect::<Vec<_>>(); 560 561 assert!(action_keys.contains(&AppTextKey::PersonalCartReviewOrderAction)); 562 assert!(action_keys.contains(&AppTextKey::PersonalOrderReviewPlaceOrderAction)); 563 564 for key in action_keys { 565 let copy = app_text(key).to_lowercase(); 566 for term in RESERVED_PAYMENT_ACTION_TERMS { 567 assert!( 568 !contains_reserved_payment_action_term(©, term), 569 "{} contains reserved payment action term `{term}`", 570 key.id() 571 ); 572 } 573 } 574 } 575 576 #[test] 577 fn english_visible_copy_does_not_explain_payment_deferral() { 578 for key in AppTextKey::ALL { 579 let normalized_copy = app_text(*key).to_lowercase(); 580 for pattern in FORBIDDEN_PAYMENT_DEFERRAL_COPY_PATTERNS { 581 assert!( 582 !normalized_copy.contains(pattern), 583 "{} contains forbidden payment-deferral copy `{pattern}`", 584 key.id() 585 ); 586 } 587 } 588 } 589 590 #[test] 591 fn english_buyer_visible_copy_does_not_use_checkout_wording() { 592 for key in AppTextKey::ALL 593 .iter() 594 .copied() 595 .filter(|key| is_buyer_visible_text_key(*key)) 596 { 597 let normalized_copy = app_text(key).to_lowercase(); 598 assert!( 599 !contains_reserved_payment_action_term(&normalized_copy, "checkout"), 600 "{} contains buyer-visible checkout wording", 601 key.id() 602 ); 603 } 604 } 605 606 #[test] 607 fn english_trade_workflow_copy_stays_compact_and_product_facing() { 608 for key in AppTextKey::ALL 609 .iter() 610 .copied() 611 .filter(|key| is_trade_workflow_text_key(*key)) 612 { 613 let copy = app_text(key); 614 let normalized_copy = copy.to_lowercase(); 615 assert!( 616 copy.split_whitespace().count() <= 4, 617 "{} is too long for a compact workflow badge", 618 key.id() 619 ); 620 for pattern in FORBIDDEN_TRADE_WORKFLOW_LEAKAGE_PATTERNS { 621 assert!( 622 !normalized_copy.contains(pattern), 623 "{} contains workflow implementation copy `{pattern}`", 624 key.id() 625 ); 626 } 627 } 628 } 629 630 #[test] 631 fn english_trade_workflow_copy_matches_the_projection_contract() { 632 assert_eq!( 633 app_text(AppTextKey::TradeWorkflowAxisAgreement), 634 "Agreement" 635 ); 636 assert_eq!(app_text(AppTextKey::TradeWorkflowAxisSource), "Source"); 637 assert_eq!( 638 app_text(AppTextKey::TradeWorkflowAgreementOrdered), 639 "Ordered" 640 ); 641 assert_eq!( 642 app_text(AppTextKey::TradeWorkflowAgreementConfirmed), 643 "Confirmed" 644 ); 645 assert_eq!( 646 app_text(AppTextKey::TradeWorkflowAgreementNeedsReview), 647 "Needs review" 648 ); 649 assert_eq!( 650 app_text(AppTextKey::TradeWorkflowRevisionChangeProposed), 651 "Change proposed" 652 ); 653 assert_eq!( 654 app_text(AppTextKey::TradeWorkflowRevisionKeptAsPlaced), 655 "Kept as placed" 656 ); 657 assert_eq!( 658 app_text(AppTextKey::TradeWorkflowInventoryReserved), 659 "Reserved" 660 ); 661 assert_eq!(app_text(AppTextKey::TradeWorkflowProvenanceCli), "CLI"); 662 assert_eq!( 663 app_text(AppTextKey::TradeWorkflowProvenanceLocalEvents), 664 "Local events" 665 ); 666 } 667 668 #[test] 669 fn validation_receipt_copy_covers_passive_evidence() { 670 for (key, expected) in [ 671 (AppTextKey::TradeValidationReceiptSectionLabel, "Validation"), 672 (AppTextKey::TradeValidationReceiptEventLabel, "Receipt"), 673 (AppTextKey::TradeValidationReceiptTargetLabel, "Target"), 674 ( 675 AppTextKey::TradeValidationReceiptEventSetRootLabel, 676 "Evidence set", 677 ), 678 ( 679 AppTextKey::TradeValidationReceiptReducerOutputRootLabel, 680 "Review output", 681 ), 682 ( 683 AppTextKey::TradeValidationReceiptPublicValuesHashLabel, 684 "Verification values", 685 ), 686 ( 687 AppTextKey::TradeValidationReceiptRecordedAtLabel, 688 "Recorded", 689 ), 690 (AppTextKey::TradeValidationReceiptResultValid, "Valid"), 691 ( 692 AppTextKey::TradeValidationReceiptResultNeedsReview, 693 "Needs review", 694 ), 695 ( 696 AppTextKey::TradeValidationReceiptTypeListingValidation, 697 "Listing", 698 ), 699 ( 700 AppTextKey::TradeValidationReceiptTypeTradeTransition, 701 "Trade", 702 ), 703 ( 704 AppTextKey::TradeValidationReceiptTypeInventoryState, 705 "Stock", 706 ), 707 ( 708 AppTextKey::TradeValidationReceiptTypeStateCheckpoint, 709 "State", 710 ), 711 (AppTextKey::TradeValidationReceiptProofNone, "None"), 712 (AppTextKey::TradeValidationReceiptProofSp1Core, "Core proof"), 713 ( 714 AppTextKey::TradeValidationReceiptProofSp1Compressed, 715 "Compressed proof", 716 ), 717 ( 718 AppTextKey::TradeValidationReceiptProofSp1Groth16, 719 "Groth16 proof", 720 ), 721 ( 722 AppTextKey::TradeValidationReceiptProofSp1Plonk, 723 "Plonk proof", 724 ), 725 ] { 726 assert_eq!(app_text(key), expected); 727 assert!( 728 app_text(key).split_whitespace().count() <= 3, 729 "{} is too long for compact validation receipt evidence", 730 key.id() 731 ); 732 } 733 } 734 735 #[test] 736 fn english_marketplace_orders_copy_matches_the_buyer_history_contract() { 737 assert_eq!( 738 app_text(AppTextKey::PersonalOrdersSurfaceBody), 739 "Review orders placed on this device." 740 ); 741 assert_eq!( 742 app_text(AppTextKey::PersonalOrdersEmptyTitle), 743 "No orders yet" 744 ); 745 assert_eq!( 746 app_text(AppTextKey::PersonalOrdersListTitle), 747 "Order history" 748 ); 749 assert_eq!( 750 app_text(AppTextKey::PersonalOrdersStatusPlaced), 751 "Requested" 752 ); 753 assert_eq!( 754 app_text(AppTextKey::PersonalOrdersStatusScheduled), 755 "Agreed" 756 ); 757 assert_eq!(app_text(AppTextKey::PersonalOrdersStatusReady), "Agreed"); 758 assert_eq!( 759 app_text(AppTextKey::PersonalOrdersStatusCompleted), 760 "Agreed" 761 ); 762 assert_eq!( 763 app_text(AppTextKey::PersonalOrdersStatusDeclined), 764 "Declined" 765 ); 766 assert_eq!( 767 app_text(AppTextKey::PersonalOrdersStatusNeedsReview), 768 "Needs review" 769 ); 770 assert_eq!( 771 app_text(AppTextKey::PersonalOrdersDetailTitle), 772 "Order detail" 773 ); 774 assert_eq!( 775 app_text(AppTextKey::PersonalOrdersDetailEmptyBody), 776 "Select an order to review the details." 777 ); 778 assert_eq!( 779 app_text(AppTextKey::PersonalOrdersDetailFulfillmentLabel), 780 "Pickup/delivery" 781 ); 782 assert_eq!( 783 app_text(AppTextKey::PersonalOrdersDetailNoteLabel), 784 "Order note" 785 ); 786 assert_eq!( 787 app_text(AppTextKey::PersonalOrdersActionCancel), 788 "Cancel order" 789 ); 790 assert_eq!( 791 app_text(AppTextKey::PersonalOrdersActionAcceptChange), 792 "Accept change" 793 ); 794 assert_eq!( 795 app_text(AppTextKey::PersonalOrdersActionKeepOrder), 796 "Keep order" 797 ); 798 assert_eq!( 799 app_text(AppTextKey::PersonalOrdersRepeatDemandTitle), 800 "Reorder" 801 ); 802 assert_eq!( 803 app_text(AppTextKey::PersonalOrdersRepeatDemandActionEligible), 804 "Reorder" 805 ); 806 assert_eq!( 807 app_text(AppTextKey::PersonalOrdersRepeatDemandActionPartial), 808 "Reorder available items" 809 ); 810 assert_eq!( 811 app_text(AppTextKey::PersonalOrdersRepeatDemandNotePartialSingle), 812 "One item from this order is currently unavailable to reorder." 813 ); 814 assert_eq!( 815 app_text(AppTextKey::PersonalOrdersRepeatDemandNotePartialMultiple), 816 "Some items from this order are currently unavailable to reorder." 817 ); 818 assert_eq!( 819 app_text(AppTextKey::PersonalOrdersRepeatDemandNoteUnavailable), 820 "Items from this order are currently unavailable to reorder." 821 ); 822 assert_eq!( 823 app_text(AppTextKey::PersonalOrdersCoordinationRetryTitle), 824 "Finish sharing saved orders" 825 ); 826 assert_eq!( 827 app_text(AppTextKey::PersonalOrdersCoordinationRetryBody), 828 "A saved order still needs to be shared with your order tools." 829 ); 830 assert_eq!( 831 app_text(AppTextKey::PersonalOrdersCoordinationRetryAction), 832 "Try sharing again" 833 ); 834 } 835 836 #[test] 837 fn english_pack_day_copy_matches_the_contextual_execution_contract() { 838 assert_eq!(app_text(AppTextKey::PackDayTitle), "Pack day"); 839 assert_eq!( 840 app_text(AppTextKey::PackDayWindowSummaryTitle), 841 "Window summary" 842 ); 843 assert_eq!( 844 app_text(AppTextKey::PackDayTotalsTitle), 845 "Totals by product" 846 ); 847 assert_eq!(app_text(AppTextKey::PackDayPackListTitle), "Pack list"); 848 assert_eq!( 849 app_text(AppTextKey::PackDayPickupRosterTitle), 850 "Pickup roster" 851 ); 852 assert_eq!( 853 app_text(AppTextKey::PackDayEmptyTitle), 854 "Nothing to pack yet" 855 ); 856 assert_eq!(app_text(AppTextKey::PackDayExportTitle), "Export pack day"); 857 assert_eq!( 858 app_text(AppTextKey::PackDayExportReadyTitle), 859 "Ready to save locally" 860 ); 861 assert_eq!( 862 app_text(AppTextKey::PackDayExportUnavailableTitle), 863 "Not ready yet" 864 ); 865 assert_eq!( 866 app_text(AppTextKey::PackDayExportRunningTitle), 867 "Saving locally" 868 ); 869 assert_eq!( 870 app_text(AppTextKey::PackDayExportSucceededTitle), 871 "Saved locally" 872 ); 873 assert_eq!( 874 app_text(AppTextKey::PackDayExportFailedTitle), 875 "Couldn't save export" 876 ); 877 assert_eq!(app_text(AppTextKey::PackDayExportAction), "Export pack day"); 878 assert_eq!( 879 app_text(AppTextKey::PackDayExportActionRunning), 880 "Exporting..." 881 ); 882 assert_eq!(app_text(AppTextKey::PackDayExportFolderLabel), "Folder"); 883 assert_eq!(app_text(AppTextKey::PackDayExportFilesLabel), "Files"); 884 assert_eq!(app_text(AppTextKey::PackDayExportErrorLabel), "Error"); 885 assert_eq!( 886 app_text(AppTextKey::PackDayPrintPackSheetAction), 887 "Print pack sheet" 888 ); 889 assert_eq!( 890 app_text(AppTextKey::PackDayPrintPackSheetActionRunning), 891 "Printing pack sheet..." 892 ); 893 assert_eq!( 894 app_text(AppTextKey::PackDayPrintPickupRosterAction), 895 "Print pickup roster" 896 ); 897 assert_eq!( 898 app_text(AppTextKey::PackDayPrintPickupRosterActionRunning), 899 "Printing pickup roster..." 900 ); 901 assert_eq!( 902 app_text(AppTextKey::PackDayPrintCustomerLabelsAction), 903 "Print customer labels (Avery 5160)" 904 ); 905 assert_eq!( 906 app_text(AppTextKey::PackDayPrintCustomerLabelsActionRunning), 907 "Printing customer labels (Avery 5160)..." 908 ); 909 assert_eq!( 910 app_text(AppTextKey::PackDayPrintUnavailableTitle), 911 "Print not available yet" 912 ); 913 assert_eq!( 914 app_text(AppTextKey::PackDayPrintUnavailableBody), 915 "Print actions become available after pack day files are saved locally." 916 ); 917 assert_eq!( 918 app_text(AppTextKey::PackDayPrintPackSheetQueuedTitle), 919 "Queueing pack sheet" 920 ); 921 assert_eq!( 922 app_text(AppTextKey::PackDayPrintPackSheetSubmittedTitle), 923 "Sent pack sheet to the printer" 924 ); 925 assert_eq!( 926 app_text(AppTextKey::PackDayPrintPackSheetFailedTitle), 927 "Couldn't print pack sheet" 928 ); 929 assert_eq!( 930 app_text(AppTextKey::PackDayPrintPickupRosterQueuedTitle), 931 "Queueing pickup roster" 932 ); 933 assert_eq!( 934 app_text(AppTextKey::PackDayPrintPickupRosterSubmittedTitle), 935 "Sent pickup roster to the printer" 936 ); 937 assert_eq!( 938 app_text(AppTextKey::PackDayPrintPickupRosterFailedTitle), 939 "Couldn't print pickup roster" 940 ); 941 assert_eq!( 942 app_text(AppTextKey::PackDayPrintCustomerLabelsQueuedTitle), 943 "Queueing customer labels" 944 ); 945 assert_eq!( 946 app_text(AppTextKey::PackDayPrintCustomerLabelsSubmittedTitle), 947 "Sent customer labels to the printer" 948 ); 949 assert_eq!( 950 app_text(AppTextKey::PackDayPrintCustomerLabelsFailedTitle), 951 "Couldn't print customer labels" 952 ); 953 assert_eq!( 954 app_text(AppTextKey::PackDayPrintCustomerLabelsAvery5160OverflowFailedTitle), 955 "Customer labels do not fit Avery 5160" 956 ); 957 assert_eq!(app_text(AppTextKey::PackDayBatchPrintAction), "Print all"); 958 assert_eq!( 959 app_text(AppTextKey::PackDayBatchPrintActionRunning), 960 "Printing all..." 961 ); 962 assert_eq!( 963 app_text(AppTextKey::PackDayBatchPrintQueuedTitle), 964 "Queueing pack day print run" 965 ); 966 assert_eq!( 967 app_text(AppTextKey::PackDayBatchPrintSucceededTitle), 968 "Sent all pack day files to the printer" 969 ); 970 assert_eq!( 971 app_text(AppTextKey::PackDayBatchPrintFailedTitle), 972 "Couldn't print all pack day files" 973 ); 974 assert_eq!( 975 app_text(AppTextKey::PackDayBatchPrintFailedPreflightTitle), 976 "Pack day files are not ready to print" 977 ); 978 assert_eq!( 979 app_text(AppTextKey::PackDayBatchPrintFailedQueueLaunchTitle), 980 "Couldn't start the print queue" 981 ); 982 assert_eq!( 983 app_text(AppTextKey::PackDayBatchPrintFailedQueueExitTitle), 984 "Print queue stopped before the run finished" 985 ); 986 assert_eq!( 987 app_text(AppTextKey::PackDayBatchPrintCustomerLabelsAvery5160OverflowFailedTitle), 988 "Customer labels do not fit Avery 5160" 989 ); 990 assert_eq!( 991 app_text(AppTextKey::PackDayHostHandoffRevealAction), 992 "Show in Finder" 993 ); 994 assert_eq!( 995 app_text(AppTextKey::PackDayHostHandoffRevealActionRunning), 996 "Showing in Finder..." 997 ); 998 assert_eq!( 999 app_text(AppTextKey::PackDayHostHandoffOpenPackSheetAction), 1000 "Open pack sheet" 1001 ); 1002 assert_eq!( 1003 app_text(AppTextKey::PackDayHostHandoffOpenPackSheetActionRunning), 1004 "Opening pack sheet..." 1005 ); 1006 assert_eq!( 1007 app_text(AppTextKey::PackDayHostHandoffOpenPickupRosterAction), 1008 "Open pickup roster" 1009 ); 1010 assert_eq!( 1011 app_text(AppTextKey::PackDayHostHandoffOpenPickupRosterActionRunning), 1012 "Opening pickup roster..." 1013 ); 1014 assert_eq!( 1015 app_text(AppTextKey::PackDayHostHandoffOpenCustomerLabelsAction), 1016 "Open customer labels" 1017 ); 1018 assert_eq!( 1019 app_text(AppTextKey::PackDayHostHandoffOpenCustomerLabelsActionRunning), 1020 "Opening customer labels..." 1021 ); 1022 assert_eq!( 1023 app_text(AppTextKey::PackDayHostHandoffRevealSucceededTitle), 1024 "Shown in Finder" 1025 ); 1026 assert_eq!( 1027 app_text(AppTextKey::PackDayHostHandoffOpenPackSheetSucceededTitle), 1028 "Opened pack sheet" 1029 ); 1030 assert_eq!( 1031 app_text(AppTextKey::PackDayHostHandoffOpenPickupRosterSucceededTitle), 1032 "Opened pickup roster" 1033 ); 1034 assert_eq!( 1035 app_text(AppTextKey::PackDayHostHandoffOpenCustomerLabelsSucceededTitle), 1036 "Opened customer labels" 1037 ); 1038 assert_eq!( 1039 app_text(AppTextKey::PackDayHostHandoffRevealFailedTitle), 1040 "Couldn't show in Finder" 1041 ); 1042 assert_eq!( 1043 app_text(AppTextKey::PackDayHostHandoffOpenPackSheetFailedTitle), 1044 "Couldn't open pack sheet" 1045 ); 1046 assert_eq!( 1047 app_text(AppTextKey::PackDayHostHandoffOpenPickupRosterFailedTitle), 1048 "Couldn't open pickup roster" 1049 ); 1050 assert_eq!( 1051 app_text(AppTextKey::PackDayHostHandoffOpenCustomerLabelsFailedTitle), 1052 "Couldn't open customer labels" 1053 ); 1054 } 1055 1056 #[test] 1057 fn english_farm_rules_host_copy_matches_the_frozen_utility_window_inventory() { 1058 assert_eq!(app_text(AppTextKey::SettingsNavFarm), "Farm"); 1059 assert_eq!( 1060 app_text(AppTextKey::SettingsFarmPanelBody), 1061 "Farm profile and pickup details stay local on this device." 1062 ); 1063 assert_eq!( 1064 app_text(AppTextKey::SettingsFarmUnavailableBody), 1065 "Finish setting up a farm before editing farm settings on this device." 1066 ); 1067 assert_eq!(app_text(AppTextKey::SettingsFarmSaveAction), "Save changes"); 1068 assert_eq!( 1069 app_text(AppTextKey::SettingsFarmSaveSaved), 1070 "Saved locally on this device." 1071 ); 1072 assert_eq!( 1073 app_text(AppTextKey::SettingsFarmSavePending), 1074 "Save changes to keep this on this device." 1075 ); 1076 assert_eq!( 1077 app_text(AppTextKey::SettingsFarmSaveBlocked), 1078 "Complete the highlighted fields before saving." 1079 ); 1080 assert_eq!( 1081 app_text(AppTextKey::SettingsFarmSaveFailed), 1082 "Could not save farm settings on this device." 1083 ); 1084 assert_eq!( 1085 app_text(AppTextKey::SettingsPickupLocationsSectionLabel), 1086 "Pickup locations" 1087 ); 1088 assert_eq!( 1089 app_text(AppTextKey::SettingsPickupLocationsEmptyBody), 1090 "Add a pickup location so customers know where to collect orders." 1091 ); 1092 assert_eq!( 1093 app_text(AppTextKey::SettingsPickupLocationsAddAction), 1094 "Add pickup location" 1095 ); 1096 assert_eq!( 1097 app_text(AppTextKey::SettingsPickupLocationsMakeDefaultAction), 1098 "Make default" 1099 ); 1100 assert_eq!( 1101 app_text(AppTextKey::SettingsPickupLocationsDefaultBadge), 1102 "Default" 1103 ); 1104 assert_eq!( 1105 app_text(AppTextKey::SettingsPickupLocationsRemoveAction), 1106 "Remove" 1107 ); 1108 assert_eq!( 1109 app_text(AppTextKey::SettingsOperatingRulesSectionLabel), 1110 "Operating rules" 1111 ); 1112 assert_eq!( 1113 app_text(AppTextKey::SettingsOperatingRulesInvalidPromiseLeadTime), 1114 "Enter whole hours, for example 24." 1115 ); 1116 assert_eq!( 1117 app_text(AppTextKey::SettingsFulfillmentWindowsSectionLabel), 1118 "Fulfillment windows" 1119 ); 1120 assert_eq!( 1121 app_text(AppTextKey::SettingsFulfillmentWindowsEmptyBody), 1122 "Add a fulfillment window so customers know when orders are ready." 1123 ); 1124 assert_eq!( 1125 app_text(AppTextKey::SettingsFulfillmentWindowsPickupLocationsBody), 1126 "Add a pickup location before saving a fulfillment window." 1127 ); 1128 assert_eq!( 1129 app_text(AppTextKey::SettingsFulfillmentWindowsAddAction), 1130 "Add fulfillment window" 1131 ); 1132 assert_eq!( 1133 app_text(AppTextKey::SettingsFulfillmentWindowsItemLabel), 1134 "Fulfillment window" 1135 ); 1136 assert_eq!( 1137 app_text(AppTextKey::SettingsBlackoutPeriodsSectionLabel), 1138 "Blackout periods" 1139 ); 1140 assert_eq!( 1141 app_text(AppTextKey::SettingsBlackoutPeriodsEmptyBody), 1142 "Add a blackout period for days when this farm is unavailable." 1143 ); 1144 assert_eq!( 1145 app_text(AppTextKey::SettingsBlackoutPeriodsAddAction), 1146 "Add blackout period" 1147 ); 1148 assert_eq!( 1149 app_text(AppTextKey::SettingsBlackoutPeriodsItemLabel), 1150 "Blackout period" 1151 ); 1152 assert_eq!( 1153 app_text(AppTextKey::SettingsReadinessSectionLabel), 1154 "Readiness" 1155 ); 1156 assert_eq!( 1157 app_text(AppTextKey::SettingsReadinessFieldInvalidTimingConflicts), 1158 "Invalid timing conflicts" 1159 ); 1160 assert_eq!( 1161 app_text(AppTextKey::SettingsReadinessFieldFulfillmentWindowEndsBeforeStart), 1162 "A fulfillment window ends before it starts." 1163 ); 1164 assert_eq!( 1165 app_text(AppTextKey::SettingsReadinessFieldBlackoutOverlapsFulfillmentWindow), 1166 "A blackout period overlaps a fulfillment window." 1167 ); 1168 assert_eq!(app_text(AppTextKey::SettingsReadinessReady), "Ready"); 1169 } 1170 1171 #[test] 1172 fn startup_identity_choice_keys_remain_defined_in_the_typed_registry_source() { 1173 let source = include_str!("keys.rs"); 1174 1175 for entry in [ 1176 "HomeSetupContinueAction => \"home.setup.continue_action\"", 1177 "HomeSetupGenerateKeyAction => \"home.setup.generate_key_action\"", 1178 "HomeSetupConnectSignerAction => \"home.setup.connect_signer_action\"", 1179 "HomeSetupSignerSourcePlaceholder => \"home.setup.signer_source.placeholder\"", 1180 "HomeSetupSignerConnectAction => \"home.setup.signer_connect_action\"", 1181 "HomeSetupBackAction => \"home.setup.back_action\"", 1182 "HomeSetupSignerReviewTitle => \"home.setup.signer.review_title\"", 1183 "HomeSetupSignerSourceLabel => \"home.setup.signer.source_label\"", 1184 "HomeSetupSignerSignerLabel => \"home.setup.signer.signer_label\"", 1185 "HomeSetupSignerRelaysLabel => \"home.setup.signer.relays_label\"", 1186 "HomeSetupSignerPermissionsLabel => \"home.setup.signer.permissions_label\"", 1187 "HomeSetupSignerConnectingTitle => \"home.setup.signer.connecting_title\"", 1188 "HomeSetupSignerPendingTitle => \"home.setup.signer.pending_title\"", 1189 "HomeSetupSignerAuthChallengeTitle => \"home.setup.signer.auth_challenge_title\"", 1190 "HomeSetupSignerApprovedTitle => \"home.setup.signer.approved_title\"", 1191 "HomeSetupIssueUnavailableBody => \"home.setup.issue.unavailable_body\"", 1192 "HomeSetupErrorStartupFailed => \"home.setup.error.startup_failed\"", 1193 "HomeSetupSignerSourceValueBunkerUri => \"home.setup.signer.source_value.bunker_uri\"", 1194 "HomeSetupSignerSourceValueDiscoveryUrl => \"home.setup.signer.source_value.discovery_url\"", 1195 "HomeSetupSignerPermissionSignEventKind1 => \"home.setup.signer.permission.sign_event_kind_1\"", 1196 "HomeSetupSignerPermissionSwitchRelays => \"home.setup.signer.permission.switch_relays\"", 1197 "HomeSetupSignerPermissionAdditional => \"home.setup.signer.permission.additional\"", 1198 "HomeSetupSignerErrorEnterSource => \"home.setup.signer.error.enter_source\"", 1199 "HomeSetupSignerErrorUseSignerUri => \"home.setup.signer.error.use_signer_uri\"", 1200 "HomeSetupSignerErrorMissingDiscoveryUri => \"home.setup.signer.error.missing_discovery_uri\"", 1201 "HomeSetupSignerErrorInvalidDiscoveryUrl => \"home.setup.signer.error.invalid_discovery_url\"", 1202 "HomeSetupSignerErrorInvalidRemoteSignerUri => \"home.setup.signer.error.invalid_remote_signer_uri\"", 1203 "HomeSetupSignerErrorPendingApprovalExists => \"home.setup.signer.error.pending_approval_exists\"", 1204 "HomeSetupSignerErrorConnectionFailed => \"home.setup.signer.error.connection_failed\"", 1205 ] { 1206 assert!( 1207 source.contains(entry), 1208 "typed startup identity-choice registry is missing {entry}" 1209 ); 1210 } 1211 } 1212 1213 #[test] 1214 fn english_startup_identity_choice_copy_matches_the_next_launcher_contract() { 1215 assert_eq!(app_text(AppTextKey::HomeSetupContinueAction), "Continue"); 1216 assert_eq!( 1217 app_text(AppTextKey::HomeSetupGenerateKeyAction), 1218 "Generate key" 1219 ); 1220 assert_eq!( 1221 app_text(AppTextKey::HomeSetupConnectSignerAction), 1222 "Connect signer" 1223 ); 1224 assert_eq!( 1225 app_text(AppTextKey::HomeSetupSignerSourcePlaceholder), 1226 "Paste bunker URI or discovery URL" 1227 ); 1228 assert_eq!( 1229 app_text(AppTextKey::HomeSetupSignerConnectAction), 1230 "Connect signer" 1231 ); 1232 assert_eq!(app_text(AppTextKey::HomeSetupBackAction), "Back"); 1233 assert_eq!( 1234 app_text(AppTextKey::HomeSetupSignerReviewTitle), 1235 "Review signer details" 1236 ); 1237 assert_eq!(app_text(AppTextKey::HomeSetupSignerSourceLabel), "Source"); 1238 assert_eq!(app_text(AppTextKey::HomeSetupSignerSignerLabel), "Signer"); 1239 assert_eq!(app_text(AppTextKey::HomeSetupSignerRelaysLabel), "Relays"); 1240 assert_eq!( 1241 app_text(AppTextKey::HomeSetupSignerPermissionsLabel), 1242 "Permissions" 1243 ); 1244 assert_eq!( 1245 app_text(AppTextKey::HomeSetupSignerConnectingTitle), 1246 "Connecting to signer" 1247 ); 1248 assert_eq!( 1249 app_text(AppTextKey::HomeSetupSignerPendingTitle), 1250 "Waiting for signer approval" 1251 ); 1252 assert_eq!( 1253 app_text(AppTextKey::HomeSetupSignerAuthChallengeTitle), 1254 "Continue in your signer" 1255 ); 1256 assert_eq!( 1257 app_text(AppTextKey::HomeSetupSignerApprovedTitle), 1258 "Signer approved" 1259 ); 1260 assert_eq!( 1261 app_text(AppTextKey::HomeSetupIssueUnavailableBody), 1262 "Radroots couldn't start normally on this device. Check the local setup and try again." 1263 ); 1264 assert_eq!( 1265 app_text(AppTextKey::HomeSetupErrorStartupFailed), 1266 "Couldn't finish startup right now. Check the connection and try again." 1267 ); 1268 assert_eq!( 1269 app_text(AppTextKey::HomeSetupSignerSourceValueBunkerUri), 1270 "Bunker URI" 1271 ); 1272 assert_eq!( 1273 app_text(AppTextKey::HomeSetupSignerSourceValueDiscoveryUrl), 1274 "Discovery URL" 1275 ); 1276 assert_eq!( 1277 app_text(AppTextKey::HomeSetupSignerPermissionSignEventKind1), 1278 "Sign notes" 1279 ); 1280 assert_eq!( 1281 app_text(AppTextKey::HomeSetupSignerPermissionSwitchRelays), 1282 "Switch relays" 1283 ); 1284 assert_eq!( 1285 app_text(AppTextKey::HomeSetupSignerPermissionAdditional), 1286 "Additional permission" 1287 ); 1288 assert_eq!( 1289 app_text(AppTextKey::HomeSetupSignerErrorEnterSource), 1290 "Paste a bunker URI or discovery URL from your signer to continue." 1291 ); 1292 assert_eq!( 1293 app_text(AppTextKey::HomeSetupSignerErrorUseSignerUri), 1294 "Use a bunker URI or discovery URL from your signer." 1295 ); 1296 assert_eq!( 1297 app_text(AppTextKey::HomeSetupSignerErrorMissingDiscoveryUri), 1298 "The discovery URL is missing the signer address." 1299 ); 1300 assert_eq!( 1301 app_text(AppTextKey::HomeSetupSignerErrorInvalidDiscoveryUrl), 1302 "That discovery URL isn't valid. Check it and try again." 1303 ); 1304 assert_eq!( 1305 app_text(AppTextKey::HomeSetupSignerErrorInvalidRemoteSignerUri), 1306 "That signer address isn't valid. Check it and try again." 1307 ); 1308 assert_eq!( 1309 app_text(AppTextKey::HomeSetupSignerErrorPendingApprovalExists), 1310 "A signer connection is already waiting for approval." 1311 ); 1312 assert_eq!( 1313 app_text(AppTextKey::HomeSetupSignerErrorConnectionFailed), 1314 "Couldn't continue with the signer. Check the signer and try again." 1315 ); 1316 } 1317 1318 #[test] 1319 fn english_products_workflow_copy_matches_the_editor_contract() { 1320 assert_eq!(app_text(AppTextKey::ProductsAddAction), "Add product"); 1321 assert_eq!(app_text(AppTextKey::ProductsEditorTitle), "Product details"); 1322 assert_eq!( 1323 app_text(AppTextKey::ProductsEditorBody), 1324 "Saved locally on this device." 1325 ); 1326 assert_eq!(app_text(AppTextKey::ProductsEditorFieldTitle), "Name"); 1327 assert_eq!(app_text(AppTextKey::ProductsEditorFieldSubtitle), "Details"); 1328 assert_eq!( 1329 app_text(AppTextKey::ProductsEditorFieldCategory), 1330 "Category" 1331 ); 1332 assert_eq!(app_text(AppTextKey::ProductsEditorFieldUnit), "Unit"); 1333 assert_eq!( 1334 app_text(AppTextKey::ProductsEditorFieldPrice), 1335 "Price (USD)" 1336 ); 1337 assert_eq!(app_text(AppTextKey::ProductsEditorFieldStock), "Stock"); 1338 assert_eq!(app_text(AppTextKey::ProductsEditorFieldStatus), "Status"); 1339 assert_eq!(app_text(AppTextKey::ProductsEditorCloseAction), "Close"); 1340 assert_eq!( 1341 app_text(AppTextKey::ProductsEditorSaveAction), 1342 "Save changes" 1343 ); 1344 assert_eq!( 1345 app_text(AppTextKey::ProductsEditorPublishReadinessTitle), 1346 "Publish readiness" 1347 ); 1348 assert_eq!( 1349 app_text(AppTextKey::ProductsEditorReady), 1350 "This product is ready to publish." 1351 ); 1352 assert_eq!( 1353 app_text(AppTextKey::ProductsEditorBlockerAddProductName), 1354 "Add a product name." 1355 ); 1356 assert_eq!( 1357 app_text(AppTextKey::ProductsEditorBlockerChooseCategory), 1358 "Choose a category." 1359 ); 1360 assert_eq!( 1361 app_text(AppTextKey::ProductsEditorBlockerChooseUnit), 1362 "Choose a unit." 1363 ); 1364 assert_eq!( 1365 app_text(AppTextKey::ProductsEditorBlockerSetPrice), 1366 "Set a price." 1367 ); 1368 assert_eq!( 1369 app_text(AppTextKey::ProductsEditorBlockerSetStock), 1370 "Set available stock." 1371 ); 1372 assert_eq!( 1373 app_text(AppTextKey::ProductsEditorBlockerAttachAvailability), 1374 "Attach an availability window." 1375 ); 1376 assert_eq!( 1377 app_text(AppTextKey::ProductsUntitledDraft), 1378 "Untitled draft" 1379 ); 1380 } 1381 1382 #[test] 1383 fn host_locale_negotiation_reduces_to_supported_base_locale() { 1384 assert_eq!(resolve_locale_from_host("en_US.UTF-8"), "en"); 1385 assert_eq!(resolve_locale_from_host("en-GB"), "en"); 1386 assert_eq!(resolve_locale_from_host("en:fr"), "en"); 1387 assert_eq!(resolve_locale_from_host(""), "en"); 1388 assert_eq!(resolve_locale_from_host("C.UTF-8"), "en"); 1389 } 1390 1391 fn is_visible_action_text_key(key: AppTextKey) -> bool { 1392 let id = key.id(); 1393 id.contains(".action") || id.contains("_action") 1394 } 1395 1396 fn is_buyer_visible_text_key(key: AppTextKey) -> bool { 1397 key.id().starts_with("messages.personal.") 1398 } 1399 1400 fn is_trade_workflow_text_key(key: AppTextKey) -> bool { 1401 key.id().starts_with("messages.trade.workflow.") 1402 } 1403 1404 fn contains_reserved_payment_action_term(value: &str, term: &str) -> bool { 1405 if term.contains(' ') || term.contains('-') { 1406 return value.contains(term); 1407 } 1408 1409 value.match_indices(term).any(|(start, _)| { 1410 let end = start + term.len(); 1411 is_reserved_payment_term_boundary_before(value, start) 1412 && is_reserved_payment_term_boundary_after(value, end) 1413 }) 1414 } 1415 1416 fn is_reserved_payment_term_boundary_before(value: &str, index: usize) -> bool { 1417 if index == 0 { 1418 return true; 1419 } 1420 1421 is_reserved_payment_term_boundary_byte(value.as_bytes()[index - 1]) 1422 } 1423 1424 fn is_reserved_payment_term_boundary_after(value: &str, index: usize) -> bool { 1425 if index == value.len() { 1426 return true; 1427 } 1428 1429 is_reserved_payment_term_boundary_byte(value.as_bytes()[index]) 1430 } 1431 1432 fn is_reserved_payment_term_boundary_byte(byte: u8) -> bool { 1433 !byte.is_ascii_alphanumeric() && byte != b'_' && byte != b'-' 1434 } 1435 }