diff --git a/.github/workflows/fastlane_ios.yml b/.github/workflows/fastlane_ios.yml index 2dc038f8f..1f74b365d 100644 --- a/.github/workflows/fastlane_ios.yml +++ b/.github/workflows/fastlane_ios.yml @@ -134,5 +134,5 @@ jobs: - name: Upload IPA artifact uses: actions/upload-artifact@v4 with: - name: example-iOS.ipa + name: ${{ inputs.app_name }}_release_iOS.ipa path: ios/example-iOS.ipa diff --git a/android/app/build.gradle b/android/app/build.gradle index 1880caf68..e65af9a13 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -92,7 +92,7 @@ android { multiDexEnabled true // No need to increment the versionCode, it will be automatically incremented by the fastlane versionCode 218 - versionName "2.27.2" + versionName "2.28.0" manifestPlaceholders = [ tipsiStripeRedirectScheme: "coopcycle", diff --git a/ios/CoopCycle/Info.plist b/ios/CoopCycle/Info.plist index 8485c8422..3b94cdb51 100644 --- a/ios/CoopCycle/Info.plist +++ b/ios/CoopCycle/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2.27.1 + 2.28.0 CFBundleSignature ???? CFBundleURLTypes diff --git a/jestSetup.js b/jestSetup.js index 1bfa6fdb6..d0ca7d6db 100644 --- a/jestSetup.js +++ b/jestSetup.js @@ -1,6 +1,10 @@ /* global jest */ -import NavigationHolder from './src/NavigationHolder' +import NavigationHolder from './src/NavigationHolder'; + +jest.mock('react-native/Libraries/AppState/AppState', () => ({ + currentState: 'active', +})); jest.mock('react-native-localize', () => ({ findBestLanguageTag: () => ({ languageTag: 'en' }), @@ -8,26 +12,25 @@ jest.mock('react-native-localize', () => ({ jest.mock('react-native-blob-util', () => ({ fetch: () => {}, - wrap: () => {} + wrap: () => {}, })); jest.mock('expo-file-system', () => ({ createUploadTask: jest.fn(), FileSystemUploadType: { - MULTIPART: 1 + MULTIPART: 1, }, FileSystemSessionType: { - BACKGROUND: 0 - } + BACKGROUND: 0, + }, })); jest.mock('@react-native-firebase/analytics', () => ({ logEvent: jest.fn(), setUserProperty: jest.fn(), -})) +})); -jest.mock('@react-native-firebase/messaging', () => ({ -})) +jest.mock('@react-native-firebase/messaging', () => ({})); jest.mock('countly-sdk-react-native-bridge', () => ({ enableParameterTamperingProtection: () => {}, @@ -46,22 +49,20 @@ jest.mock('react-native-background-geolocation', () => ({ stop: jest.fn(), removeListeners: jest.fn(), changePace: jest.fn(), -})) +})); -jest.mock('@stripe/stripe-react-native', () => ({ -})) +jest.mock('@stripe/stripe-react-native', () => ({})); -jest.mock('react-native-share', () => ({ -})) +jest.mock('react-native-share', () => ({})); jest.mock('uuid', () => ({ v4: jest.fn(), -})) +})); const fakeNavigator = { current: { - dispatch: (action) => {} - } -} + dispatch: action => {}, + }, +}; -NavigationHolder.setNavigationRef(fakeNavigator) +NavigationHolder.setNavigationRef(fakeNavigator); diff --git a/src/Datadog.js b/src/Datadog.js index 9463f28d9..3833add54 100644 --- a/src/Datadog.js +++ b/src/Datadog.js @@ -29,14 +29,16 @@ const datadogConfig = new DatadogProviderConfiguration( applicationId, true, // track User interactions (e.g.: Tap on buttons. You can use 'accessibilityLabel' element property to give tap action the name, otherwise element type will be reported) true, // track XHR Resources - true, // track Errors + //FIXME: re-enable after migrating to react-native 0.76: https://github.com/facebook/hermes/issues/1496 + false, // track Errors ); // Optional: Select your Datadog website (one of "US1", "EU1", "US3", "US5", "AP1" or "GOV") datadogConfig.site = 'US1'; // Optional: Enable JavaScript long task collection datadogConfig.longTaskThresholdMs = 100; // Optional: enable or disable native crash reports -datadogConfig.nativeCrashReportEnabled = true; +//FIXME: re-enable after migrating to react-native 0.76: https://github.com/facebook/hermes/issues/1496 +datadogConfig.nativeCrashReportEnabled = false; // Optional: Sample RUM sessions (% of session are sent to Datadog. Default is 100%). datadogConfig.sessionSamplingRate = __DEV__ ? 100 : 20; // Optional: Sample tracing integrations for network calls between your app and your backend (% of calls to your instrumented backend are linked from the RUM view to the APM view. Default is 20%) diff --git a/src/components/NotificationHandler.js b/src/components/NotificationHandler.js index 743870987..98a6bed78 100644 --- a/src/components/NotificationHandler.js +++ b/src/components/NotificationHandler.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { @@ -26,32 +26,39 @@ export default function NotificationHandler() { const notificationsToDisplay = useSelector(selectNotificationsToDisplay); const notificationsWithSound = useSelector(selectNotificationsWithSound); + const hasNotifications = useMemo( + () => + notificationsToDisplay.length > 0 || notificationsWithSound.length > 0, + [notificationsToDisplay, notificationsWithSound], + ); + + const hasNotificationsWithSound = useMemo( + () => notificationsWithSound.length > 0, + [notificationsWithSound], + ); + const dispatch = useDispatch(); useEffect(() => { - if ( - notificationsToDisplay.length > 0 || - notificationsWithSound.length > 0 - ) { + // use memoized value to avoid re-setting timeout when more notifications arrive + if (hasNotifications) { setTimeout(() => { dispatch(clearNotifications()); }, NOTIFICATION_DURATION_MS); } - }, [notificationsToDisplay, notificationsWithSound, dispatch]); + }, [hasNotifications, dispatch]); useEffect(() => { // on Android, when notification is received, OS let us execute some code // but it's very limited, e.g. handlers set via setTimeout are not executed // so we do not play sound in that case, because we will not be able to stop it - if ( - notificationsWithSound.length > 0 && - AppState.currentState === 'active' - ) { + // use memoized value to avoid re-starting the sound when more notifications arrive + if (hasNotificationsWithSound && AppState.currentState === 'active') { dispatch(startSound()); } else { dispatch(stopSound()); } - }, [notificationsWithSound, dispatch]); + }, [hasNotificationsWithSound, dispatch]); return ( { const backgroundColor = useBackgroundContainerColor(); - const isAvailable = isRestaurantAvailable(restaurant); + const isAvailable = isRestaurantOrderingAvailable(restaurant); return ( diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 6bc2546cb..92de6312e 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -1,547 +1,549 @@ { - "common": { - "MY_ADDRESSES": "My addresses", - "MY_ORDERS": "My orders", - "MY_DETAILS": "My details", - "COURIER": "Courier", - "TASK": "Task", - "TASKS": "Tasks", - "ITEM": "{{count}} product", - "ITEM_plural": "{{count}} products", - "TASK_LIST": "Task List", - "WS_NOT_CONNECTED_WARNING": "Failed to connect to auto refresh. Retrying...", - "DISPATCH": "Dispatch", - "RESTAURANT": "Restaurant", - "RESTAURANTS": "Restaurants", - "BACK": "Back", - "CART": "Cart", - "CART_EMPTY_WARNING": "Your cart is empty", - "DELIVERY_ADDR": "Delivery address", - "PAYMENT": "Payment", - "PAY_AMOUNT": "Pay {{amount}}", - "ORDER_TRACKING": "Order tracking", - "TASKS_UPDATED": "Your tasks have been updated", - "FAILED_TASK_LOAD": "Could not load your tasks", - "FAILED_TASK_COMPLETE": "Could not complete this task", - "TRY_LATER": "Please try again later", - "NET_FAILED": "Network request failed", - "RETRY": "Retry", - "SERVER_INCOMPATIBLE": "Incompatible server", - "SERVER_INVALID": "Invalid server", - "SERVER_UNDER_MAINTENANCE": "The server is under maintenance", - "CHOOSE_SERVER": "Please choose a city to start", - "CHOOSE_CITY": "Choose a city", - "CITY_NOT_LISTED": "Don't see your city listed?", - "CONTACT_US": "Contact us", - "ABOUT_COOPCYCLE": "Unlike big platforms, with CoopCycle the implementation of a food delivery service is at the initiative of bike messengers, who decide to form a co-op at the local scale. \n\nIf your city is not listed, it means that there is no project (yet). \n\nIf you are a group of bike messengers, contact us to find out how we can help you to set up your project as a co-op.", - "ABOUT": "About", - "ABOUT_INSTANCE": "About {{name}}", - "CUSTOM": "Custom", - "SERVER_URL": "Your server is not listed? Enter its address", - "EXAMPLE": "Example", - "SUBMIT": "Submit", - "NEXT": "Next", - "CONNECTED_TO": "Connected to", - "CHANGE_SERVER": "Choose another server", - "LOADING": "Loading", - "HELLO": "Hello", - "DETAILS": "Details", - "ADDRESSES": "Addresses", - "ORDERS": "Orders", - "SIGN_OUT": "Sign out", - "CONTINUE": "Continue", - "EDIT": "Edit", - "NAME": "Name", - "ADDRESS_DESCRIPTION": "Instructions", - "ADDRESS_DESCRIPTION_PLACEHOLDER": "2nd door on the left…", - "TELEPHONE": "Telephone", - "TOTAL_ITEMS": "Total items", - "TOTAL_DELIVERY": "Total delivery", - "TOTAL": "Total", - "MY_TASKS": "My tasks", - "MY_STATS": "My statistics", - "ENTER_PAY_DETAILS": "Please enter your payment details", - "PAY": "Pay", - "CONNECTING_SERVER": "Connecting to the server", - "PLS_WAIT": "Please wait", - "WAITING_FOR_ORDER": "Waiting for an order", - "LOADING_ORDER": "Loading order", - "CALCULATING_ROUTE": "Calculating route", - "CALCULATING_YOUR_POS": "Determining your position", - "TRACKING_YOUR_POS": "Tracking your position", - "CONN_LOST": "Connection lost! Reconnecting", - "AUTHENTICATING": "Authenticating", - "REFUSE": "Refuse", - "ACCEPT": "Accept", - "ORDER_COLLECTED": "Order collected", - "ORDER_DELIVERED": "Order delivered", - "HOME": "Home", - "MY_ACCOUNT": "My account", - "FIND_RESTAURANT": "Find a restaurant", - "SEARCH_NEARBY": "Search nearby, browse menus, place your order", - "WELCOME": "Welcome", - "WELCOME_TEXT": "Thank you for downloading CoopCycle. \nTo start using the application, connect to a server.", - "NO_RESTAURANTS": "Sorry, we could not find any open restaurants", - "SEARCH_WITHOUT_DATE": "Do you want to restart the search without including a date", - "SEARCH_AGAIN": "Search again", - "ENTER_ADDRESS": "Enter your address", - "ENTER_POSTCODE": "Enter your postcode", - "TODAY": "Today", - "GO_TO_TODAY": "Go to Today", - "TOMORROW": "Tomorrow", - "CANCEL": "Cancel", - "VIEW": "Show", - "WHEN": "When", - "ADDRESS": "Address", - "POST_CODE": "Post code", - "CITY": "City", - "USERNAME": "Username", - "USERNAME_OR_EMAIL": "Username or email address", - "PASSWORD": "Password", - "EMAIL": "E-mail", - "CHECKOUT_FROM": "From {{date}}", - "ORDER": "Order", - "SCHEDULE_ORDER": "Schedule order", - "INVALID_USER_PASS": "Username and/or password incorrect", - "NO_TASKS": "No tasks planned today", - "NOTES": "Notes", - "COMPLETED": "Completed", - "FAILED": "Failed", - "COMPLETE_TASK": "Complete", - "VALIDATE": "Validate", - "MARK_FAILED": "Mark task as failed", - "REPORT_INCIDENT": "Report incident", - "SWIPE_TO_END": "Swipe right to end task, or to the left if there is a problem", - "WAITING_FOR_POS": "Waiting for location", - "PROBLEM_CONNECTING": "Problem connecting", - "OK": "OK", - "LAST_TASK_EVENT": "Last event {{fromNow}}", - "PREVIOUS_TASK": "Previous task", - "NEXT_TASK": "Next task", - "HISTORY": "History", - "SETTINGS": "Settings", - "SETTING_KEEP_AWAKE": "Disable screen lock", - "OR_REGISTER": "Don't have an account? Register", - "OR_LOGIN": "Already have an account? Login", - "FORGOT_PASSWORD": "Forgot password?", - "RESET_PASSWORD_CHECK_EMAIL": "Reset password", - "RESET_PASSWORD_CHECK_EMAIL_DISCLAIMER": "An email has been sent to {{email}}. It contains a link you must click to reset your password.", - "RESET_PASSWORD_NEW_PASSWORD": "Reset password", - "RESET_PASSWORD_LINK_EXPIRED": "Link has expired, try to reset password again", - "GIVEN_NAME": "First name", - "FAMILY_NAME": "Last name", - "PHONE_NUMBER": "Phone number", - "CONFIRM_PASSWORD": "Confirm password", - "EMAIL_ALREADY_REGISTERED": "This e-mail has already been registered", - "INVALID_GIVEN_NAME": "Please enter a valid first name", - "INVALID_FAMILY_NAME": "Please enter a valid last name", - "INVALID_EMAIL": "Please enter a valid email", - "INVALID_PHONE_NUMBER": "Please enter a valid phone number", - "INVALID_USERNAME": "Please enter a valid username", - "INVALID_USERNAME_FORMAT": "A username can only contain alphanumeric characters (letters A-Z, numbers 0-9) with the exception of underscores", - "INVALID_PASSWORD": "Please enter a valid password at least 8 characters", - "INVALID_PASSWORD_CONFIRMATION": "Password confirmation does not match", - "TASKS_FILTER": "Tasks", - "HIDE_DONE_TASKS": "Hide done tasks", - "TASKS_SHOW_POLYLINE": "Show line linking tasks", - "HIDE_FAILED_TASKS": "Hide failed tasks", - "HIDE_TASKS_TAGGED_WITH": "Hide tasks tagged with", - "TASKS_CHANGED_ALERT_SOUND": "Tasks changed alert sound", - "RESTAURANT_LIST_CLICK_BELOW": "Click on a restaurant in the list below", - "RESTAURANT_ORDER_LIST_NEW_ORDERS": "New orders ({{count}})", - "RESTAURANT_ORDER_LIST_ACCEPTED_ORDERS": "Accepted ({{count}})", - "RESTAURANT_ORDER_LIST_STARTED_ORDERS": "In preparation ({{count}})", - "RESTAURANT_ORDER_LIST_READY_ORDERS": "Ready ({{count}})", - "RESTAURANT_ORDER_LIST_CANCELLED_ORDERS": "Cancelled ({{count}})", - "RESTAURANT_ORDER_LIST_FULFILLED_ORDERS": "Delivered ({{count}})", - "RESTAURANT_ORDER_BUTTON_ACCEPT": "Accept", - "RESTAURANT_ORDER_BUTTON_REFUSE": "Refuse", - "RESTAURANT_ORDER_BUTTON_CANCEL": "Cancel", - "RESTAURANT_ORDER_BUTTON_DELAY": "Delay", - "RESTAURANT_ORDER_BUTTON_FULFILL": "Fulfill", - "RESTAURANT_ORDER_REFUSE_DISCLAIMER": "Please tell us why you want to decline this order so that we can inform the customer", - "RESTAURANT_ORDER_REFUSE_REASON_SOLD_OUT_HEADING": "I'm out of stock", - "RESTAURANT_ORDER_REFUSE_REASON_RUSH_HOUR_HEADING": "I'm in a rush", - "RESTAURANT_ORDER_REFUSE_REASON_CLOSING_HEADING": "I'm going to close", - "RESTAURANT_ORDER_REFUSE_REASON_ORDER_WILL_BE_REFUSED": "This order will be cancelled", - "RESTAURANT_ORDER_REFUSE_REASON_ORDER_CONTINUE_RECEIVING": "You will continue receiving new orders", - "RESTAURANT_ORDER_REFUSE_REASON_ORDER_STOP_RECEIVING": "You will not receive orders anymore", - "RESTAURANT_ORDER_REFUSE_REASON_NO_SHOW": "No show", - "RESTAURANT_ORDER_REFUSE_REASON_ORDER_WILL_BE_CAPTURED": "The order will be cancelled, but won't be refunded to customer", - "RESTAURANT_ORDER_PRINT": "Print", - "RESTAURANT_PRODUCTS": "Products", - "RESTAURANT_CLOSE_ALERT_TITLE": "Close until tomorrow", - "RESTAURANT_CLOSE_ALERT_MESSAGE": "This will close your restaurant for today", - "RESTAURANT_CLOSE_ALERT_CONFIRM": "Yes", - "RESTAURANT_OPENING_HOURS": "Opening hours", - "RESTAURANT_SPECIAL_OPENING_HOURS": "Exceptional closings", - "RESTAURANT_OPENING_HOURS_VALID_FROM_THROUGH": "From {{validFrom}} to {{validThrough}}", - "RESTAURANT_OPENING_HOURS_ONE_DAY": "{{day}} from {{opens}} to {{closes}}", - "RESTAURANT_OPENING_HOURS_DAY_RANGE": "From {{firstDay}} to {{lastDay}} from {{opens}} to {{closes}}", - "RESTAURANT_ORDER_ACCEPTED_CONFIRM_TITLE": "Order accepted!", - "RESTAURANT_ORDER_ACCEPTED_CONFIRM_BODY": "Order {{number}} ({{id}}) has been accepted", - "RESTAURANT_ORDER_CANCELLED_CONFIRM_TITLE": "Order cancelled!", - "RESTAURANT_ORDER_CANCELLED_CONFIRM_BODY": "Order {{number}} ({{id}}) has been cancelled", - "OPEN_IN_MAPS_TITLE": "Show task on map", - "OPEN_IN_MAPS_MESSAGE": "Choose an app", - "RESTAURANT_ORDER_TITLE": "Order {{order.number}}", - "RESTAURANT_ORDER_PREPARATION_EXPECTED_AT": "Should begin at {{date}}", - "RESTAURANT_ORDER_PICKUP_EXPECTED_AT": "Should be ready at {{date}}", - "RESTAURANT_ORDER_DELAY_MODAL_TITLE": "Delay order", - "RESTAURANT_ORDER_DELAY_DISCLAIMER": "How much delay would you like to apply?", - "RESTAURANT_ORDER_CANCEL_MODAL_TITLE": "Cancel", - "RESTAURANT_ORDER_CANCEL_DISCLAIMER": "Please tell us why you want to cancel this order", - "RESTAURANT_ORDER_CANCEL_REASON_CUSTOMER_HEADING": "Cancelled by customer", - "RESTAURANT_SETTINGS_RUSH": "Switch to rush mode", - "RESTAURANT_SETTINGS_CHANGE_RESTAURANT": "Change restaurant", - "RESTAURANT_SETTINGS_MANAGE_PRODUCTS": "Manage products", - "RESTAURANT_SETTINGS_OPENING_HOURS": "Opening hours", - "RESTAURANT_SETTINGS_PRINTER": "Printers", - "RESTAURANT_SETTINGS_HEADING": "Settings for restaurant {{- name}}", - "RESTAURANT_SETTINGS_MENUS": "Switch menus", - "RESTAURANT_SETTINGS_MENU_ACTIVATE": "Activate menu {{- name}}", - "RESTAURANT_ALERT_RUSH_MODE_ON": "Rush mode is activated", - "RESTAURANT_ALERT_CLOSED": "Restaurant is closed", - "RESTAURANT_PRINTER_CONNECTED_TITLE": "Device connected", - "RESTAURANT_PRINTER_CONNECTED_BODY": "The device {{name}} is connected", - "RESTAURANT_PRINTER_CONNECT_ERROR_TITLE": "Can't connect to device", - "RESTAURANT_PRINTER_DISCONNECTED_TITLE": "Device disconnected", - "RESTAURANT_PRINTER_DISCONNECTED_BODY": "The device {{name}} has been disconnected", - "RESTAURANT_SOUND_ALERT_TITLE": "Sound volume is low", - "RESTAURANT_SOUND_ALERT_MESSAGE": "The sound volume is quite low. You should increase it to make sure you hear notifications.", - "RESTAURANT_SOUND_ALERT_CONFIRM": "Ok, I will", - "CHECKOUT_PRODUCT_OPTIONS_TITLE": "Choose options", - "CHECKOUT_LOGIN_TITLE": "Authentication", - "CHECKOUT_LOGIN_DISCLAIMER": "Please authenticate to continue", - "CHECKOUT_SHIPPING_DATE": "Choose date", - "CHECKOUT_PLEASE_ENTER_ADDRESS": "Please enter your address", - "CHECKOUT_ADDRESS_NOT_VALID": "Sorry, we don't deliver at this address", - "CHECKOUT_ORDER_ADDRESS_DESCRIPTION": "Access information", - "CHECKOUT_ORDER_ADDRESS_DESCRIPTION_HELP": "Specify any useful information", - "CHECKOUT_ORDER_PHONE_NUMBER_HELP": "To contact you if there's a problem", - "CHECKOUT_ORDER_NOTES": "A message for the restaurant owner ?", - "CHECKOUT_ORDER_NOTES_HELP": "This message will be transmitted to the cook (allergies, special requests)", - "CHECKOUT_MORE_INFOS": "Additional infos", - "CHECKOUT_MORE_INFOS_DISCLAIMER": "Just one last step", - "CHECKOUT_PICK_DATE": "This store is closed for the moment, but you can schedule an order for later.", - "CHECKOUT_SCHEDULE_ORDER": "Schedule the order", - "SCHEDULE": "Schedule", - "IGNORE": "Skip", - "CHECKOUT_PRODUCT_OPTIONS_CHOICES_BETWEEN": "Between {{min}} and {{max}} choices", - "CHECKOUT_UNITS": "Units", - "ADD_TO_CART": "Add to cart", - "NOTIFICATION_TASKS_CHANGED_TITLE": "Tasks changed", - "NOTIFICATION_TASKS_CHANGED_DESC": "Your tasks from {{date}} have been updated, {{added}}", - "NOTIFICATION_ORDER_CREATED_TITLE": "New order", - "NOTIFICATION_ORDER_CREATED_DESC": "A new order for {{date}} has been created", - "NOTIFICATION_CLOSES_SOON": "Hop hop hop ! Hurry up, it remains {{diff}} until closing !", - "UNASSIGNED_TASKS": "Unassigned", - "DISPATCH_UNASSIGNED_TASKS": "Unassigned", - "DISPATCH_TASK_LISTS": "Assigned", - "DISPATCH_TASK_LIST": "Assigned to {{username}}", - "DISPATCH_ADD_TASK_LIST": "Add user", - "DISPATCH_ADD_TASK": "Add task", - "DISPATCH_CREATE_TASK": "Create task", - "DISPATCH_NO_TASKS": "No tasks yet", - "DISPATCH_TASK": "Task #{{id}}", - "DISPATCH_ASSIGN_TASK": "Assign task", - "DISPATCH_PICK_USER": "Pick a user", - "DISPATCH_ASSIGN_TO_ME": "Assign to me", - "TASK_FORM_ADDRESS_LABEL": "Address", - "TASK_FORM_DONE_AFTER_LABEL": "To do starting at", - "TASK_FORM_DONE_BEFORE_LABEL": "To do before", - "TASK_FORM_COMMENTS_LABEL": "Comments", - "DISPATCH_DATE": "Choose a date", - "BACKGROUND_GEOLOCATION_NOTIFICATION_TITLE": "Background tracking", - "BACKGROUND_GEOLOCATION_NOTIFICATION_TEXT": "Enabled", - "NEW_NOTIFICATION": "New notification", - "CLOSE": "Close", - "OPTIONAL": "Optional", - "ORDER_NEW": "Waiting", - "ORDER_NEW_HELP": "The restaurant has to confirm your order", - "ORDER_ACCEPTED": "Accepted", - "ORDER_FULFILLED": "Fulfilled", - "ORDER_CANCELLED": "Cancelled", - "ORDER_NUMBER": "Order {{number}}", - "ADDRESS_NOT_PRECISE_ENOUGH": "This address is not precise enough", - "REGISTER_CHECK_EMAIL": "Activation", - "REGISTER_CHECK_EMAIL_DISCLAIMER": "An email has been sent to {{email}}. It contains an activation link you must click to activate your account.", - "REGISTER_CHECK_EMAIL_ALREADY_ACTIVATED": "You have already activated your account?", - "REGISTER_CHECK_EMAIL_LOGIN": "Login", - "REGISTER_CONFIRM": "Activation", - "TASK_WITH_ID": "Task #{{id}}", - "SIGNATURE": "Signature", - "SIGNATURE_DISCLAIMER": "Sign below", - "SIGNATURE_CLEAR": "Clear", - "SIGNATURE_ADD": "Add signature", - "PHOTO": "Picture", - "PHOTO_ADD": "Add picture", - "PHOTO_DISCLAIMER": "Take a picture", - "LOAD_MORE": "Load more", - "TASK_IMAGE_UPLOAD_CONFIRM_SHORT": "Image uploaded!", - "TASK_IMAGE_UPLOAD_CONFIRM_LONG": "Image was successfully uploaded", - "AN_ERROR_OCCURRED": "An error occurred", - "DELIVERY_ASAP": "As soon as possible", - "CART_DELIVERY_TIME": "Delivery {{- fromNow}}", - "CART_DELIVERY_TIME_DIFF": "Delivery in {{diff}} minutes", - "CART_COLLECTION_TIME": "Collection {{- fromNow}}", - "CART_COLLECTION_TIME_DIFF": "Collection in {{diff}} minutes", - "TIME_DIFF_SHORT": "{{min}} - {{max}} minutes", - "TASK_ADD_PROOF_OF_DELIVERY": "Add a proof of delivery", - "STORE_NEW_DELIVERY": "New delivery", - "STORE_NEW_DELIVERY_ADDRESS": "Address", - "STORE_NEW_DELIVERY_SEARCH_CLIENT": "Search for a client", - "STORE_NEW_DELIVERY_ENTER_SEARCH_CLIENT": "Enter a client name", - "STORE_NEW_DELIVERY_ADDRESS_HELP": "Enter dropoff address to verify feasibility", - "STORE_NEW_DELIVERY_PHONE_NUMBER": "Phone number", - "STORE_NEW_DELIVERY_ENTER_PHONE_NUMBER": "Enter a valid phone number", - "STORE_NEW_DELIVERY_BUSINESS_NAME": "Business name", - "STORE_NEW_DELIVERY_ENTER_BUSINESS_NAME": "Enter a business name", - "STORE_NEW_DELIVERY_CONTACT_NAME": "Contact name", - "STORE_NEW_DELIVERY_ENTER_CONTACT_NAME": "Enter a contact name", - "STORE_NEW_DELIVERY_ADDRESS_DESCRIPTION": "Access information", - "STORE_NEW_DELIVERY_ENTER_ADDRESS_DESCRIPTION": "Induce any useful information (intercom...)", - "STORE_NEW_DELIVERY_COMMENTS": "Comments", - "STORE_NEW_DELIVERY_ENTER_COMMENTS": "Specify any useful details to complete the delviery (fragile, ...)", - "STORE_NEW_DELIVERY_WEIGHT": "Custom weight", - "STORE_NEW_DELIVERY_ENTER_WEIGHT": "Enter a custom weight", - "STORE_NEW_DELIVERY_PACKAGES": "Packages", - "STORE_NEW_DELIVERY_NO_PACKAGES": "No packages", - "STORE_NEW_DELIVERY_DROPOFF_BEFORE": "Dropoff before", - "STORE_NEW_DELIVERY_TIME_SLOT": "Time slot", - "STORE_NEW_DELIVERY_SELECT_TIME_SLOT": "Select a time slot", - "STORE_NEW_DELIVERY_ERROR": { - "EMPTY_TIME_SLOT": "Please select a time slot", - "EMPTY_CONTACT_NAME": "Please provide a name for the recipient", - "EMPTY_PHONE_NUMBER": "Please provide a phone number", - "EMPTY_WEIGHT": "Please provide a weight", - "EMPTY_PACKAGES": "Please select packages" - }, - "STORE_DELIVERY": "Delivery #{{id}}", - "DELIVERY_STATE": { - "undefined": "Unknown", - "unknown": "Unknown", - "new": "New", - "picked": "Picked", - "fulfilled": "Fulfilled" - }, - "DELIVERY_DETAILS_TIME_SLOT": "Time slot", - "DELIVERY_DETAILS_RECIPIENT": "Recipient", - "TODAY_BETWEEN_START_AND_END": "Today between {{start}} and {{end}}", - "TOMORROW_BETWEEN_START_AND_END": "Tomorrow between {{start}} and {{end}}", - "OTHER_DAY_BETWEEN_START_AND_END": "{{date}} between {{start}} and {{end}}", - "SIGNATURE_SCREEN_FIRST": "Signature screen as default", - "FINISHED": "Finished", - "SEARCH": "Find restaurants...", - "SEARCH_INPUT_PLACEHOLDER": "A shop, a dish...", - "GO_BACK_TO_RESTAURANTS": "Start again", - "SESSION_EXPIRED": "Your session has expired", - "TOUCH_TO_RELOAD": "Touch to reload", - "OFFLINE": "You are offline", - "SCAN_FOR_PRINTERS": "Tap to scan", - "AUTO_ACCEPT_ORDERS_PRINT_NUMBER_OF_COPIES": "Number of copies", - "SEARCH_WITH_ADDRESS": "Search « {{address}} »", - "RESTAURANT_ORDER_CONNECT_PRINTER": "Connect a printer", - "RESTAURANT_ORDER_PRINTING": "Printing order", - "RESTAURANT_ORDER_FAILED_TO_PRINT": "Failed to print", - "RESTAURANT_SEARCH_ORDERS": "Find order...", - "RESTAURANT_SEARCH_ORDERS_INPUT_PLACEHOLDER": "Order number...", - "SWIPE_TO_ACCEPT_REFUSE": "Slide to the right to accept, to the left to refuse", - "ADD_COUPON": "Add a voucher code", - "VOUCHER_CODE": "Voucher code", - "RECEIPT_HEADING_ORDER_NUMBER": "ORDER {{number}} (#{{id}})", - "RECEIPT_CUSTOMER_NAME": "CUSTOMER: {{customer}}", - "RECEIPT_HEADING_PICKUP_EXPECTED_AT": "AT {{time}}", - "RECEIPT_HEADING_PICKUP_EXPECTED_ON": "PICKUP EXPECTED ON {{time}}", - "RECEIPT_HEADING_PICKUP_EXPECTED_TODAY": "PICKUP TODAY", - "RESTRICTED_DIET": { - "DiabeticDiet": "Diabetic", - "GlutenFreeDiet": "Gluten free", - "HalalDiet": "Halal", - "HinduDiet": "Hindu", - "KosherDiet": "Kosher", - "LowCalorieDiet": "Low calorie", - "LowFatDiet": "Low fat", - "LowLactoseDiet": "Low lactose", - "LowSaltDiet": "Low salt", - "VeganDiet": "Vegan", - "VegetarianDiet": "Vegetarian" - }, - "ALLERGEN": { - "CEREALS_CONTAINING_GLUTEN": "Cereals containing gluten", - "CRUSTACEANS": "Crustaceans", - "EGGS": "Eggs", - "FISH": "Fish", - "PEANUTS": "Peanuts", - "SOYBEANS": "Soybeans", - "MILK": "Milk", - "NUTS": "Nuts", - "CELERY": "Celery", - "MUSTARD": "Mustard", - "SESAME_SEEDS": "Sesame seeds", - "SULPHUR_DIOXIDE_SULPHITES": "Sulphur dioxide", - "LUPIN": "Lupin", - "MOLLUSCS": "Molluscs" - }, - "TASK_COMPLETE_ALERT_TITLE": "Start or complete?", - "TASK_COMPLETE_ALERT_MESSAGE": "Do you want to start, or complete this task?", - "TASK_COMPLETE_ALERT_NEGATIVE": "▶ Start", - "TASK_COMPLETE_ALERT_POSITIVE": "✓ Complete", - "FULFILLMENT_METHOD": { - "collection": "Collection", - "delivery": "Delivery" - }, - "CART_COLLECTION_DISCLAIMER": "You have opted for click&collect. \nYou are responsible for collecting your order on the day and time selected. \nIf you have a problem, contact the establishment at {{telephone}}. \nPlease note - If you do not collect, you will be charged the full amount for the order.", - "LEARN_MORE": "Learn more", - "SELECTED": "Selected", - "AVAILABLE": "Available", - "COMING_SOON": "Coming soon", - "CARDHOLDER_NAME": "Cardholder name", - "RESTAURANT_ORDER_LIST_PICKED_ORDERS": "Picked orders ({{count}})", - "NOTIFICATION_TASKS_ADDED": "{{ count }} added", - "NOTIFICATION_TASKS_REMOVED": "{{ count }} removed", - "NOT_AVAILABLE_ATM": "Not available at the moment", - "BACKGROUND_PERMISSION_DISCLOSURE": { - "title": "Use your location", - "message": "To allow dispatchers to assign jobs accurately, allow CoopCycle to access this device's location even when the app is closed or not in use.\n\nThis data will be uploaded to the CoopCycle instance you are connected to." - }, - "BACKGROUND_PERMISSION_RATIONALE": { - "title": "Allow {applicationName} to access this device's location even when the app is closed or not in use", - "message": "When authenticated as a messenger, this app collects location data to allow dispatchers to assign jobs accurately", - "positiveAction": "Change to « {backgroundPermissionOptionLabel} »" - }, - "RESTAURANT_SETTINGS_MANAGE_PRODUCT_OPTIONS": "Manage product options", - "SELECT_PAYMENT_METHOD": "Select a payment method", - "PAYMENT_METHOD": { - "card": "Credit card", - "cash_on_delivery": "Cash on delivery", - "edenred": "Edenred", - "edenred+card": "Edenred" - }, - "CASH_ON_DELIVERY_DISCLAIMER": "You are going to pay by cash on delivery. Please prepare the exact amount and make sure to be reachable.", - "STORES": "Stores", - "FILTER_BY_TAGS": "Filter by tags", - "CONNECT_WITH_FACEBOOK": "Sign in with Facebook", - "TERMS_OF_SERVICE": "Terms of service", - "PRIVACY": "Privacy", - "RESTAURANT_PRODUCT_OPTIONS": "Product options", - "RESTAURANT_PRE_ORDER": "Pre-order", - "RESTAURANT_CLOSED_BUT_OPENS": "This restaurant is closed now but opens {{ datetime }}. You can still order for later on though!", - "RESTAURANT_CLOSED_AND_NOT_AVAILABLE": "This restaurant is closed and not available until {{ datetime }}.", - "CHECKOUT_AS_GUEST": "Checkout as guest", - "GUEST_CHECKOUT_ORDER_EMAIL_HELP": "To send you updates about your order", - "GUEST_CHECK_EMAIL_FOR_ORDER_UPDATES": "Because you chose to checkout as guest, please check your emails to follow your order", - "MY_ORDER": "My order", - "OR": "or", - "MULTIPLE_SERVERS_IN_SAME_CITY_MODAL_TEXT": "There are more than one cooperative in your city, you can navigate between them using the tabs or by swiping left or right.", - "DO_NOT_SHOW_IT_AGAIN": "Don't show it again", - "total_packages": "Total: {{count}} package", - "total_packages_plural": "Total: {{count}} packages", - "EMPTY_CARTS_TITLE": "Add items to start a cart", - "EMPTY_CARTS_SUBTITLE": "Once you add items from a restaurant or store, your cart will appear here.", - "GO_TO_SHOPPING": "Start shopping", - "SAVE_AND_CONTINUE": "Save and continue", - "ENTER_NEW_ADDRESS": "Enter a new address", - "OPTION_REQUIRED": "Mandatory", - "SOME_OPTION_IS_REQUIRED_SELECT_ONE_VALUE": "There are mandatory options, select a value from them to continue", - "WHERE_ARE_YOU": "Where are you?", - "ASK_ADDRESS_DISCLAIMER": "We use your address to help you find the best businesses nearby", - "TOP_RESULTS": "Top results", - "OTHER_RESULTS": "Other results", - "RESULT": "{{count}} result", - "RESULT_plural": "{{count}} results", - "CALL": "Call", - "EMPTY_HERE": "It's completely empty over here", - "HELP": "Help", - "SHOW_DELIVERY_ZONE": "Display the delivery zone", - "ORDER_TIMELINE_CREATED_TITLE": "Order created", - "ORDER_TIMELINE_ACCEPTED_TITLE": "Order confirmed", - "ORDER_TIMELINE_REFUSED_TITLE": "Order refused", - "ORDER_TIMELINE_REFUSED_DESCRIPTION": "Your order cannot be prepared by the restaurant", - "ORDER_TIMELINE_PICKED_TITLE": "Order picked", - "ORDER_TIMELINE_DROPPED_TITLE": "Order delivered", - "ORDER_TIMELINE_CANCELLED_TITLE": "Order cancelled", - "ORDER_TIMELINE_AFTER_ACCEPTED_TITLE": "The restaurant now needs to prepare your order", - "ORDER_TIMELINE_AFTER_ACCEPTED_DESCRIPTION": "We will keep you updated when the courier picks your order", - "ORDER_TIMELINE_AFTER_CREATED_TITLE": "Order is waiting for confirmation", - "ORDER_TIMELINE_AFTER_CREATED_DESCRIPTION": "The restaurant has to confirm your order. In case of refusal no amount will be debited from your card", - "ORDER_TIMELINE_AFTER_PICKED_TITLE": "The courier is coming!", - "ORDER_TIMELINE_AFTER_PICKED_DESCRIPTION": "Get ready!", - "ORDER_ETA": "Estimated arrival between {{start}} and {{end}}", - "ORDER_ABOUT": "Order Information", - "SHOW_ORDER_DETAILS": "Show order details", - "SHARE_INVOICE": "Share the invoice", - "GENERATE_INVOICE": "Edit your invoice", - "BILLING_ADDRESS": "Billing address", - "TIP": "Tip", - "LEGAL_TEXTS_LABEL": "I accept the Terms and Conditions and the Privacy Policy", - "LEGAL_TEXTS_ERROR_MESSAGE": "You must accept the Terms and Conditions and Privacy Policy", - "TERMS_AND_CONDITIONS_BUTTON_LABEL": "Read Terms and Conditions", - "PRIVACY_POLICY_BUTTON_LABEL": "Read Privacy Policy", - "TERMS_AND_CONDITIONS_LABEL": "I accept the Terms and Conditions", - "TERMS_AND_CONDITIONS_ERROR_MESSAGE": "You must accept the Terms and Conditions", - "PRIVACY_POLICY_LABEL": "I accept the Privacy Policy", - "PRIVACY_POLICY_ERROR_MESSAGE": "You must accept the Privacy Policy", - "AGREE": "I accept", - "DELETE_ACCOUNT": "Delete my account", - "DELETE_ACCOUNT_DISCLAIMER": "By clicking the button below, you are going to delete your account. Are you sure?", - "SEARCH_SHOPS": "Shops", - "SEARCH_PRODUCTS": "Products", - "SEARCH_PRODUCT_ITEM_SUBTITLE": "In {{shop}}", - "SEARCH_WITHOUT_RESULTS": "No results found. Please try again with another search.", - "COMPLETE_TASKS": "Complete tasks", - "PAY_WITH_SAVED_CREDIT_CARD": "Pay with a saved credit card", - "SELECT_SAVED_CARD_ERROR": "Please select or add a credit card to pay", - "INVALID_CREDIT_CARD_ERROR": "Please enter a valid credit card information", - "INVALID_CARD_HOLDER_NAME_ERROR": "Please enter a valid card holder name", - "CREDIT_CARD_EXPIRATION": "Expiration", - "ADD_NEW_CREDIT_CARD": "Add new credit card", - "SELECT_SAVED_CARD": "Select a saved credit card", - "SAVE_CARD": "Save credit card", - "SEARCH_TAB": "Search", - "DELIVERY": "Delivery", - "DELIVERIES": "Deliveries", - "APPLE_SIGN_IN_HIDE_MY_EMAIL_ERROR": "Sorry, we canʼt authenticate your account if you choose to hide your email", - "SOCIAL_SIGN_IN_UNKNOWN_EMAIL": "Sorry, we couldnʼt find an account matching the e-mail associated with your {{provider}} account.", - "CHECKOUT_LOOPEAT_CONNECT_ACCOUNT": "Connect my {{name}} account", - "CHECKOUT_LOOPEAT_OPTION_ENABLED": "Zero waste option enabled", - "CHECKOUT_LOOPEAT_INSUFFICIENT_WALLET_AMOUNT": "Insufficient wallet amount", - "CHECKOUT_LOOPEAT_VALIDATE": "Validate and go back to cart", - "CHECKOUT_LOOPEAT_RETURN_CONTAINERS": "Return containers", - "CHECKOUT_LOOPEAT_RETURN_CONTAINER": "Return container", - "CHECKOUT_LOOPEAT_WALLET": "Wallet", - "CHECKOUT_LOOPEAT_TOTAL_RETURNS": "Total returns", - "CHECKOUT_LOOPEAT_DIFF": "Difference", - "CHECKOUT_LOOPEAT_ADD_CREDITS": "Credit my wallet", - "RESTAURANT_LOOPEAT_DISCLAIMER": "Change the number of containers used to pack the order", - "ZERO_WASTE": "Zero waste", - "RESTAURANT_LOOPEAT_UPDATE_FORMATS": "Update packagings", - "SESSION_ERROR_TRY_AGAIN": "An error occurred with the session, please login and try again.", - "FAILURE_REASON": "Failure Reason", - "CHECKOUT_LOOPEAT_CONNECT_ACCOUNT_TEXT": "Connect your {{name}} account to continue, or disable the zero waste option", - "CHECKOUT_LOOPEAT_MISSING_AMOUNT": "{{amount}} missing to complete your order", - "CHECKOUT_LOOPEAT_WALLET_AMOUNT": "Wallet ({{amount}})", - "CART_ZERO_WASTE_POPUP_TEXT": "Congratulations, your cart contains a dish eligible for zero-waste delivery with our partner « {{name}} ». Have it delivered in returnable boxes and return your boxes anywhere in the « {{name}} » network or to your courier with your next order. You can disable this option at any time.", - "CART_ZERO_WASTE_POPUP_BUTTON_TEXT": "I understand", - "FEATURED": "Featured", - "EXCLUSIVE": "Exclusive", - "NEW": "New", - "RESTAURANT_MORE_INFOS": "More information", - "PRICE_EXCLUDING_TAX": "Price excluding tax", - "PRICE_TOTAL": "Total price", + "common": { + "MY_ADDRESSES": "My addresses", + "MY_ORDERS": "My orders", + "MY_DETAILS": "My details", + "COURIER": "Courier", + "TASK": "Task", + "TASKS": "Tasks", + "ITEM": "{{count}} product", + "ITEM_plural": "{{count}} products", + "TASK_LIST": "Task List", + "WS_NOT_CONNECTED_WARNING": "Failed to connect to auto refresh. Retrying...", + "DISPATCH": "Dispatch", + "RESTAURANT": "Restaurant", + "RESTAURANTS": "Restaurants", + "BACK": "Back", + "CART": "Cart", + "CART_EMPTY_WARNING": "Your cart is empty", + "DELIVERY_ADDR": "Delivery address", + "PAYMENT": "Payment", + "PAY_AMOUNT": "Pay {{amount}}", + "ORDER_TRACKING": "Order tracking", + "TASKS_UPDATED": "Your tasks have been updated", + "FAILED_TASK_LOAD": "Could not load your tasks", + "FAILED_TASK_COMPLETE": "Could not complete this task", + "TRY_LATER": "Please try again later", + "NET_FAILED": "Network request failed", + "RETRY": "Retry", + "SERVER_INCOMPATIBLE": "Incompatible server", + "SERVER_INVALID": "Invalid server", + "SERVER_UNDER_MAINTENANCE": "The server is under maintenance", + "CHOOSE_SERVER": "Please choose a city to start", + "CHOOSE_CITY": "Choose a city", + "CITY_NOT_LISTED": "Don't see your city listed?", + "CONTACT_US": "Contact us", + "ABOUT_COOPCYCLE": "Unlike big platforms, with CoopCycle the implementation of a food delivery service is at the initiative of bike messengers, who decide to form a co-op at the local scale. \n\nIf your city is not listed, it means that there is no project (yet). \n\nIf you are a group of bike messengers, contact us to find out how we can help you to set up your project as a co-op.", + "ABOUT": "About", + "ABOUT_INSTANCE": "About {{name}}", + "CUSTOM": "Custom", + "SERVER_URL": "Your server is not listed? Enter its address", + "EXAMPLE": "Example", + "SUBMIT": "Submit", + "NEXT": "Next", + "CONNECTED_TO": "Connected to", + "CHANGE_SERVER": "Choose another server", + "LOADING": "Loading", + "HELLO": "Hello", + "DETAILS": "Details", + "ADDRESSES": "Addresses", + "ORDERS": "Orders", + "SIGN_OUT": "Sign out", + "CONTINUE": "Continue", + "EDIT": "Edit", + "NAME": "Name", + "ADDRESS_DESCRIPTION": "Instructions", + "ADDRESS_DESCRIPTION_PLACEHOLDER": "2nd door on the left…", + "TELEPHONE": "Telephone", + "TOTAL_ITEMS": "Total items", + "TOTAL_DELIVERY": "Total delivery", + "TOTAL": "Total", + "MY_TASKS": "My tasks", + "MY_STATS": "My statistics", + "ENTER_PAY_DETAILS": "Please enter your payment details", + "PAY": "Pay", + "CONNECTING_SERVER": "Connecting to the server", + "PLS_WAIT": "Please wait", + "WAITING_FOR_ORDER": "Waiting for an order", + "LOADING_ORDER": "Loading order", + "CALCULATING_ROUTE": "Calculating route", + "CALCULATING_YOUR_POS": "Determining your position", + "TRACKING_YOUR_POS": "Tracking your position", + "CONN_LOST": "Connection lost! Reconnecting", + "CONN_LOST_IDLE": "Connection lost!", + "AUTHENTICATING": "Authenticating", + "REFUSE": "Refuse", + "ACCEPT": "Accept", + "ORDER_COLLECTED": "Order collected", + "ORDER_DELIVERED": "Order delivered", + "HOME": "Home", + "MY_ACCOUNT": "My account", + "FIND_RESTAURANT": "Find a restaurant", + "SEARCH_NEARBY": "Search nearby, browse menus, place your order", + "WELCOME": "Welcome", + "WELCOME_TEXT": "Thank you for downloading CoopCycle. \nTo start using the application, connect to a server.", + "NO_RESTAURANTS": "Sorry, we could not find any open restaurants", + "SEARCH_WITHOUT_DATE": "Do you want to restart the search without including a date", + "SEARCH_AGAIN": "Search again", + "ENTER_ADDRESS": "Enter your address", + "ENTER_POSTCODE": "Enter your postcode", + "TODAY": "Today", + "GO_TO_TODAY": "Go to Today", + "TOMORROW": "Tomorrow", + "CANCEL": "Cancel", + "VIEW": "Show", + "WHEN": "When", + "ADDRESS": "Address", + "POST_CODE": "Post code", + "CITY": "City", + "USERNAME": "Username", + "USERNAME_OR_EMAIL": "Username or email address", + "PASSWORD": "Password", + "EMAIL": "E-mail", + "CHECKOUT_FROM": "From {{date}}", + "ORDER": "Order", + "SCHEDULE_ORDER": "Schedule order", + "INVALID_USER_PASS": "Username and/or password incorrect", + "NO_TASKS": "No tasks planned today", + "NOTES": "Notes", + "COMPLETED": "Completed", + "FAILED": "Failed", + "COMPLETE_TASK": "Complete", + "VALIDATE": "Validate", + "MARK_FAILED": "Mark task as failed", + "REPORT_INCIDENT": "Report incident", + "SWIPE_TO_END": "Swipe right to end task, or to the left if there is a problem", + "WAITING_FOR_POS": "Waiting for location", + "PROBLEM_CONNECTING": "Problem connecting", + "OK": "OK", + "LAST_TASK_EVENT": "Last event {{fromNow}}", + "PREVIOUS_TASK": "Previous task", + "NEXT_TASK": "Next task", + "HISTORY": "History", + "SETTINGS": "Settings", + "SETTING_KEEP_AWAKE": "Disable screen lock", + "OR_REGISTER": "Don't have an account? Register", + "OR_LOGIN": "Already have an account? Login", + "FORGOT_PASSWORD": "Forgot password?", + "RESET_PASSWORD_CHECK_EMAIL": "Reset password", + "RESET_PASSWORD_CHECK_EMAIL_DISCLAIMER": "An email has been sent to {{email}}. It contains a link you must click to reset your password.", + "RESET_PASSWORD_NEW_PASSWORD": "Reset password", + "RESET_PASSWORD_LINK_EXPIRED": "Link has expired, try to reset password again", + "GIVEN_NAME": "First name", + "FAMILY_NAME": "Last name", + "PHONE_NUMBER": "Phone number", + "CONFIRM_PASSWORD": "Confirm password", + "EMAIL_ALREADY_REGISTERED": "This e-mail has already been registered", + "INVALID_GIVEN_NAME": "Please enter a valid first name", + "INVALID_FAMILY_NAME": "Please enter a valid last name", + "INVALID_EMAIL": "Please enter a valid email", + "INVALID_PHONE_NUMBER": "Please enter a valid phone number", + "INVALID_USERNAME": "Please enter a valid username", + "INVALID_USERNAME_FORMAT": "A username can only contain alphanumeric characters (letters A-Z, numbers 0-9) with the exception of underscores", + "INVALID_PASSWORD": "Please enter a valid password at least 8 characters", + "INVALID_PASSWORD_CONFIRMATION": "Password confirmation does not match", + "TASKS_FILTER": "Tasks", + "HIDE_DONE_TASKS": "Hide done tasks", + "TASKS_SHOW_POLYLINE": "Show line linking tasks", + "HIDE_FAILED_TASKS": "Hide failed tasks", + "HIDE_TASKS_TAGGED_WITH": "Hide tasks tagged with", + "TASKS_CHANGED_ALERT_SOUND": "Tasks changed alert sound", + "RESTAURANT_LIST_CLICK_BELOW": "Click on a restaurant in the list below", + "RESTAURANT_ORDER_LIST_NEW_ORDERS": "New orders ({{count}})", + "RESTAURANT_ORDER_LIST_ACCEPTED_ORDERS": "Accepted ({{count}})", + "RESTAURANT_ORDER_LIST_STARTED_ORDERS": "In preparation ({{count}})", + "RESTAURANT_ORDER_LIST_READY_ORDERS": "Ready ({{count}})", + "RESTAURANT_ORDER_LIST_CANCELLED_ORDERS": "Cancelled ({{count}})", + "RESTAURANT_ORDER_LIST_FULFILLED_ORDERS": "Delivered ({{count}})", + "RESTAURANT_ORDER_BUTTON_ACCEPT": "Accept", + "RESTAURANT_ORDER_BUTTON_REFUSE": "Refuse", + "RESTAURANT_ORDER_BUTTON_CANCEL": "Cancel", + "RESTAURANT_ORDER_BUTTON_DELAY": "Delay", + "RESTAURANT_ORDER_BUTTON_FULFILL": "Fulfill", + "RESTAURANT_ORDER_REFUSE_DISCLAIMER": "Please tell us why you want to decline this order so that we can inform the customer", + "RESTAURANT_ORDER_REFUSE_REASON_SOLD_OUT_HEADING": "I'm out of stock", + "RESTAURANT_ORDER_REFUSE_REASON_RUSH_HOUR_HEADING": "I'm in a rush", + "RESTAURANT_ORDER_REFUSE_REASON_CLOSING_HEADING": "I'm going to close", + "RESTAURANT_ORDER_REFUSE_REASON_ORDER_WILL_BE_REFUSED": "This order will be cancelled", + "RESTAURANT_ORDER_REFUSE_REASON_ORDER_CONTINUE_RECEIVING": "You will continue receiving new orders", + "RESTAURANT_ORDER_REFUSE_REASON_ORDER_STOP_RECEIVING": "You will not receive orders anymore", + "RESTAURANT_ORDER_REFUSE_REASON_NO_SHOW": "No show", + "RESTAURANT_ORDER_REFUSE_REASON_ORDER_WILL_BE_CAPTURED": "The order will be cancelled, but won't be refunded to customer", + "RESTAURANT_ORDER_PRINT": "Print", + "RESTAURANT_PRODUCTS": "Products", + "RESTAURANT_CLOSE_ALERT_TITLE": "Close until tomorrow", + "RESTAURANT_CLOSE_ALERT_MESSAGE": "This will close your restaurant for today", + "RESTAURANT_CLOSE_ALERT_CONFIRM": "Yes", + "RESTAURANT_OPENING_HOURS": "Opening hours", + "RESTAURANT_SPECIAL_OPENING_HOURS": "Exceptional closings", + "RESTAURANT_OPENING_HOURS_VALID_FROM_THROUGH": "From {{validFrom}} to {{validThrough}}", + "RESTAURANT_OPENING_HOURS_ONE_DAY": "{{day}} from {{opens}} to {{closes}}", + "RESTAURANT_OPENING_HOURS_DAY_RANGE": "From {{firstDay}} to {{lastDay}} from {{opens}} to {{closes}}", + "RESTAURANT_ORDER_ACCEPTED_CONFIRM_TITLE": "Order accepted!", + "RESTAURANT_ORDER_ACCEPTED_CONFIRM_BODY": "Order {{number}} ({{id}}) has been accepted", + "RESTAURANT_ORDER_CANCELLED_CONFIRM_TITLE": "Order cancelled!", + "RESTAURANT_ORDER_CANCELLED_CONFIRM_BODY": "Order {{number}} ({{id}}) has been cancelled", + "OPEN_IN_MAPS_TITLE": "Show task on map", + "OPEN_IN_MAPS_MESSAGE": "Choose an app", + "RESTAURANT_ORDER_TITLE": "Order {{order.number}}", + "RESTAURANT_ORDER_PREPARATION_EXPECTED_AT": "Should begin at {{date}}", + "RESTAURANT_ORDER_PICKUP_EXPECTED_AT": "Should be ready at {{date}}", + "RESTAURANT_ORDER_DELAY_MODAL_TITLE": "Delay order", + "RESTAURANT_ORDER_DELAY_DISCLAIMER": "How much delay would you like to apply?", + "RESTAURANT_ORDER_CANCEL_MODAL_TITLE": "Cancel", + "RESTAURANT_ORDER_CANCEL_DISCLAIMER": "Please tell us why you want to cancel this order", + "RESTAURANT_ORDER_CANCEL_REASON_CUSTOMER_HEADING": "Cancelled by customer", + "RESTAURANT_SETTINGS_RUSH": "Switch to rush mode", + "RESTAURANT_SETTINGS_CHANGE_RESTAURANT": "Change restaurant", + "RESTAURANT_SETTINGS_MANAGE_PRODUCTS": "Manage products", + "RESTAURANT_SETTINGS_OPENING_HOURS": "Opening hours", + "RESTAURANT_SETTINGS_PRINTER": "Printers", + "RESTAURANT_SETTINGS_HEADING": "Settings for restaurant {{- name}}", + "RESTAURANT_SETTINGS_MENUS": "Switch menus", + "RESTAURANT_SETTINGS_MENU_ACTIVATE": "Activate menu {{- name}}", + "RESTAURANT_ALERT_RUSH_MODE_ON": "Rush mode is activated", + "RESTAURANT_ALERT_CLOSED": "Restaurant is closed", + "RESTAURANT_PRINTER_CONNECTED_TITLE": "Device connected", + "RESTAURANT_PRINTER_CONNECTED_BODY": "The device {{name}} is connected", + "RESTAURANT_PRINTER_CONNECT_ERROR_TITLE": "Can't connect to device", + "RESTAURANT_PRINTER_DISCONNECTED_TITLE": "Device disconnected", + "RESTAURANT_PRINTER_DISCONNECTED_BODY": "The device {{name}} has been disconnected", + "RESTAURANT_SOUND_ALERT_TITLE": "Sound volume is low", + "RESTAURANT_SOUND_ALERT_MESSAGE": "The sound volume is quite low. You should increase it to make sure you hear notifications.", + "RESTAURANT_SOUND_ALERT_CONFIRM": "Ok, I will", + "CHECKOUT_PRODUCT_OPTIONS_TITLE": "Choose options", + "CHECKOUT_LOGIN_TITLE": "Authentication", + "CHECKOUT_LOGIN_DISCLAIMER": "Please authenticate to continue", + "CHECKOUT_SHIPPING_DATE": "Choose date", + "CHECKOUT_PLEASE_ENTER_ADDRESS": "Please enter your address", + "CHECKOUT_ADDRESS_NOT_VALID": "Sorry, we don't deliver at this address", + "CHECKOUT_ORDER_ADDRESS_DESCRIPTION": "Access information", + "CHECKOUT_ORDER_ADDRESS_DESCRIPTION_HELP": "Specify any useful information", + "CHECKOUT_ORDER_PHONE_NUMBER_HELP": "To contact you if there's a problem", + "CHECKOUT_ORDER_NOTES": "A message for the restaurant owner ?", + "CHECKOUT_ORDER_NOTES_HELP": "This message will be transmitted to the cook (allergies, special requests)", + "CHECKOUT_MORE_INFOS": "Additional infos", + "CHECKOUT_MORE_INFOS_DISCLAIMER": "Just one last step", + "CHECKOUT_PICK_DATE": "This store is closed for the moment, but you can schedule an order for later.", + "CHECKOUT_SCHEDULE_ORDER": "Schedule the order", + "SCHEDULE": "Schedule", + "IGNORE": "Skip", + "CHECKOUT_PRODUCT_OPTIONS_CHOICES_BETWEEN": "Between {{min}} and {{max}} choices", + "CHECKOUT_UNITS": "Units", + "ADD_TO_CART": "Add to cart", + "NOTIFICATION_TASKS_CHANGED_TITLE": "Tasks changed", + "NOTIFICATION_TASKS_CHANGED_DESC": "Your tasks from {{date}} have been updated, {{added}}", + "NOTIFICATION_ORDER_CREATED_TITLE": "New order", + "NOTIFICATION_ORDER_CREATED_DESC": "A new order for {{date}} has been created", + "NOTIFICATION_CLOSES_SOON": "Hop hop hop ! Hurry up, it remains {{diff}} until closing !", + "UNASSIGNED_TASKS": "Unassigned", + "DISPATCH_UNASSIGNED_TASKS": "Unassigned", + "DISPATCH_TASK_LISTS": "Assigned", + "DISPATCH_TASK_LIST": "Assigned to {{username}}", + "DISPATCH_ADD_TASK_LIST": "Add user", + "DISPATCH_ADD_TASK": "Add task", + "DISPATCH_CREATE_TASK": "Create task", + "DISPATCH_NO_TASKS": "No tasks yet", + "DISPATCH_TASK": "Task #{{id}}", + "DISPATCH_ASSIGN_TASK": "Assign task", + "DISPATCH_PICK_USER": "Pick a user", + "DISPATCH_ASSIGN_TO_ME": "Assign to me", + "TASK_FORM_ADDRESS_LABEL": "Address", + "TASK_FORM_DONE_AFTER_LABEL": "To do starting at", + "TASK_FORM_DONE_BEFORE_LABEL": "To do before", + "TASK_FORM_COMMENTS_LABEL": "Comments", + "DISPATCH_DATE": "Choose a date", + "BACKGROUND_GEOLOCATION_NOTIFICATION_TITLE": "Background tracking", + "BACKGROUND_GEOLOCATION_NOTIFICATION_TEXT": "Enabled", + "NEW_NOTIFICATION": "New notification", + "CLOSE": "Close", + "OPTIONAL": "Optional", + "ORDER_NEW": "Waiting", + "ORDER_NEW_HELP": "The restaurant has to confirm your order", + "ORDER_ACCEPTED": "Accepted", + "ORDER_FULFILLED": "Fulfilled", + "ORDER_CANCELLED": "Cancelled", + "ORDER_NUMBER": "Order {{number}}", + "ADDRESS_NOT_PRECISE_ENOUGH": "This address is not precise enough", + "REGISTER_CHECK_EMAIL": "Activation", + "REGISTER_CHECK_EMAIL_DISCLAIMER": "An email has been sent to {{email}}. It contains an activation link you must click to activate your account.", + "REGISTER_CHECK_EMAIL_ALREADY_ACTIVATED": "You have already activated your account?", + "REGISTER_CHECK_EMAIL_LOGIN": "Login", + "REGISTER_CONFIRM": "Activation", + "TASK_WITH_ID": "Task #{{id}}", + "SIGNATURE": "Signature", + "SIGNATURE_DISCLAIMER": "Sign below", + "SIGNATURE_CLEAR": "Clear", + "SIGNATURE_ADD": "Add signature", + "PHOTO": "Picture", + "PHOTO_ADD": "Add picture", + "PHOTO_DISCLAIMER": "Take a picture", + "LOAD_MORE": "Load more", + "TASK_IMAGE_UPLOAD_CONFIRM_SHORT": "Image uploaded!", + "TASK_IMAGE_UPLOAD_CONFIRM_LONG": "Image was successfully uploaded", + "AN_ERROR_OCCURRED": "An error occurred", + "DELIVERY_ASAP": "As soon as possible", + "CART_DELIVERY_TIME": "Delivery {{- fromNow}}", + "CART_DELIVERY_TIME_DIFF": "Delivery in {{diff}} minutes", + "CART_COLLECTION_TIME": "Collection {{- fromNow}}", + "CART_COLLECTION_TIME_DIFF": "Collection in {{diff}} minutes", + "TIME_DIFF_SHORT": "{{min}} - {{max}} minutes", + "TASK_ADD_PROOF_OF_DELIVERY": "Add a proof of delivery", + "STORE_NEW_DELIVERY": "New delivery", + "STORE_NEW_DELIVERY_ADDRESS": "Address", + "STORE_NEW_DELIVERY_SEARCH_CLIENT": "Search for a client", + "STORE_NEW_DELIVERY_ENTER_SEARCH_CLIENT": "Enter a client name", + "STORE_NEW_DELIVERY_ADDRESS_HELP": "Enter dropoff address to verify feasibility", + "STORE_NEW_DELIVERY_PHONE_NUMBER": "Phone number", + "STORE_NEW_DELIVERY_ENTER_PHONE_NUMBER": "Enter a valid phone number", + "STORE_NEW_DELIVERY_BUSINESS_NAME": "Business name", + "STORE_NEW_DELIVERY_ENTER_BUSINESS_NAME": "Enter a business name", + "STORE_NEW_DELIVERY_CONTACT_NAME": "Contact name", + "STORE_NEW_DELIVERY_ENTER_CONTACT_NAME": "Enter a contact name", + "STORE_NEW_DELIVERY_ADDRESS_DESCRIPTION": "Access information", + "STORE_NEW_DELIVERY_ENTER_ADDRESS_DESCRIPTION": "Induce any useful information (intercom...)", + "STORE_NEW_DELIVERY_COMMENTS": "Comments", + "STORE_NEW_DELIVERY_ENTER_COMMENTS": "Specify any useful details to complete the delviery (fragile, ...)", + "STORE_NEW_DELIVERY_WEIGHT": "Custom weight", + "STORE_NEW_DELIVERY_ENTER_WEIGHT": "Enter a custom weight", + "STORE_NEW_DELIVERY_PACKAGES": "Packages", + "STORE_NEW_DELIVERY_NO_PACKAGES": "No packages", + "STORE_NEW_DELIVERY_DROPOFF_BEFORE": "Dropoff before", + "STORE_NEW_DELIVERY_TIME_SLOT": "Time slot", + "STORE_NEW_DELIVERY_SELECT_TIME_SLOT": "Select a time slot", + "STORE_NEW_DELIVERY_ERROR": { + "EMPTY_TIME_SLOT": "Please select a time slot", + "EMPTY_CONTACT_NAME": "Please provide a name for the recipient", + "EMPTY_PHONE_NUMBER": "Please provide a phone number", + "EMPTY_WEIGHT": "Please provide a weight", + "EMPTY_PACKAGES": "Please select packages" + }, + "STORE_DELIVERY": "Delivery #{{id}}", + "DELIVERY_STATE": { + "undefined": "Unknown", + "unknown": "Unknown", + "new": "New", + "picked": "Picked", + "fulfilled": "Fulfilled" + }, + "DELIVERY_DETAILS_TIME_SLOT": "Time slot", + "DELIVERY_DETAILS_RECIPIENT": "Recipient", + "TODAY_BETWEEN_START_AND_END": "Today between {{start}} and {{end}}", + "TOMORROW_BETWEEN_START_AND_END": "Tomorrow between {{start}} and {{end}}", + "OTHER_DAY_BETWEEN_START_AND_END": "{{date}} between {{start}} and {{end}}", + "SIGNATURE_SCREEN_FIRST": "Signature screen as default", + "FINISHED": "Finished", + "SEARCH": "Find restaurants...", + "SEARCH_INPUT_PLACEHOLDER": "A shop, a dish...", + "GO_BACK_TO_RESTAURANTS": "Start again", + "SESSION_EXPIRED": "Your session has expired", + "TOUCH_TO_RELOAD": "Touch to reload", + "OFFLINE": "You are offline", + "SCAN_FOR_PRINTERS": "Tap to scan", + "AUTO_ACCEPT_ORDERS_PRINT_NUMBER_OF_COPIES": "Number of copies", + "SEARCH_WITH_ADDRESS": "Search « {{address}} »", + "RESTAURANT_ORDER_CONNECT_PRINTER": "Connect a printer", + "RESTAURANT_ORDER_PRINTING": "Printing order", + "RESTAURANT_ORDER_FAILED_TO_PRINT": "Failed to print", + "RESTAURANT_SEARCH_ORDERS": "Find order...", + "RESTAURANT_SEARCH_ORDERS_INPUT_PLACEHOLDER": "Order number...", + "SWIPE_TO_ACCEPT_REFUSE": "Slide to the right to accept, to the left to refuse", + "ADD_COUPON": "Add a voucher code", + "VOUCHER_CODE": "Voucher code", + "RECEIPT_HEADING_ORDER_NUMBER": "ORDER {{number}} (#{{id}})", + "RECEIPT_CUSTOMER_NAME": "CUSTOMER: {{customer}}", + "RECEIPT_HEADING_PICKUP_EXPECTED_AT": "AT {{time}}", + "RECEIPT_HEADING_PICKUP_EXPECTED_ON": "PICKUP EXPECTED ON {{time}}", + "RECEIPT_HEADING_PICKUP_EXPECTED_TODAY": "PICKUP TODAY", + "RESTRICTED_DIET": { + "DiabeticDiet": "Diabetic", + "GlutenFreeDiet": "Gluten free", + "HalalDiet": "Halal", + "HinduDiet": "Hindu", + "KosherDiet": "Kosher", + "LowCalorieDiet": "Low calorie", + "LowFatDiet": "Low fat", + "LowLactoseDiet": "Low lactose", + "LowSaltDiet": "Low salt", + "VeganDiet": "Vegan", + "VegetarianDiet": "Vegetarian" + }, + "ALLERGEN": { + "CEREALS_CONTAINING_GLUTEN": "Cereals containing gluten", + "CRUSTACEANS": "Crustaceans", + "EGGS": "Eggs", + "FISH": "Fish", + "PEANUTS": "Peanuts", + "SOYBEANS": "Soybeans", + "MILK": "Milk", + "NUTS": "Nuts", + "CELERY": "Celery", + "MUSTARD": "Mustard", + "SESAME_SEEDS": "Sesame seeds", + "SULPHUR_DIOXIDE_SULPHITES": "Sulphur dioxide", + "LUPIN": "Lupin", + "MOLLUSCS": "Molluscs" + }, + "TASK_COMPLETE_ALERT_TITLE": "Start or complete?", + "TASK_COMPLETE_ALERT_MESSAGE": "Do you want to start, or complete this task?", + "TASK_COMPLETE_ALERT_NEGATIVE": "▶ Start", + "TASK_COMPLETE_ALERT_POSITIVE": "✓ Complete", + "FULFILLMENT_METHOD": { + "collection": "Collection", + "delivery": "Delivery" + }, + "CART_COLLECTION_DISCLAIMER": "You have opted for click&collect. \nYou are responsible for collecting your order on the day and time selected. \nIf you have a problem, contact the establishment at {{telephone}}. \nPlease note - If you do not collect, you will be charged the full amount for the order.", + "LEARN_MORE": "Learn more", + "SELECTED": "Selected", + "AVAILABLE": "Available", + "COMING_SOON": "Coming soon", + "CARDHOLDER_NAME": "Cardholder name", + "RESTAURANT_ORDER_LIST_PICKED_ORDERS": "Picked orders ({{count}})", + "NOTIFICATION_TASKS_ADDED": "{{ count }} added", + "NOTIFICATION_TASKS_REMOVED": "{{ count }} removed", + "NOT_AVAILABLE_ATM": "Not available at the moment", + "BACKGROUND_PERMISSION_DISCLOSURE": { + "title": "Use your location", + "message": "To allow dispatchers to assign jobs accurately, allow CoopCycle to access this device's location even when the app is closed or not in use.\n\nThis data will be uploaded to the CoopCycle instance you are connected to." + }, + "BACKGROUND_PERMISSION_RATIONALE": { + "title": "Allow {applicationName} to access this device's location even when the app is closed or not in use", + "message": "When authenticated as a messenger, this app collects location data to allow dispatchers to assign jobs accurately", + "positiveAction": "Change to « {backgroundPermissionOptionLabel} »" + }, + "RESTAURANT_SETTINGS_MANAGE_PRODUCT_OPTIONS": "Manage product options", + "SELECT_PAYMENT_METHOD": "Select a payment method", + "PAYMENT_METHOD": { + "card": "Credit card", + "cash_on_delivery": "Cash on delivery", + "edenred": "Edenred", + "edenred+card": "Edenred" + }, + "CASH_ON_DELIVERY_DISCLAIMER": "You are going to pay by cash on delivery. Please prepare the exact amount and make sure to be reachable.", + "STORES": "Stores", + "FILTER_BY_TAGS": "Filter by tags", + "CONNECT_WITH_FACEBOOK": "Sign in with Facebook", + "TERMS_OF_SERVICE": "Terms of service", + "PRIVACY": "Privacy", + "RESTAURANT_PRODUCT_OPTIONS": "Product options", + "RESTAURANT_PRE_ORDER": "Pre-order", + "RESTAURANT_CLOSED_BUT_OPENS": "This restaurant is closed now but opens {{ datetime }}. You can still order for later on though!", + "RESTAURANT_CLOSED_PRE_ORDER": "This restaurant is closed now. You can still order for {{ datetime }} though!", + "RESTAURANT_CLOSED_AND_NOT_AVAILABLE": "This restaurant is closed and not available until {{ datetime }}.", + "CHECKOUT_AS_GUEST": "Checkout as guest", + "GUEST_CHECKOUT_ORDER_EMAIL_HELP": "To send you updates about your order", + "GUEST_CHECK_EMAIL_FOR_ORDER_UPDATES": "Because you chose to checkout as guest, please check your emails to follow your order", + "MY_ORDER": "My order", + "OR": "or", + "MULTIPLE_SERVERS_IN_SAME_CITY_MODAL_TEXT": "There are more than one cooperative in your city, you can navigate between them using the tabs or by swiping left or right.", + "DO_NOT_SHOW_IT_AGAIN": "Don't show it again", + "total_packages": "Total: {{count}} package", + "total_packages_plural": "Total: {{count}} packages", + "EMPTY_CARTS_TITLE": "Add items to start a cart", + "EMPTY_CARTS_SUBTITLE": "Once you add items from a restaurant or store, your cart will appear here.", + "GO_TO_SHOPPING": "Start shopping", + "SAVE_AND_CONTINUE": "Save and continue", + "ENTER_NEW_ADDRESS": "Enter a new address", + "OPTION_REQUIRED": "Mandatory", + "SOME_OPTION_IS_REQUIRED_SELECT_ONE_VALUE": "There are mandatory options, select a value from them to continue", + "WHERE_ARE_YOU": "Where are you?", + "ASK_ADDRESS_DISCLAIMER": "We use your address to help you find the best businesses nearby", + "TOP_RESULTS": "Top results", + "OTHER_RESULTS": "Other results", + "RESULT": "{{count}} result", + "RESULT_plural": "{{count}} results", + "CALL": "Call", + "EMPTY_HERE": "It's completely empty over here", + "HELP": "Help", + "SHOW_DELIVERY_ZONE": "Display the delivery zone", + "ORDER_TIMELINE_CREATED_TITLE": "Order created", + "ORDER_TIMELINE_ACCEPTED_TITLE": "Order confirmed", + "ORDER_TIMELINE_REFUSED_TITLE": "Order refused", + "ORDER_TIMELINE_REFUSED_DESCRIPTION": "Your order cannot be prepared by the restaurant", + "ORDER_TIMELINE_PICKED_TITLE": "Order picked", + "ORDER_TIMELINE_DROPPED_TITLE": "Order delivered", + "ORDER_TIMELINE_CANCELLED_TITLE": "Order cancelled", + "ORDER_TIMELINE_AFTER_ACCEPTED_TITLE": "The restaurant now needs to prepare your order", + "ORDER_TIMELINE_AFTER_ACCEPTED_DESCRIPTION": "We will keep you updated when the courier picks your order", + "ORDER_TIMELINE_AFTER_CREATED_TITLE": "Order is waiting for confirmation", + "ORDER_TIMELINE_AFTER_CREATED_DESCRIPTION": "The restaurant has to confirm your order. In case of refusal no amount will be debited from your card", + "ORDER_TIMELINE_AFTER_PICKED_TITLE": "The courier is coming!", + "ORDER_TIMELINE_AFTER_PICKED_DESCRIPTION": "Get ready!", + "ORDER_ETA": "Estimated arrival between {{start}} and {{end}}", + "ORDER_ABOUT": "Order Information", + "SHOW_ORDER_DETAILS": "Show order details", + "SHARE_INVOICE": "Share the invoice", + "GENERATE_INVOICE": "Edit your invoice", + "BILLING_ADDRESS": "Billing address", + "TIP": "Tip", + "LEGAL_TEXTS_LABEL": "I accept the Terms and Conditions and the Privacy Policy", + "LEGAL_TEXTS_ERROR_MESSAGE": "You must accept the Terms and Conditions and Privacy Policy", + "TERMS_AND_CONDITIONS_BUTTON_LABEL": "Read Terms and Conditions", + "PRIVACY_POLICY_BUTTON_LABEL": "Read Privacy Policy", + "TERMS_AND_CONDITIONS_LABEL": "I accept the Terms and Conditions", + "TERMS_AND_CONDITIONS_ERROR_MESSAGE": "You must accept the Terms and Conditions", + "PRIVACY_POLICY_LABEL": "I accept the Privacy Policy", + "PRIVACY_POLICY_ERROR_MESSAGE": "You must accept the Privacy Policy", + "AGREE": "I accept", + "DELETE_ACCOUNT": "Delete my account", + "DELETE_ACCOUNT_DISCLAIMER": "By clicking the button below, you are going to delete your account. Are you sure?", + "SEARCH_SHOPS": "Shops", + "SEARCH_PRODUCTS": "Products", + "SEARCH_PRODUCT_ITEM_SUBTITLE": "In {{shop}}", + "SEARCH_WITHOUT_RESULTS": "No results found. Please try again with another search.", + "COMPLETE_TASKS": "Complete tasks", + "PAY_WITH_SAVED_CREDIT_CARD": "Pay with a saved credit card", + "SELECT_SAVED_CARD_ERROR": "Please select or add a credit card to pay", + "INVALID_CREDIT_CARD_ERROR": "Please enter a valid credit card information", + "INVALID_CARD_HOLDER_NAME_ERROR": "Please enter a valid card holder name", + "CREDIT_CARD_EXPIRATION": "Expiration", + "ADD_NEW_CREDIT_CARD": "Add new credit card", + "SELECT_SAVED_CARD": "Select a saved credit card", + "SAVE_CARD": "Save credit card", + "SEARCH_TAB": "Search", + "DELIVERY": "Delivery", + "DELIVERIES": "Deliveries", + "APPLE_SIGN_IN_HIDE_MY_EMAIL_ERROR": "Sorry, we canʼt authenticate your account if you choose to hide your email", + "SOCIAL_SIGN_IN_UNKNOWN_EMAIL": "Sorry, we couldnʼt find an account matching the e-mail associated with your {{provider}} account.", + "CHECKOUT_LOOPEAT_CONNECT_ACCOUNT": "Connect my {{name}} account", + "CHECKOUT_LOOPEAT_OPTION_ENABLED": "Zero waste option enabled", + "CHECKOUT_LOOPEAT_INSUFFICIENT_WALLET_AMOUNT": "Insufficient wallet amount", + "CHECKOUT_LOOPEAT_VALIDATE": "Validate and go back to cart", + "CHECKOUT_LOOPEAT_RETURN_CONTAINERS": "Return containers", + "CHECKOUT_LOOPEAT_RETURN_CONTAINER": "Return container", + "CHECKOUT_LOOPEAT_WALLET": "Wallet", + "CHECKOUT_LOOPEAT_TOTAL_RETURNS": "Total returns", + "CHECKOUT_LOOPEAT_DIFF": "Difference", + "CHECKOUT_LOOPEAT_ADD_CREDITS": "Credit my wallet", + "RESTAURANT_LOOPEAT_DISCLAIMER": "Change the number of containers used to pack the order", + "ZERO_WASTE": "Zero waste", + "RESTAURANT_LOOPEAT_UPDATE_FORMATS": "Update packagings", + "SESSION_ERROR_TRY_AGAIN": "An error occurred with the session, please login and try again.", + "FAILURE_REASON": "Failure Reason", + "CHECKOUT_LOOPEAT_CONNECT_ACCOUNT_TEXT": "Connect your {{name}} account to continue, or disable the zero waste option", + "CHECKOUT_LOOPEAT_MISSING_AMOUNT": "{{amount}} missing to complete your order", + "CHECKOUT_LOOPEAT_WALLET_AMOUNT": "Wallet ({{amount}})", + "CART_ZERO_WASTE_POPUP_TEXT": "Congratulations, your cart contains a dish eligible for zero-waste delivery with our partner « {{name}} ». Have it delivered in returnable boxes and return your boxes anywhere in the « {{name}} » network or to your courier with your next order. You can disable this option at any time.", + "CART_ZERO_WASTE_POPUP_BUTTON_TEXT": "I understand", + "FEATURED": "Featured", + "EXCLUSIVE": "Exclusive", + "NEW": "New", + "RESTAURANT_MORE_INFOS": "More information", + "PRICE_EXCLUDING_TAX": "Price excluding tax", + "PRICE_TOTAL": "Total price", "PRICE_CALCULATION_FAILED": "Price calculation failed", "PRICE_CALCULATION_FAILED_DISCLAIMER": "The price could not be calculated; please contact us to resolve the issue.", - "HIDE_INCIDENTS_TASKS": "Hide incidents", - "CART_TIME_RANGE_CHANGED_MODAL_TITLE": "Selected time range is no longer available for today anymore", - "CART_TIME_RANGE_CHANGED_MODAL_MESSAGE": "The opening hours do not allow the order to be placed today", - "CART_TIME_RANGE_CHANGED_MODAL_CHOOSE_TIME_RANGE_TEXT": "Please choose another time range", - "CART_TIME_RANGE_CHANGED_MODAL_SELECT_TIME_RANGE_ACTION": "Change delivery time", - "CART_TIME_RANGE_CHANGED_MODAL_CHOOSE_RESTAURANT_ACTION": "Choose another restaurant", - "ORDER__SHIPPED_AT__NOT_AVAILABLE": "The delivery date is no longer available", - "ORDER__SHIPPED_AT__EXPIRED": "The delivery date is expired", - "ORDER__SHIPPING_TIME_RANGE__NOT_AVAILABLE": "Not available at the moment", - "LOOPEAT_HAS_RETURNS": "This task has zero waste returns", - "EDENRED_ELIGIBLE_AMOUNT": "Ticket Restaurant® eligible amount", - "EDENRED_COMPLEMENT": "Complement" - } + "HIDE_INCIDENTS_TASKS": "Hide incidents", + "CART_TIME_RANGE_CHANGED_MODAL_TITLE": "Selected time range is no longer available for today anymore", + "CART_TIME_RANGE_CHANGED_MODAL_MESSAGE": "The opening hours do not allow the order to be placed today", + "CART_TIME_RANGE_CHANGED_MODAL_CHOOSE_TIME_RANGE_TEXT": "Please choose another time range", + "CART_TIME_RANGE_CHANGED_MODAL_SELECT_TIME_RANGE_ACTION": "Change delivery time", + "CART_TIME_RANGE_CHANGED_MODAL_CHOOSE_RESTAURANT_ACTION": "Choose another restaurant", + "ORDER__SHIPPED_AT__NOT_AVAILABLE": "The delivery date is no longer available", + "ORDER__SHIPPED_AT__EXPIRED": "The delivery date is expired", + "ORDER__SHIPPING_TIME_RANGE__NOT_AVAILABLE": "Not available at the moment", + "LOOPEAT_HAS_RETURNS": "This task has zero waste returns", + "EDENRED_ELIGIBLE_AMOUNT": "Ticket Restaurant® eligible amount", + "EDENRED_COMPLEMENT": "Complement" + } } diff --git a/src/i18n/locales/es.json b/src/i18n/locales/es.json index 882b887f9..fca0ac144 100644 --- a/src/i18n/locales/es.json +++ b/src/i18n/locales/es.json @@ -65,6 +65,7 @@ "CALCULATING_YOUR_POS": "Cálculando tu posición", "TRACKING_YOUR_POS": "Seguimiento de su posición", "CONN_LOST": "Conexión perdida, reconexión", + "CONN_LOST_IDLE": "Conexión perdida", "AUTHENTICATING": "Autenticando", "REFUSE": "Rechazar", "ACCEPT": "Acceptar", @@ -407,6 +408,7 @@ "PRIVACY": "Privacidad", "RESTAURANT_PRE_ORDER": "Reservar", "RESTAURANT_CLOSED_BUT_OPENS": "Este comercio se encuentra cerrado pero vuelve a abrir {{ datetime }}. Puedes programar tu pedido!", + "RESTAURANT_CLOSED_PRE_ORDER": "Este restaurante está cerrado ahora. ¡Aún puedes pedir para {{ datetime }}!", "RESTAURANT_CLOSED_AND_NOT_AVAILABLE": "Este comercio se encuentra cerrado y no disponible hasta el {{ datetime }}.", "MY_ORDER": "Mi pedido", "CHECKOUT_AS_GUEST": "Comprar como inivitado", diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 4287e479d..1fc604851 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -1,519 +1,521 @@ { - "common": { - "MY_ADDRESSES": "Mes adresses", - "MY_ORDERS": "Mes commandes", - "MY_DETAILS": "Mes informations personnelles", - "COURIER": "Coursier", - "TASK": "Tâche", - "TASKS": "Tâches", - "ITEM": "{{count}} article", - "ITEM_plural": "{{count}} articles", - "TASK_LIST": "Liste des tâches", - "WS_NOT_CONNECTED_WARNING": "Refresh automatique désactivé. Reconnexion..", - "DISPATCH": "Dispatch", - "RESTAURANT": "Restaurant", - "RESTAURANTS": "Restaurants", - "BACK": "Retour", - "CART": "Panier", - "CART_EMPTY_WARNING": "Votre panier est vide", - "DELIVERY_ADDR": "Adresse de livraison", - "PAYMENT": "Paiement", - "PAY_AMOUNT": "Payer {{amount}}", - "ORDER_TRACKING": "Suivi de commande", - "TASKS_UPDATED": "Vos tâches ont été mises à jour", - "FAILED_TASK_LOAD": "Impossible de charger les tâches", - "FAILED_TASK_COMPLETE": "Impossible de terminer cette tâche", - "TRY_LATER": "Veuillez réessayer plus tard", - "NET_FAILED": "Impossible de se connecter", - "RETRY": "Réessayer", - "SERVER_INCOMPATIBLE": "Ce serveur n'est pas compatible", - "SERVER_INVALID": "Ce serveur n'est pas valide", - "SERVER_UNDER_MAINTENANCE": "Le serveur est en maintenance", - "CHOOSE_SERVER": "Veuillez choisir une ville pour commencer", - "CHOOSE_CITY": "Choisir une ville", - "CITY_NOT_LISTED": "Votre ville n'est pas listée ?", - "CONTACT_US": "Contactez-nous", - "CUSTOM": "Personnalisé", - "SERVER_URL": "Votre serveur n'est pas listé ? Entrez son adresse", - "EXAMPLE": "Exemple", - "SUBMIT": "Valider", - "NEXT": "Suivant", - "CONNECTED_TO": "Connecté à", - "CHANGE_SERVER": "Choisir un autre serveur", - "LOADING": "Chargement", - "HELLO": "Bonjour", - "DETAILS": "Informations personnelles", - "ADDRESSES": "Adresses", - "ORDERS": "Commandes", - "SIGN_OUT": "Déconnexion", - "CONTINUE": "Continuer", - "EDIT": "Modifier", - "NAME": "Nom", - "ADDRESS_DESCRIPTION": "Instructions", - "ADDRESS_DESCRIPTION_PLACEHOLDER": "2ème porte à gauche…", - "TELEPHONE": "Téléphone", - "TOTAL_ITEMS": "Total articles", - "TOTAL_DELIVERY": "Total livraison", - "TOTAL": "Total", - "MY_TASKS": "Mes tâches", - "MY_STATS": "Mes statistiques", - "ENTER_PAY_DETAILS": "Veuillez entrer vos coordonnées bancaires", - "PAY": "Payer", - "CONNECTING_SERVER": "Connexion au serveur", - "PLS_WAIT": "Veuillez patienter", - "WAITING_FOR_ORDER": "En attente d'une commande", - "LOADING_ORDER": "Chargement de la commande", - "CALCULATING_ROUTE": "Calcul de l'itinéraire", - "CALCULATING_YOUR_POS": "Calcul de votre position", - "TRACKING_YOUR_POS": "Suivi de votre position", - "CONN_LOST": "Connexion perdue, reconnexion", - "AUTHENTICATING": "Authentification…", - "REFUSE": "Refuser", - "ACCEPT": "Accepter", - "ORDER_COLLECTED": "Commande récupérée", - "ORDER_DELIVERED": "Commande livrée", - "HOME": "Accueil", - "MY_ACCOUNT": "Mon compte", - "FIND_RESTAURANT": "Trouvez un restaurant", - "SEARCH_NEARBY": "Cherchez à proximité, découvrez les menus, passez votre commande", - "WELCOME": "Bienvenue", - "WELCOME_TEXT": "Merci d'avoir téléchargé CoopCycle. \nPour commencer à utiliser l'application, connectez-vous à un serveur.", - "ABOUT_COOPCYCLE": "À l'inverse des grandes plateformes, avec CoopCycle la mise en place d'un service de livraison de repas est à l'initiative des livreuses et des livreurs, qui décident de se constituer en coopérative à l'échelle locale. \n\nSi votre ville n'est pas listée, c'est qu'il n'y a pas (encore) de projet. \n\nSi vous êtes un groupe de livreuses et de livreurs, contactez-nous pour savoir comment nous pouvons vous aider à monter votre projet en coopérative.", - "ABOUT": "À propos", - "ABOUT_INSTANCE": "À propos de {{name}}", - "NO_RESTAURANTS": "Désolé, nous ne sommes pas disponibles à cet emplacement pour le moment", - "SEARCH_WITHOUT_DATE": "Voulez-vous relancer la recherche sans inclure de date", - "SEARCH_AGAIN": "Relancer la recherche", - "ENTER_ADDRESS": "Entrez votre adresse", - "ENTER_POSTCODE": "Entrez votre code postal", - "TODAY": "Aujourd'hui", - "TOMORROW": "Demain", - "CANCEL": "Annuler", - "VIEW": "Afficher", - "WHEN": "Quand", - "ADDRESS": "Adresse", - "POST_CODE": "Code postal", - "CITY": "Ville", - "USERNAME": "Nom d'utilisateur", - "USERNAME_OR_EMAIL": "Nom d'utilisateur ou email", - "PASSWORD": "Mot de passe", - "EMAIL": "E-mail", - "CHECKOUT_FROM": "À partir de {{date}}", - "ORDER": "Commander", - "SCHEDULE_ORDER": "Pré-commander", - "INVALID_USER_PASS": "Utilisateur et/ou mot de passe inexistant", - "NO_TASKS": "Pas de tâches prévues aujourd'hui", - "NOTES": "Notes", - "COMPLETED": "Terminée", - "FAILED": "Échec", - "VALIDATE": "Valider", - "MARK_FAILED": "Signaler un problème", - "REPORT_INCIDENT": "Signaler un incident", - "COMPLETE_TASK": "Terminer", - "SWIPE_TO_END": "Glissez vers la droite pour terminer, ou vers la gauche en cas de problème", - "WAITING_FOR_POS": "En attente de la position", - "PROBLEM_CONNECTING": "Connexion impossible", - "OK": "OK", - "LAST_TASK_EVENT": "Dernier évènement {{fromNow}}", - "PREVIOUS_TASK": "Tâche précédente", - "NEXT_TASK": "Tâche suivante", - "HISTORY": "Historique", - "SETTINGS": "Réglages", - "SETTING_KEEP_AWAKE": "Désactiver la mise en veille", - "OR_REGISTER": "Pas encore inscrit ? Créez votre compte", - "OR_LOGIN": "Vous avez déjà un compte ? Identifiez-vous", - "FORGOT_PASSWORD": "Mot de passe oublié ?", - "RESET_PASSWORD_CHECK_EMAIL": "Reset password", - "RESET_PASSWORD_CHECK_EMAIL_DISCLAIMER": "Un e-mail a été envoyé à l'adresse {{email}}. Il contient un lien sur lequel il vous faudra cliquer pour réinitialiser votre mot de passe.", - "RESET_PASSWORD_NEW_PASSWORD": "Réinitialiser le mot de passe", - "RESET_PASSWORD_LINK_EXPIRED": "Le lien a expiré, essayez de réinitialiser le mot de passe à nouveau", - "GIVEN_NAME": "Prénom", - "FAMILY_NAME": "Nom de Famille", - "PHONE_NUMBER": "Téléphone", - "CONFIRM_PASSWORD": "Confirmez le mot de passe", - "EMAIL_ALREADY_REGISTERED": "Cet e-mail a déjà été enregistré", - "INVALID_GIVEN_NAME": "Veuillez entrer un prénom valide", - "INVALID_FAMILY_NAME": "Veuillez entrer un nom de famille valide", - "INVALID_EMAIL": "Veuillez entrer un email valide", - "INVALID_PHONE_NUMBER": "Veuillez entrer une numéro de téléphone valide", - "INVALID_USERNAME": "Veuillez entrer un nom d'utilisateur valide", - "INVALID_USERNAME_FORMAT": "Un nom d’utilisateur peut seulement contenir des caractères alphanumériques (lettres A-Z, chiffres 0-9) à l’exception des underscores", - "INVALID_PASSWORD": "Veuillez entrer un mot de passe valide d'au moins 8 caractères", - "INVALID_PASSWORD_CONFIRMATION": "La confirmation du mot de passe ne correspond pas", - "TASKS_FILTER": "Filtrer les tâches", - "HIDE_DONE_TASKS": "Masquer les tâches effectuées", - "TASKS_SHOW_POLYLINE": "Montrer la ligne entre les tâches", - "HIDE_FAILED_TASKS": "Masquer les tâches ayant échoué", - "HIDE_TASKS_TAGGED_WITH": "Masquer les tâches marquées avec", - "RESTAURANT_LIST_CLICK_BELOW": "Cliquez sur un restaurant dans la liste ci-dessous", - "RESTAURANT_ORDER_LIST_NEW_ORDERS": "Nouvelles commandes ({{count}})", - "RESTAURANT_ORDER_LIST_ACCEPTED_ORDERS": "Commandes acceptées ({{count}})", - "RESTAURANT_ORDER_LIST_CANCELLED_ORDERS": "Commandes annulées ({{count}})", - "RESTAURANT_ORDER_LIST_FULFILLED_ORDERS": "Commandes terminées ({{count}})", - "RESTAURANT_ORDER_BUTTON_ACCEPT": "Accepter", - "RESTAURANT_ORDER_BUTTON_REFUSE": "Refuser", - "RESTAURANT_ORDER_BUTTON_CANCEL": "Annuler", - "RESTAURANT_ORDER_BUTTON_DELAY": "Retarder", - "RESTAURANT_ORDER_BUTTON_FULFILL": "Terminer", - "RESTAURANT_ORDER_REFUSE_DISCLAIMER": "Veuillez nous indiquer pourquoi vous souhaitez refuser cette commande pour que nous puissions informer le client.", - "RESTAURANT_ORDER_REFUSE_REASON_SOLD_OUT_HEADING": "Je suis en rupture de stock", - "RESTAURANT_ORDER_REFUSE_REASON_RUSH_HOUR_HEADING": "Je suis en plein rush", - "RESTAURANT_ORDER_REFUSE_REASON_CLOSING_HEADING": "Je vais fermer", - "RESTAURANT_ORDER_REFUSE_REASON_ORDER_WILL_BE_REFUSED": "Cette commande sera annulée", - "RESTAURANT_ORDER_REFUSE_REASON_ORDER_CONTINUE_RECEIVING": "Vous continuerez à recevoir de nouvelles commandes", - "RESTAURANT_ORDER_REFUSE_REASON_ORDER_STOP_RECEIVING": "Vous ne recevrez plus de nouvelles commandes", - "RESTAURANT_ORDER_PRINT": "Imprimer", - "RESTAURANT_SEARCH_ORDERS": "Trouver commande...", - "RESTAURANT_SEARCH_ORDERS_INPUT_PLACEHOLDER": "Numéro de commande...", - "RESTAURANT_PRODUCTS": "Produits", - "RESTAURANT_CLOSE_ALERT_TITLE": "Fermer jusqu'à demain", - "RESTAURANT_OPENING_HOURS": "Horaires d'ouverture", - "RESTAURANT_SPECIAL_OPENING_HOURS": "Fermetures exceptionnelles", - "RESTAURANT_OPENING_HOURS_VALID_FROM_THROUGH": "Du {{validFrom}} au {{validThrough}}", - "RESTAURANT_OPENING_HOURS_ONE_DAY": "Le {{day}} de {{opens}} à {{closes}}", - "RESTAURANT_OPENING_HOURS_DAY_RANGE": "Du {{firstDay}} au {{lastDay}} de {{opens}} à {{closes}}", - "OPEN_IN_MAPS_TITLE": "Afficher la tâche sur une carte", - "OPEN_IN_MAPS_MESSAGE": "Choisissez une application", - "RESTAURANT_ORDER_TITLE": "Commande {{order.number}}", - "RESTAURANT_ORDER_PREPARATION_EXPECTED_AT": "À commencer à partir de {{date}}", - "RESTAURANT_ORDER_PICKUP_EXPECTED_AT": "À préparer pour {{date}}", - "RESTAURANT_ORDER_DELAY_MODAL_TITLE": "Retarder la commande", - "RESTAURANT_ORDER_DELAY_DISCLAIMER": "De combien souhaitez-vous retarder la commande ?", - "RESTAURANT_ORDER_CANCEL_MODAL_TITLE": "Annuler", - "RESTAURANT_ORDER_CANCEL_DISCLAIMER": "Veuillez nous indiquer pourquoi vous souhaitez annuler la commande", - "RESTAURANT_ORDER_CANCEL_REASON_CUSTOMER_HEADING": "Annulée par le client", - "RESTAURANT_SETTINGS_RUSH": "Passer en mode rush", - "RESTAURANT_SETTINGS_CHANGE_RESTAURANT": "Changer de restaurant", - "RESTAURANT_SETTINGS_MANAGE_PRODUCTS": "Gérer les produits", - "RESTAURANT_SETTINGS_OPENING_HOURS": "Horaires d'ouverture", - "RESTAURANT_SETTINGS_PRINTER": "Imprimantes", - "RESTAURANT_SETTINGS_HEADING": "Réglages du restaurant {{- name}}", - "RESTAURANT_SETTINGS_MENUS": "Changer de menu", - "RESTAURANT_SETTINGS_MENU_ACTIVATE": "Activer le menu {{- name }}", - "RESTAURANT_ALERT_RUSH_MODE_ON": "Le mode rush est activé", - "RESTAURANT_ALERT_CLOSED": "Le restaurant est fermé", - "RESTAURANT_ORDER_ACCEPTED_CONFIRM_TITLE": "Commande acceptée !", - "RESTAURANT_ORDER_ACCEPTED_CONFIRM_BODY": "La commande {{number}} ({{id}}) a été acceptée", - "RESTAURANT_ORDER_CANCELLED_CONFIRM_TITLE": "Commande annulée !", - "RESTAURANT_ORDER_CANCELLED_CONFIRM_BODY": "La commande {{number}} ({{id}}) a été annulée", - "RESTAURANT_PRINTER_CONNECTED_TITLE": "Appareil connecté", - "RESTAURANT_PRINTER_CONNECTED_BODY": "L'appareil {{name}} est désormais connecté", - "RESTAURANT_PRINTER_DISCONNECTED_TITLE": "Appareil déconnecté", - "RESTAURANT_PRINTER_DISCONNECTED_BODY": "L'appareil {{name}} a été déconnecté", - "RESTAURANT_PRINTER_CONNECT_ERROR_TITLE": "Impossible de se connecter à l'appareil", - "RESTAURANT_SOUND_ALERT_TITLE": "Le volume sonore est bas", - "RESTAURANT_SOUND_ALERT_MESSAGE": "Le volume sonore est plutôt bas. Vous devriez l'augmenter pour ne pas manquer les notifications.", - "RESTAURANT_SOUND_ALERT_CONFIRM": "D'accord", - "CHECKOUT_PRODUCT_OPTIONS_TITLE": "Choix des options", - "CHECKOUT_PRODUCT_OPTIONS_DISCLAIMER": "Choisissez les options pour le produit {{name}}", - "CHECKOUT_LOGIN_TITLE": "Authentification", - "CHECKOUT_LOGIN_DISCLAIMER": "Veuillez vous identifier pour poursuivre", - "CHECKOUT_SHIPPING_DATE": "Choisissez une date", - "CHECKOUT_PLEASE_ENTER_ADDRESS": "Veuillez entrer votre adresse", - "CHECKOUT_ADDRESS_NOT_VALID": "Désolé, nous ne livrons pas à cette adresse", - "CHECKOUT_ORDER_ADDRESS_DESCRIPTION": "Informations d'accès", - "CHECKOUT_ORDER_ADDRESS_DESCRIPTION_HELP": "Indiquez toute information utile (interphone…)", - "CHECKOUT_ORDER_PHONE_NUMBER_HELP": "Pour vous contacter en cas de problème", - "CHECKOUT_ORDER_NOTES": "Un message pour le restaurateur ?", - "CHECKOUT_ORDER_NOTES_HELP": "Ce message sera transmis au restaurateur (allergies, demandes spéciales)", - "CHECKOUT_MORE_INFOS": "Informations complémentaires", - "CHECKOUT_MORE_INFOS_DISCLAIMER": "Juste une dernière étape", - "CHECKOUT_PICK_DATE": "Ce magasin est fermé pour le moment, mais vous pouvez planifier une commande pour plus tard.", - "CHECKOUT_SCHEDULE_ORDER": "Planifier la commande", - "SCHEDULE": "Planifier", - "IGNORE": "Ignorer", - "CHECKOUT_PRODUCT_OPTIONS_CHOICES_BETWEEN": "Entre {{min}} et {{max}} choix", - "CHECKOUT_UNITS": "Quantité", - "ADD_TO_CART": "Ajouter au panier", - "NOTIFICATION_TASKS_CHANGED_TITLE": "Tâches mises à jour", - "NOTIFICATION_TASKS_CHANGED_DESC": "Vos tâches du {{date}} ont été mises à jour", - "NOTIFICATION_ORDER_CREATED_TITLE": "Nouvelle commande", - "NOTIFICATION_ORDER_CREATED_DESC": "Une nouvelle commande pour le {{date}} a été créée", - "NOTIFICATION_CLOSES_SOON": "Hop hop hop ! On se dépêche, il reste {{diff}} avant la fermeture !", - "UNASSIGNED_TASKS": "Non assignées", - "DISPATCH_UNASSIGNED_TASKS": "Non assignées", - "DISPATCH_TASK_LISTS": "Assignées", - "DISPATCH_TASK_LIST": "Assigné à {{username}}", - "DISPATCH_ADD_TASK_LIST": "Ajouter un utilisateur", - "DISPATCH_ADD_TASK": "Ajouter une tâche", - "DISPATCH_CREATE_TASK": "Créer une tâche", - "DISPATCH_NO_TASKS": "Pas de tâches pour le moment", - "DISPATCH_TASK": "Task #{{id}}", - "DISPATCH_ASSIGN_TASK": "Assigner une tâche", - "DISPATCH_PICK_USER": "Choisissez un utilisateur", - "DISPATCH_ASSIGN_TO_ME": "M'assigner la tâche", - "TASK_FORM_ADDRESS_LABEL": "Adresse", - "TASK_FORM_DONE_AFTER_LABEL": "À faire à partir de", - "TASK_FORM_DONE_BEFORE_LABEL": "À faire avant", - "TASK_FORM_COMMENTS_LABEL": "Commentaires", - "DISPATCH_DATE": "Choisissez une date", - "BACKGROUND_GEOLOCATION_NOTIFICATION_TITLE": "Géolocalisation en arrière-plan", - "BACKGROUND_GEOLOCATION_NOTIFICATION_TEXT": "Activée", - "NEW_NOTIFICATION": "Nouvelle notification", - "CLOSE": "Fermer", - "OPTIONAL": "Optionnel", - "ORDER_NEW": "En attente", - "ORDER_NEW_HELP": "Le restaurateur doit valider votre commande", - "ORDER_ACCEPTED": "Acceptée", - "ORDER_FULFILLED": "Terminée", - "ORDER_NUMBER": "Commande {{number}}", - "ORDER_CANCELLED": "Annulée", - "ADDRESS_NOT_PRECISE_ENOUGH": "Cette adresse n'est pas assez précise", - "REGISTER_CHECK_EMAIL": "Activation", - "REGISTER_CHECK_EMAIL_DISCLAIMER": "Un e-mail a été envoyé à l‘adresse {{email}}. Il contient un lien d‘activation sur lequel il vous faudra cliquer afin d‘activer votre compte.", - "REGISTER_CHECK_EMAIL_ALREADY_ACTIVATED": "Vous avez déjà activé votre compte ?", - "REGISTER_CHECK_EMAIL_LOGIN": "Identifiez-vous", - "REGISTER_CONFIRM": "Activation", - "TASK_WITH_ID": "Tâche #{{id}}", - "SIGNATURE": "Signature", - "SIGNATURE_DISCLAIMER": "Signez ci-dessous", - "SIGNATURE_CLEAR": "Effacer", - "SIGNATURE_ADD": "Ajouter la signature", - "PHOTO": "Photo", - "PHOTO_DISCLAIMER": "Prenez une photo", - "LOAD_MORE": "Charger plus", - "TASK_IMAGE_UPLOAD_CONFIRM_SHORT": "Image uploadée !", - "TASK_IMAGE_UPLOAD_CONFIRM_LONG": "L'image a été uploadée avec succès", - "AN_ERROR_OCCURRED": "Une erreur s'est produite", - "TASK_ADD_PROOF_OF_DELIVERY": "Ajouter une preuve de livraison", - "STORE_NEW_DELIVERY": "Nouvelle livraison", - "STORE_NEW_DELIVERY_ADDRESS": "Adresse", - "STORE_NEW_DELIVERY_SEARCH_CLIENT": "Rechercher un client", - "STORE_NEW_DELIVERY_ENTER_SEARCH_CLIENT": "Entrez le nom du client", + "common": { + "MY_ADDRESSES": "Mes adresses", + "MY_ORDERS": "Mes commandes", + "MY_DETAILS": "Mes informations personnelles", + "COURIER": "Coursier", + "TASK": "Tâche", + "TASKS": "Tâches", + "ITEM": "{{count}} article", + "ITEM_plural": "{{count}} articles", + "TASK_LIST": "Liste des tâches", + "WS_NOT_CONNECTED_WARNING": "Refresh automatique désactivé. Reconnexion..", + "DISPATCH": "Dispatch", + "RESTAURANT": "Restaurant", + "RESTAURANTS": "Restaurants", + "BACK": "Retour", + "CART": "Panier", + "CART_EMPTY_WARNING": "Votre panier est vide", + "DELIVERY_ADDR": "Adresse de livraison", + "PAYMENT": "Paiement", + "PAY_AMOUNT": "Payer {{amount}}", + "ORDER_TRACKING": "Suivi de commande", + "TASKS_UPDATED": "Vos tâches ont été mises à jour", + "FAILED_TASK_LOAD": "Impossible de charger les tâches", + "FAILED_TASK_COMPLETE": "Impossible de terminer cette tâche", + "TRY_LATER": "Veuillez réessayer plus tard", + "NET_FAILED": "Impossible de se connecter", + "RETRY": "Réessayer", + "SERVER_INCOMPATIBLE": "Ce serveur n'est pas compatible", + "SERVER_INVALID": "Ce serveur n'est pas valide", + "SERVER_UNDER_MAINTENANCE": "Le serveur est en maintenance", + "CHOOSE_SERVER": "Veuillez choisir une ville pour commencer", + "CHOOSE_CITY": "Choisir une ville", + "CITY_NOT_LISTED": "Votre ville n'est pas listée ?", + "CONTACT_US": "Contactez-nous", + "CUSTOM": "Personnalisé", + "SERVER_URL": "Votre serveur n'est pas listé ? Entrez son adresse", + "EXAMPLE": "Exemple", + "SUBMIT": "Valider", + "NEXT": "Suivant", + "CONNECTED_TO": "Connecté à", + "CHANGE_SERVER": "Choisir un autre serveur", + "LOADING": "Chargement", + "HELLO": "Bonjour", + "DETAILS": "Informations personnelles", + "ADDRESSES": "Adresses", + "ORDERS": "Commandes", + "SIGN_OUT": "Déconnexion", + "CONTINUE": "Continuer", + "EDIT": "Modifier", + "NAME": "Nom", + "ADDRESS_DESCRIPTION": "Instructions", + "ADDRESS_DESCRIPTION_PLACEHOLDER": "2ème porte à gauche…", + "TELEPHONE": "Téléphone", + "TOTAL_ITEMS": "Total articles", + "TOTAL_DELIVERY": "Total livraison", + "TOTAL": "Total", + "MY_TASKS": "Mes tâches", + "MY_STATS": "Mes statistiques", + "ENTER_PAY_DETAILS": "Veuillez entrer vos coordonnées bancaires", + "PAY": "Payer", + "CONNECTING_SERVER": "Connexion au serveur", + "PLS_WAIT": "Veuillez patienter", + "WAITING_FOR_ORDER": "En attente d'une commande", + "LOADING_ORDER": "Chargement de la commande", + "CALCULATING_ROUTE": "Calcul de l'itinéraire", + "CALCULATING_YOUR_POS": "Calcul de votre position", + "TRACKING_YOUR_POS": "Suivi de votre position", + "CONN_LOST": "Connexion perdue, reconnexion", + "CONN_LOST_IDLE": "Connexion perdue", + "AUTHENTICATING": "Authentification…", + "REFUSE": "Refuser", + "ACCEPT": "Accepter", + "ORDER_COLLECTED": "Commande récupérée", + "ORDER_DELIVERED": "Commande livrée", + "HOME": "Accueil", + "MY_ACCOUNT": "Mon compte", + "FIND_RESTAURANT": "Trouvez un restaurant", + "SEARCH_NEARBY": "Cherchez à proximité, découvrez les menus, passez votre commande", + "WELCOME": "Bienvenue", + "WELCOME_TEXT": "Merci d'avoir téléchargé CoopCycle. \nPour commencer à utiliser l'application, connectez-vous à un serveur.", + "ABOUT_COOPCYCLE": "À l'inverse des grandes plateformes, avec CoopCycle la mise en place d'un service de livraison de repas est à l'initiative des livreuses et des livreurs, qui décident de se constituer en coopérative à l'échelle locale. \n\nSi votre ville n'est pas listée, c'est qu'il n'y a pas (encore) de projet. \n\nSi vous êtes un groupe de livreuses et de livreurs, contactez-nous pour savoir comment nous pouvons vous aider à monter votre projet en coopérative.", + "ABOUT": "À propos", + "ABOUT_INSTANCE": "À propos de {{name}}", + "NO_RESTAURANTS": "Désolé, nous ne sommes pas disponibles à cet emplacement pour le moment", + "SEARCH_WITHOUT_DATE": "Voulez-vous relancer la recherche sans inclure de date", + "SEARCH_AGAIN": "Relancer la recherche", + "ENTER_ADDRESS": "Entrez votre adresse", + "ENTER_POSTCODE": "Entrez votre code postal", + "TODAY": "Aujourd'hui", + "TOMORROW": "Demain", + "CANCEL": "Annuler", + "VIEW": "Afficher", + "WHEN": "Quand", + "ADDRESS": "Adresse", + "POST_CODE": "Code postal", + "CITY": "Ville", + "USERNAME": "Nom d'utilisateur", + "USERNAME_OR_EMAIL": "Nom d'utilisateur ou email", + "PASSWORD": "Mot de passe", + "EMAIL": "E-mail", + "CHECKOUT_FROM": "À partir de {{date}}", + "ORDER": "Commander", + "SCHEDULE_ORDER": "Pré-commander", + "INVALID_USER_PASS": "Utilisateur et/ou mot de passe inexistant", + "NO_TASKS": "Pas de tâches prévues aujourd'hui", + "NOTES": "Notes", + "COMPLETED": "Terminée", + "FAILED": "Échec", + "VALIDATE": "Valider", + "MARK_FAILED": "Signaler un problème", + "REPORT_INCIDENT": "Signaler un incident", + "COMPLETE_TASK": "Terminer", + "SWIPE_TO_END": "Glissez vers la droite pour terminer, ou vers la gauche en cas de problème", + "WAITING_FOR_POS": "En attente de la position", + "PROBLEM_CONNECTING": "Connexion impossible", + "OK": "OK", + "LAST_TASK_EVENT": "Dernier évènement {{fromNow}}", + "PREVIOUS_TASK": "Tâche précédente", + "NEXT_TASK": "Tâche suivante", + "HISTORY": "Historique", + "SETTINGS": "Réglages", + "SETTING_KEEP_AWAKE": "Désactiver la mise en veille", + "OR_REGISTER": "Pas encore inscrit ? Créez votre compte", + "OR_LOGIN": "Vous avez déjà un compte ? Identifiez-vous", + "FORGOT_PASSWORD": "Mot de passe oublié ?", + "RESET_PASSWORD_CHECK_EMAIL": "Reset password", + "RESET_PASSWORD_CHECK_EMAIL_DISCLAIMER": "Un e-mail a été envoyé à l'adresse {{email}}. Il contient un lien sur lequel il vous faudra cliquer pour réinitialiser votre mot de passe.", + "RESET_PASSWORD_NEW_PASSWORD": "Réinitialiser le mot de passe", + "RESET_PASSWORD_LINK_EXPIRED": "Le lien a expiré, essayez de réinitialiser le mot de passe à nouveau", + "GIVEN_NAME": "Prénom", + "FAMILY_NAME": "Nom de Famille", + "PHONE_NUMBER": "Téléphone", + "CONFIRM_PASSWORD": "Confirmez le mot de passe", + "EMAIL_ALREADY_REGISTERED": "Cet e-mail a déjà été enregistré", + "INVALID_GIVEN_NAME": "Veuillez entrer un prénom valide", + "INVALID_FAMILY_NAME": "Veuillez entrer un nom de famille valide", + "INVALID_EMAIL": "Veuillez entrer un email valide", + "INVALID_PHONE_NUMBER": "Veuillez entrer une numéro de téléphone valide", + "INVALID_USERNAME": "Veuillez entrer un nom d'utilisateur valide", + "INVALID_USERNAME_FORMAT": "Un nom d’utilisateur peut seulement contenir des caractères alphanumériques (lettres A-Z, chiffres 0-9) à l’exception des underscores", + "INVALID_PASSWORD": "Veuillez entrer un mot de passe valide d'au moins 8 caractères", + "INVALID_PASSWORD_CONFIRMATION": "La confirmation du mot de passe ne correspond pas", + "TASKS_FILTER": "Filtrer les tâches", + "HIDE_DONE_TASKS": "Masquer les tâches effectuées", + "TASKS_SHOW_POLYLINE": "Montrer la ligne entre les tâches", + "HIDE_FAILED_TASKS": "Masquer les tâches ayant échoué", + "HIDE_TASKS_TAGGED_WITH": "Masquer les tâches marquées avec", + "RESTAURANT_LIST_CLICK_BELOW": "Cliquez sur un restaurant dans la liste ci-dessous", + "RESTAURANT_ORDER_LIST_NEW_ORDERS": "Nouvelles commandes ({{count}})", + "RESTAURANT_ORDER_LIST_ACCEPTED_ORDERS": "Commandes acceptées ({{count}})", + "RESTAURANT_ORDER_LIST_CANCELLED_ORDERS": "Commandes annulées ({{count}})", + "RESTAURANT_ORDER_LIST_FULFILLED_ORDERS": "Commandes terminées ({{count}})", + "RESTAURANT_ORDER_BUTTON_ACCEPT": "Accepter", + "RESTAURANT_ORDER_BUTTON_REFUSE": "Refuser", + "RESTAURANT_ORDER_BUTTON_CANCEL": "Annuler", + "RESTAURANT_ORDER_BUTTON_DELAY": "Retarder", + "RESTAURANT_ORDER_BUTTON_FULFILL": "Terminer", + "RESTAURANT_ORDER_REFUSE_DISCLAIMER": "Veuillez nous indiquer pourquoi vous souhaitez refuser cette commande pour que nous puissions informer le client.", + "RESTAURANT_ORDER_REFUSE_REASON_SOLD_OUT_HEADING": "Je suis en rupture de stock", + "RESTAURANT_ORDER_REFUSE_REASON_RUSH_HOUR_HEADING": "Je suis en plein rush", + "RESTAURANT_ORDER_REFUSE_REASON_CLOSING_HEADING": "Je vais fermer", + "RESTAURANT_ORDER_REFUSE_REASON_ORDER_WILL_BE_REFUSED": "Cette commande sera annulée", + "RESTAURANT_ORDER_REFUSE_REASON_ORDER_CONTINUE_RECEIVING": "Vous continuerez à recevoir de nouvelles commandes", + "RESTAURANT_ORDER_REFUSE_REASON_ORDER_STOP_RECEIVING": "Vous ne recevrez plus de nouvelles commandes", + "RESTAURANT_ORDER_PRINT": "Imprimer", + "RESTAURANT_SEARCH_ORDERS": "Trouver commande...", + "RESTAURANT_SEARCH_ORDERS_INPUT_PLACEHOLDER": "Numéro de commande...", + "RESTAURANT_PRODUCTS": "Produits", + "RESTAURANT_CLOSE_ALERT_TITLE": "Fermer jusqu'à demain", + "RESTAURANT_OPENING_HOURS": "Horaires d'ouverture", + "RESTAURANT_SPECIAL_OPENING_HOURS": "Fermetures exceptionnelles", + "RESTAURANT_OPENING_HOURS_VALID_FROM_THROUGH": "Du {{validFrom}} au {{validThrough}}", + "RESTAURANT_OPENING_HOURS_ONE_DAY": "Le {{day}} de {{opens}} à {{closes}}", + "RESTAURANT_OPENING_HOURS_DAY_RANGE": "Du {{firstDay}} au {{lastDay}} de {{opens}} à {{closes}}", + "OPEN_IN_MAPS_TITLE": "Afficher la tâche sur une carte", + "OPEN_IN_MAPS_MESSAGE": "Choisissez une application", + "RESTAURANT_ORDER_TITLE": "Commande {{order.number}}", + "RESTAURANT_ORDER_PREPARATION_EXPECTED_AT": "À commencer à partir de {{date}}", + "RESTAURANT_ORDER_PICKUP_EXPECTED_AT": "À préparer pour {{date}}", + "RESTAURANT_ORDER_DELAY_MODAL_TITLE": "Retarder la commande", + "RESTAURANT_ORDER_DELAY_DISCLAIMER": "De combien souhaitez-vous retarder la commande ?", + "RESTAURANT_ORDER_CANCEL_MODAL_TITLE": "Annuler", + "RESTAURANT_ORDER_CANCEL_DISCLAIMER": "Veuillez nous indiquer pourquoi vous souhaitez annuler la commande", + "RESTAURANT_ORDER_CANCEL_REASON_CUSTOMER_HEADING": "Annulée par le client", + "RESTAURANT_SETTINGS_RUSH": "Passer en mode rush", + "RESTAURANT_SETTINGS_CHANGE_RESTAURANT": "Changer de restaurant", + "RESTAURANT_SETTINGS_MANAGE_PRODUCTS": "Gérer les produits", + "RESTAURANT_SETTINGS_OPENING_HOURS": "Horaires d'ouverture", + "RESTAURANT_SETTINGS_PRINTER": "Imprimantes", + "RESTAURANT_SETTINGS_HEADING": "Réglages du restaurant {{- name}}", + "RESTAURANT_SETTINGS_MENUS": "Changer de menu", + "RESTAURANT_SETTINGS_MENU_ACTIVATE": "Activer le menu {{- name }}", + "RESTAURANT_ALERT_RUSH_MODE_ON": "Le mode rush est activé", + "RESTAURANT_ALERT_CLOSED": "Le restaurant est fermé", + "RESTAURANT_ORDER_ACCEPTED_CONFIRM_TITLE": "Commande acceptée !", + "RESTAURANT_ORDER_ACCEPTED_CONFIRM_BODY": "La commande {{number}} ({{id}}) a été acceptée", + "RESTAURANT_ORDER_CANCELLED_CONFIRM_TITLE": "Commande annulée !", + "RESTAURANT_ORDER_CANCELLED_CONFIRM_BODY": "La commande {{number}} ({{id}}) a été annulée", + "RESTAURANT_PRINTER_CONNECTED_TITLE": "Appareil connecté", + "RESTAURANT_PRINTER_CONNECTED_BODY": "L'appareil {{name}} est désormais connecté", + "RESTAURANT_PRINTER_DISCONNECTED_TITLE": "Appareil déconnecté", + "RESTAURANT_PRINTER_DISCONNECTED_BODY": "L'appareil {{name}} a été déconnecté", + "RESTAURANT_PRINTER_CONNECT_ERROR_TITLE": "Impossible de se connecter à l'appareil", + "RESTAURANT_SOUND_ALERT_TITLE": "Le volume sonore est bas", + "RESTAURANT_SOUND_ALERT_MESSAGE": "Le volume sonore est plutôt bas. Vous devriez l'augmenter pour ne pas manquer les notifications.", + "RESTAURANT_SOUND_ALERT_CONFIRM": "D'accord", + "CHECKOUT_PRODUCT_OPTIONS_TITLE": "Choix des options", + "CHECKOUT_PRODUCT_OPTIONS_DISCLAIMER": "Choisissez les options pour le produit {{name}}", + "CHECKOUT_LOGIN_TITLE": "Authentification", + "CHECKOUT_LOGIN_DISCLAIMER": "Veuillez vous identifier pour poursuivre", + "CHECKOUT_SHIPPING_DATE": "Choisissez une date", + "CHECKOUT_PLEASE_ENTER_ADDRESS": "Veuillez entrer votre adresse", + "CHECKOUT_ADDRESS_NOT_VALID": "Désolé, nous ne livrons pas à cette adresse", + "CHECKOUT_ORDER_ADDRESS_DESCRIPTION": "Informations d'accès", + "CHECKOUT_ORDER_ADDRESS_DESCRIPTION_HELP": "Indiquez toute information utile (interphone…)", + "CHECKOUT_ORDER_PHONE_NUMBER_HELP": "Pour vous contacter en cas de problème", + "CHECKOUT_ORDER_NOTES": "Un message pour le restaurateur ?", + "CHECKOUT_ORDER_NOTES_HELP": "Ce message sera transmis au restaurateur (allergies, demandes spéciales)", + "CHECKOUT_MORE_INFOS": "Informations complémentaires", + "CHECKOUT_MORE_INFOS_DISCLAIMER": "Juste une dernière étape", + "CHECKOUT_PICK_DATE": "Ce magasin est fermé pour le moment, mais vous pouvez planifier une commande pour plus tard.", + "CHECKOUT_SCHEDULE_ORDER": "Planifier la commande", + "SCHEDULE": "Planifier", + "IGNORE": "Ignorer", + "CHECKOUT_PRODUCT_OPTIONS_CHOICES_BETWEEN": "Entre {{min}} et {{max}} choix", + "CHECKOUT_UNITS": "Quantité", + "ADD_TO_CART": "Ajouter au panier", + "NOTIFICATION_TASKS_CHANGED_TITLE": "Tâches mises à jour", + "NOTIFICATION_TASKS_CHANGED_DESC": "Vos tâches du {{date}} ont été mises à jour", + "NOTIFICATION_ORDER_CREATED_TITLE": "Nouvelle commande", + "NOTIFICATION_ORDER_CREATED_DESC": "Une nouvelle commande pour le {{date}} a été créée", + "NOTIFICATION_CLOSES_SOON": "Hop hop hop ! On se dépêche, il reste {{diff}} avant la fermeture !", + "UNASSIGNED_TASKS": "Non assignées", + "DISPATCH_UNASSIGNED_TASKS": "Non assignées", + "DISPATCH_TASK_LISTS": "Assignées", + "DISPATCH_TASK_LIST": "Assigné à {{username}}", + "DISPATCH_ADD_TASK_LIST": "Ajouter un utilisateur", + "DISPATCH_ADD_TASK": "Ajouter une tâche", + "DISPATCH_CREATE_TASK": "Créer une tâche", + "DISPATCH_NO_TASKS": "Pas de tâches pour le moment", + "DISPATCH_TASK": "Task #{{id}}", + "DISPATCH_ASSIGN_TASK": "Assigner une tâche", + "DISPATCH_PICK_USER": "Choisissez un utilisateur", + "DISPATCH_ASSIGN_TO_ME": "M'assigner la tâche", + "TASK_FORM_ADDRESS_LABEL": "Adresse", + "TASK_FORM_DONE_AFTER_LABEL": "À faire à partir de", + "TASK_FORM_DONE_BEFORE_LABEL": "À faire avant", + "TASK_FORM_COMMENTS_LABEL": "Commentaires", + "DISPATCH_DATE": "Choisissez une date", + "BACKGROUND_GEOLOCATION_NOTIFICATION_TITLE": "Géolocalisation en arrière-plan", + "BACKGROUND_GEOLOCATION_NOTIFICATION_TEXT": "Activée", + "NEW_NOTIFICATION": "Nouvelle notification", + "CLOSE": "Fermer", + "OPTIONAL": "Optionnel", + "ORDER_NEW": "En attente", + "ORDER_NEW_HELP": "Le restaurateur doit valider votre commande", + "ORDER_ACCEPTED": "Acceptée", + "ORDER_FULFILLED": "Terminée", + "ORDER_NUMBER": "Commande {{number}}", + "ORDER_CANCELLED": "Annulée", + "ADDRESS_NOT_PRECISE_ENOUGH": "Cette adresse n'est pas assez précise", + "REGISTER_CHECK_EMAIL": "Activation", + "REGISTER_CHECK_EMAIL_DISCLAIMER": "Un e-mail a été envoyé à l‘adresse {{email}}. Il contient un lien d‘activation sur lequel il vous faudra cliquer afin d‘activer votre compte.", + "REGISTER_CHECK_EMAIL_ALREADY_ACTIVATED": "Vous avez déjà activé votre compte ?", + "REGISTER_CHECK_EMAIL_LOGIN": "Identifiez-vous", + "REGISTER_CONFIRM": "Activation", + "TASK_WITH_ID": "Tâche #{{id}}", + "SIGNATURE": "Signature", + "SIGNATURE_DISCLAIMER": "Signez ci-dessous", + "SIGNATURE_CLEAR": "Effacer", + "SIGNATURE_ADD": "Ajouter la signature", + "PHOTO": "Photo", + "PHOTO_DISCLAIMER": "Prenez une photo", + "LOAD_MORE": "Charger plus", + "TASK_IMAGE_UPLOAD_CONFIRM_SHORT": "Image uploadée !", + "TASK_IMAGE_UPLOAD_CONFIRM_LONG": "L'image a été uploadée avec succès", + "AN_ERROR_OCCURRED": "Une erreur s'est produite", + "TASK_ADD_PROOF_OF_DELIVERY": "Ajouter une preuve de livraison", + "STORE_NEW_DELIVERY": "Nouvelle livraison", + "STORE_NEW_DELIVERY_ADDRESS": "Adresse", + "STORE_NEW_DELIVERY_SEARCH_CLIENT": "Rechercher un client", + "STORE_NEW_DELIVERY_ENTER_SEARCH_CLIENT": "Entrez le nom du client", - "STORE_NEW_DELIVERY_ADDRESS_HELP": "Entrez l'adresse de dépôt pour vérifier la faisabilité", - "STORE_NEW_DELIVERY_PHONE_NUMBER": "Numéro de téléphone", - "STORE_NEW_DELIVERY_ENTER_PHONE_NUMBER": "Entrez un numéro de téléphone valide", - "STORE_NEW_DELIVERY_BUSINESS_NAME": "Nom de l'entreprise", - "STORE_NEW_DELIVERY_ENTER_BUSINESS_NAME": "Entrez le nom de l'entreprise", - "STORE_NEW_DELIVERY_CONTACT_NAME": "Contact", - "STORE_NEW_DELIVERY_ENTER_CONTACT_NAME": "Entrez le nom de la personne à contacter", - "STORE_NEW_DELIVERY_ADDRESS_DESCRIPTION": "Informations d'accès", - "STORE_NEW_DELIVERY_ENTER_ADDRESS_DESCRIPTION": "Indiquez toute information utile pour l'adresse indiquée (interphone…)", - "STORE_NEW_DELIVERY_COMMENTS": "Commentaires", - "STORE_NEW_DELIVERY_ENTER_COMMENTS": "Indiquez tous les détails pour finaliser la livraison (fragile, ...)", - "STORE_NEW_DELIVERY_WEIGHT": "Poids personnalisé", - "STORE_NEW_DELIVERY_ENTER_WEIGHT": "Entrez le poids du paquet", - "STORE_NEW_DELIVERY_PACKAGES": "Paquets", - "STORE_NEW_DELIVERY_NO_PACKAGES": "Aucun paquet", - "STORE_NEW_DELIVERY_DROPOFF_BEFORE": "À déposer avant", - "STORE_NEW_DELIVERY_TIME_SLOT": "Tranche horaire", - "STORE_NEW_DELIVERY_SELECT_TIME_SLOT": "Choisissez une tranche horaire", - "STORE_NEW_DELIVERY_ERROR": { - "EMPTY_TIME_SLOT": "Veuillez choisir une tranche horaire", - "EMPTY_CONTACT_NAME": "Veuillez fournir le nom d'une personne à contacter", - "EMPTY_PHONE_NUMBER": "Veuillez fournir un numéro de téléphone", - "EMPTY_WEIGHT": "Veillez fournir le poids du colis", - "EMPTY_PACKAGES": "Veillez sélectionner les paquets" - }, - "STORE_DELIVERY": "Livraison #{{id}}", - "DELIVERY_STATE": { - "undefined": "Inconnu", - "unknown": "Inconnu", - "new": "Nouveau", - "picked": "Récupérée", - "fulfilled": "Livrée" - }, - "DELIVERY_DETAILS_TIME_SLOT": "Tranche horaire", - "DELIVERY_DETAILS_RECIPIENT": "Destinataire", - "TODAY_BETWEEN_START_AND_END": "Aujourd'hui entre {{start}} et {{end}}", - "TOMORROW_BETWEEN_START_AND_END": "Demain entre {{start}} et {{end}}", - "OTHER_DAY_BETWEEN_START_AND_END": "{{date}} entre {{start}} et {{end}}", - "SIGNATURE_SCREEN_FIRST": "Écran de signature par défaut", - "CART_DELIVERY_TIME": "Livraison {{- fromNow}}", - "CART_DELIVERY_TIME_DIFF": "Livraison dans {{diff}} minutes", - "CART_COLLECTION_TIME": "Retrait {{- fromNow}}", - "CART_COLLECTION_TIME_DIFF": "Retrait dans {{diff}} minutes", - "TIME_DIFF_SHORT": "{{min}} - {{max}} minutes", - "FINISHED": "Terminé", - "SEARCH": "Rechercher…", - "GO_BACK_TO_RESTAURANTS": "Recommencer", - "SESSION_EXPIRED": "Votre session a expiré", - "TOUCH_TO_RELOAD": "Touchez pour réessayer", - "OFFLINE": "Vous êtes déconnecté·e", - "SCAN_FOR_PRINTERS": "Touchez l'écran pour scanner", - "SEARCH_WITH_ADDRESS": "Rechercher « {{address}} »", - "RESTAURANT_ORDER_CONNECT_PRINTER": "Connecter une imprimante", - "SWIPE_TO_ACCEPT_REFUSE": "Glissez vers la droite pour accepter, vers la gauche pour refuser", - "ADD_COUPON": "Ajouter un code promo", - "VOUCHER_CODE": "Code promo", - "DELIVERY_ASAP": "Au plus tôt", - "PHOTO_ADD": "Ajouter l'image", - "TASKS_CHANGED_ALERT_SOUND": "Son pour les notifications", - "RECEIPT_HEADING_ORDER_NUMBER": "COMMANDE {{number}} (#{{id}})", - "RECEIPT_CUSTOMER_NAME": "CLIENT: {{customer}}", - "RECEIPT_HEADING_PICKUP_EXPECTED_AT": "A {{time}}", - "RECEIPT_HEADING_PICKUP_EXPECTED_ON": "RETRAIT LE {{time}}", - "RECEIPT_HEADING_PICKUP_EXPECTED_TODAY": "RETRAIT AUJOURD'HUI", - "RESTRICTED_DIET": { - "DiabeticDiet": "Diabétique", - "GlutenFreeDiet": "Sans gluten", - "HalalDiet": "Halal", - "HinduDiet": "Hindou", - "KosherDiet": "Casher", - "LowCalorieDiet": "Faible calorie", - "LowFatDiet": "Sans graisses", - "LowLactoseDiet": "Sans lactose", - "LowSaltDiet": "Sans sel", - "VeganDiet": "Vegan", - "VegetarianDiet": "Végétarien" - }, - "ALLERGEN": { - "CEREALS_CONTAINING_GLUTEN": "Céréales contenant du gluten", - "CRUSTACEANS": "Crustacés", - "EGGS": "Oeufs", - "FISH": "Poisson", - "PEANUTS": "Arachides", - "SOYBEANS": "Soja", - "MILK": "Lait", - "NUTS": "Noix", - "CELERY": "Céleri", - "MUSTARD": "Moutarde", - "SESAME_SEEDS": "Graines de sésame", - "SULPHUR_DIOXIDE_SULPHITES": "Dioxyde de soufre", - "LUPIN": "Lupins", - "MOLLUSCS": "Mollusques" - }, - "FULFILLMENT_METHOD": { - "collection": "Retrait sur place", - "delivery": "Livraison" - }, - "CART_COLLECTION_DISCLAIMER": "Vous avez opté pour le retrait sur place de votre commande. Vous vous engagez à venir récupérer votre commande le jour et l'heure indiqués. \nEn cas de problème, contactez l'établissement au {{telephone}}. \nSi vous ne vous présentez pas, nous encaisserons la commande dans son intégralité. ", - "LEARN_MORE": "En savoir plus", - "SELECTED": "Selectionné", - "AVAILABLE": "Disponible", - "COMING_SOON": "Bientôt", - "CARDHOLDER_NAME": "Nom du titulaire de la carte", - "RESTAURANT_ORDER_LIST_PICKED_ORDERS": "Commandes récupérées ({{count}})", - "NOTIFICATION_TASKS_ADDED": "{{ count }} ajoutée(s)", - "NOTIFICATION_TASKS_REMOVED": "{{ count }} supprimée(s)", - "NOT_AVAILABLE_ATM": "Indisponible pour l'instant", - "BACKGROUND_PERMISSION_DISCLOSURE": { - "title": "Utilisation de votre position", - "message": "Afin de permettre aux répartiteurs d'attribuer les tâches avec précision, autorisez CoopCycle à accéder à la position de cet appareil même lorsque l'application fermée ou n'est pas utilisée.\n\nCes données seront téléversées sur le serveur CoopCycle auquel vous êtes connecté." - }, - "BACKGROUND_PERMISSION_RATIONALE": { - "title": "Permettre à {applicationName} d'accéder à la position de cet appareil même lorsque l'application fermée ou n'est pas utilisée", - "message": "Pour les utilisateurs authentifiés comme coursiers, cette application collecte les données de localisation afin de permettre aux répartiteurs d'attribuer les tâches avec précision", - "positiveAction": "Changer pour « {backgroundPermissionOptionLabel} »" - }, - "RESTAURANT_SETTINGS_MANAGE_PRODUCT_OPTIONS": "Gérer les options des produits", - "STORES": "Magasins", - "FILTER_BY_TAGS": "Filtrer par tags", - "CONNECT_WITH_FACEBOOK": "Se connecter avec Facebook", - "RESTAURANT_PRE_ORDER": "Pré-commander", - "RESTAURANT_CLOSED_BUT_OPENS": "Ce restaurant est fermé maintenant mais ouvre {{ datetime }}. Vous pouvez quand même commander pour plus tard !", - "RESTAURANT_CLOSED_AND_NOT_AVAILABLE": "Ce restaurant est fermé et non disponible jusqu'à {{ datetime }}.", - "CHECKOUT_AS_GUEST": "Commander en tant qu'invité", - "GUEST_CHECKOUT_ORDER_EMAIL_HELP": "Pour vous envoyer des mises à jour à propos de votre commande", - "GUEST_CHECK_EMAIL_FOR_ORDER_UPDATES": "Comme vous avez choisi de commander en tant qu'invité, veuillez vérifier vos e-mails pour suivre votre commande", - "MY_ORDER": "Ma commande", - "OR": "ou", - "total_packages": "Total : {{count}} paquet", - "total_packages_plural": "Total : {{count}} paquets", - "EMPTY_CARTS_TITLE": "Ajoutez des articles pour commencer un panier", - "EMPTY_CARTS_SUBTITLE": "Une fois que vous avez ajouté des articles, votre panier s'affiche ici.", - "GO_TO_SHOPPING": "Commander", - "SAVE_AND_CONTINUE": "Enregistrer et continuer", - "ENTER_NEW_ADDRESS": "Entrez votre nouvelle adresse", - "OPTION_REQUIRED": "Obligatoire", - "SOME_OPTION_IS_REQUIRED_SELECT_ONE_VALUE": "Il y a des options obligatoires, sélectionnez une valeur parmi celles-ci pour continuer", - "WHERE_ARE_YOU": "Adresse de livraison ?", - "ASK_ADDRESS_DISCLAIMER": "Nous utilisons votre adresse pour vous aider à trouver les meilleurs commerces à proximité.", - "TOP_RESULTS": "Top résultats", - "OTHER_RESULTS": "Autres résultats", - "RESULT": "{{count}} résultat", - "RESULT_plural": "{{count}} résultats", - "EMPTY_HERE": "C'est complètement vide par ici", - "CALL": "Appeler", - "HELP": "Aide", - "SHOW_DELIVERY_ZONE": "Afficher la zone de livraison", - "ORDER_TIMELINE_CREATED_TITLE": "Commande créée", - "ORDER_TIMELINE_ACCEPTED_TITLE": "Commande confirmée", - "ORDER_TIMELINE_REFUSED_TITLE": "Commande refusée", - "ORDER_TIMELINE_REFUSED_DESCRIPTION": "Votre commande ne peut pas être préparée par le restaurant", - "ORDER_TIMELINE_PICKED_TITLE": "Commande récupérée", - "ORDER_TIMELINE_DROPPED_TITLE": "Commande livrée", - "ORDER_TIMELINE_CANCELLED_TITLE": "Commande annulée", - "ORDER_TIMELINE_AFTER_ACCEPTED_TITLE": "Le restaurant prépare votre commande", - "ORDER_TIMELINE_AFTER_ACCEPTED_DESCRIPTION": "Nous vous tiendrons informé quand le livreur aura récupéré votre commande", - "ORDER_TIMELINE_AFTER_CREATED_TITLE": "Commande en attente de validation", - "ORDER_TIMELINE_AFTER_CREATED_DESCRIPTION": "Le restaurateur doit valider votre commande. Dans le cas où votre commande serait refusée vous ne serez pas débité", - "ORDER_TIMELINE_AFTER_PICKED_TITLE": "Votre livreur arrive !", - "ORDER_TIMELINE_AFTER_PICKED_DESCRIPTION": "Préparez-vous pour l'accueillir !", - "ORDER_ETA": "Arrivée estimée entre {{start}} et {{end}}", - "ORDER_ABOUT": "Informations de la commande", - "SHOW_ORDER_DETAILS": "Afficher le détail de la commande", - "SHARE_INVOICE": "Partager la facture", - "GENERATE_INVOICE": "Éditer la facture", - "BILLING_ADDRESS": "Adresse de facturation", - "TIP": "Pourboire", - "LEGAL_TEXTS_LABEL": "J'accepte les conditions générales et la politique de confidentialité", - "LEGAL_TEXTS_ERROR_MESSAGE": "Vous devez accepter les conditions générales et la politique de confidentialité", - "TERMS_AND_CONDITIONS_BUTTON_LABEL": "Lire les conditions générales", - "PRIVACY_POLICY_BUTTON_LABEL": "Lire la politique de confidentialité", - "TERMS_AND_CONDITIONS_LABEL": "J'accepte les conditions générales", - "TERMS_AND_CONDITIONS_ERROR_MESSAGE": "Vous devez accepter les conditions générales", - "PRIVACY_POLICY_LABEL": "J'accepte la politique de confidentialité", - "PRIVACY_POLICY_ERROR_MESSAGE": "Vous devez accepter la politique de confidentialité", - "AGREE": "J'accepte", - "DELETE_ACCOUNT": "Supprimer mon compte", - "DELETE_ACCOUNT_DISCLAIMER": "En cliquant sur le bouton ci-dessous, vous allez supprimer votre compte. Vous êtes sûr·e ?", - "PAY_WITH_SAVED_CREDIT_CARD": "Payer avec une carte bancaire sauvegardée", - "SELECT_SAVED_CARD_ERROR": "Veuillez sélectionner une carte bancaire pour payer", - "INVALID_CREDIT_CARD_ERROR": "Veuillez entrer des données de carte bancaire valides", - "INVALID_CARD_HOLDER_NAME_ERROR": "Veuillez entrer un titulaire de carte bancaire valide", - "CREDIT_CARD_EXPIRATION": "Expiration", - "ADD_NEW_CREDIT_CARD": "Ajouter une nouvelle carte bancaire", - "SELECT_SAVED_CARD": "Sélectionner une carte bancaire sauvegardée", - "SAVE_CARD": "Sauvegarder cette carte bancaire", - "SEARCH_TAB": "Rechercher", - "SEARCH_INPUT_PLACEHOLDER": "Une boutique, un plat…", - "SEARCH_SHOPS": "Boutiques", - "SEARCH_PRODUCTS": "Produits", - "SEARCH_PRODUCT_ITEM_SUBTITLE": "Dans {{shop}}", - "SEARCH_WITHOUT_RESULTS": "Aucun résultat. Essayez de chercher autre chose.", - "APPLE_SIGN_IN_HIDE_MY_EMAIL_ERROR": "Désolé, nous ne pouvons pas vous authentifier si vous choisissez de masquer votre e-mail", - "SOCIAL_SIGN_IN_UNKNOWN_EMAIL": "Désolé, nous nʼavons pas trouvé de compte correspondant à l'e-mail associé à votre compte {{provider}}.", - "CHECKOUT_LOOPEAT_CONNECT_ACCOUNT": "Connecter mon compte {{name}}", - "CHECKOUT_LOOPEAT_OPTION_ENABLED": "Gérer ma consigne", - "CHECKOUT_LOOPEAT_INSUFFICIENT_WALLET_AMOUNT": "Cagnotte consigne insuffisante", - "CHECKOUT_LOOPEAT_VALIDATE": "Valider et retourner au panier", - "CHECKOUT_LOOPEAT_RETURN_CONTAINERS": "Rendre des boîtes", - "CHECKOUT_LOOPEAT_RETURN_CONTAINER": "Rendre la boîte", - "CHECKOUT_LOOPEAT_WALLET": "Cagnotte", - "CHECKOUT_LOOPEAT_TOTAL_RETURNS": "Total retours", - "CHECKOUT_LOOPEAT_DIFF": "Différence", - "CHECKOUT_LOOPEAT_ADD_CREDITS": "Créditer ma cagnotte", - "RESTAURANT_LOOPEAT_DISCLAIMER": "Changez le nombre d'emballages utilisés pour emballer la commande", - "ZERO_WASTE": "Zéro déchet", - "FEATURED": "À la une", - "EXCLUSIVE": "Exclusivités", - "NEW": "Nouveautés", - "RESTAURANT_LOOPEAT_UPDATE_FORMATS": "Modifier les emballages", - "FAILURE_REASON": "Raison de l'échec", - "CHECKOUT_LOOPEAT_CONNECT_ACCOUNT_TEXT": "Connectez votre compte {{name}} pour continuer, ou désactivez l'option zéro déchet", - "CHECKOUT_LOOPEAT_MISSING_AMOUNT": "Il manque {{amount}} pour finaliser votre commande", - "CHECKOUT_LOOPEAT_WALLET_AMOUNT": "Ma cagnotte ({{amount}})", - "CART_ZERO_WASTE_POPUP_TEXT": "Bravo !\nVotre panier contient un plat éligible à la livraison zéro-déchet avec notre partenaire « {{name}} ».\nFaites-vous livrer en boîtes consignées et retournez vos boîtes partout dans le réseau « {{name}} » ou à votre coursier à la prochaine commande.\nVous pouvez désactiver cette option à tout moment.", - "CART_ZERO_WASTE_POPUP_BUTTON_TEXT": "Jʼai compris", - "TERMS_OF_SERVICE": "Conditions générales", - "PRIVACY": "Confidentialité", - "RESTAURANT_MORE_INFOS": "En savoir plus", - "PRICE_EXCLUDING_TAX": "Prix hors taxes", - "PRICE_TOTAL": "Prix total", + "STORE_NEW_DELIVERY_ADDRESS_HELP": "Entrez l'adresse de dépôt pour vérifier la faisabilité", + "STORE_NEW_DELIVERY_PHONE_NUMBER": "Numéro de téléphone", + "STORE_NEW_DELIVERY_ENTER_PHONE_NUMBER": "Entrez un numéro de téléphone valide", + "STORE_NEW_DELIVERY_BUSINESS_NAME": "Nom de l'entreprise", + "STORE_NEW_DELIVERY_ENTER_BUSINESS_NAME": "Entrez le nom de l'entreprise", + "STORE_NEW_DELIVERY_CONTACT_NAME": "Contact", + "STORE_NEW_DELIVERY_ENTER_CONTACT_NAME": "Entrez le nom de la personne à contacter", + "STORE_NEW_DELIVERY_ADDRESS_DESCRIPTION": "Informations d'accès", + "STORE_NEW_DELIVERY_ENTER_ADDRESS_DESCRIPTION": "Indiquez toute information utile pour l'adresse indiquée (interphone…)", + "STORE_NEW_DELIVERY_COMMENTS": "Commentaires", + "STORE_NEW_DELIVERY_ENTER_COMMENTS": "Indiquez tous les détails pour finaliser la livraison (fragile, ...)", + "STORE_NEW_DELIVERY_WEIGHT": "Poids personnalisé", + "STORE_NEW_DELIVERY_ENTER_WEIGHT": "Entrez le poids du paquet", + "STORE_NEW_DELIVERY_PACKAGES": "Paquets", + "STORE_NEW_DELIVERY_NO_PACKAGES": "Aucun paquet", + "STORE_NEW_DELIVERY_DROPOFF_BEFORE": "À déposer avant", + "STORE_NEW_DELIVERY_TIME_SLOT": "Tranche horaire", + "STORE_NEW_DELIVERY_SELECT_TIME_SLOT": "Choisissez une tranche horaire", + "STORE_NEW_DELIVERY_ERROR": { + "EMPTY_TIME_SLOT": "Veuillez choisir une tranche horaire", + "EMPTY_CONTACT_NAME": "Veuillez fournir le nom d'une personne à contacter", + "EMPTY_PHONE_NUMBER": "Veuillez fournir un numéro de téléphone", + "EMPTY_WEIGHT": "Veillez fournir le poids du colis", + "EMPTY_PACKAGES": "Veillez sélectionner les paquets" + }, + "STORE_DELIVERY": "Livraison #{{id}}", + "DELIVERY_STATE": { + "undefined": "Inconnu", + "unknown": "Inconnu", + "new": "Nouveau", + "picked": "Récupérée", + "fulfilled": "Livrée" + }, + "DELIVERY_DETAILS_TIME_SLOT": "Tranche horaire", + "DELIVERY_DETAILS_RECIPIENT": "Destinataire", + "TODAY_BETWEEN_START_AND_END": "Aujourd'hui entre {{start}} et {{end}}", + "TOMORROW_BETWEEN_START_AND_END": "Demain entre {{start}} et {{end}}", + "OTHER_DAY_BETWEEN_START_AND_END": "{{date}} entre {{start}} et {{end}}", + "SIGNATURE_SCREEN_FIRST": "Écran de signature par défaut", + "CART_DELIVERY_TIME": "Livraison {{- fromNow}}", + "CART_DELIVERY_TIME_DIFF": "Livraison dans {{diff}} minutes", + "CART_COLLECTION_TIME": "Retrait {{- fromNow}}", + "CART_COLLECTION_TIME_DIFF": "Retrait dans {{diff}} minutes", + "TIME_DIFF_SHORT": "{{min}} - {{max}} minutes", + "FINISHED": "Terminé", + "SEARCH": "Rechercher…", + "GO_BACK_TO_RESTAURANTS": "Recommencer", + "SESSION_EXPIRED": "Votre session a expiré", + "TOUCH_TO_RELOAD": "Touchez pour réessayer", + "OFFLINE": "Vous êtes déconnecté·e", + "SCAN_FOR_PRINTERS": "Touchez l'écran pour scanner", + "SEARCH_WITH_ADDRESS": "Rechercher « {{address}} »", + "RESTAURANT_ORDER_CONNECT_PRINTER": "Connecter une imprimante", + "SWIPE_TO_ACCEPT_REFUSE": "Glissez vers la droite pour accepter, vers la gauche pour refuser", + "ADD_COUPON": "Ajouter un code promo", + "VOUCHER_CODE": "Code promo", + "DELIVERY_ASAP": "Au plus tôt", + "PHOTO_ADD": "Ajouter l'image", + "TASKS_CHANGED_ALERT_SOUND": "Son pour les notifications", + "RECEIPT_HEADING_ORDER_NUMBER": "COMMANDE {{number}} (#{{id}})", + "RECEIPT_CUSTOMER_NAME": "CLIENT: {{customer}}", + "RECEIPT_HEADING_PICKUP_EXPECTED_AT": "A {{time}}", + "RECEIPT_HEADING_PICKUP_EXPECTED_ON": "RETRAIT LE {{time}}", + "RECEIPT_HEADING_PICKUP_EXPECTED_TODAY": "RETRAIT AUJOURD'HUI", + "RESTRICTED_DIET": { + "DiabeticDiet": "Diabétique", + "GlutenFreeDiet": "Sans gluten", + "HalalDiet": "Halal", + "HinduDiet": "Hindou", + "KosherDiet": "Casher", + "LowCalorieDiet": "Faible calorie", + "LowFatDiet": "Sans graisses", + "LowLactoseDiet": "Sans lactose", + "LowSaltDiet": "Sans sel", + "VeganDiet": "Vegan", + "VegetarianDiet": "Végétarien" + }, + "ALLERGEN": { + "CEREALS_CONTAINING_GLUTEN": "Céréales contenant du gluten", + "CRUSTACEANS": "Crustacés", + "EGGS": "Oeufs", + "FISH": "Poisson", + "PEANUTS": "Arachides", + "SOYBEANS": "Soja", + "MILK": "Lait", + "NUTS": "Noix", + "CELERY": "Céleri", + "MUSTARD": "Moutarde", + "SESAME_SEEDS": "Graines de sésame", + "SULPHUR_DIOXIDE_SULPHITES": "Dioxyde de soufre", + "LUPIN": "Lupins", + "MOLLUSCS": "Mollusques" + }, + "FULFILLMENT_METHOD": { + "collection": "Retrait sur place", + "delivery": "Livraison" + }, + "CART_COLLECTION_DISCLAIMER": "Vous avez opté pour le retrait sur place de votre commande. Vous vous engagez à venir récupérer votre commande le jour et l'heure indiqués. \nEn cas de problème, contactez l'établissement au {{telephone}}. \nSi vous ne vous présentez pas, nous encaisserons la commande dans son intégralité. ", + "LEARN_MORE": "En savoir plus", + "SELECTED": "Selectionné", + "AVAILABLE": "Disponible", + "COMING_SOON": "Bientôt", + "CARDHOLDER_NAME": "Nom du titulaire de la carte", + "RESTAURANT_ORDER_LIST_PICKED_ORDERS": "Commandes récupérées ({{count}})", + "NOTIFICATION_TASKS_ADDED": "{{ count }} ajoutée(s)", + "NOTIFICATION_TASKS_REMOVED": "{{ count }} supprimée(s)", + "NOT_AVAILABLE_ATM": "Indisponible pour l'instant", + "BACKGROUND_PERMISSION_DISCLOSURE": { + "title": "Utilisation de votre position", + "message": "Afin de permettre aux répartiteurs d'attribuer les tâches avec précision, autorisez CoopCycle à accéder à la position de cet appareil même lorsque l'application fermée ou n'est pas utilisée.\n\nCes données seront téléversées sur le serveur CoopCycle auquel vous êtes connecté." + }, + "BACKGROUND_PERMISSION_RATIONALE": { + "title": "Permettre à {applicationName} d'accéder à la position de cet appareil même lorsque l'application fermée ou n'est pas utilisée", + "message": "Pour les utilisateurs authentifiés comme coursiers, cette application collecte les données de localisation afin de permettre aux répartiteurs d'attribuer les tâches avec précision", + "positiveAction": "Changer pour « {backgroundPermissionOptionLabel} »" + }, + "RESTAURANT_SETTINGS_MANAGE_PRODUCT_OPTIONS": "Gérer les options des produits", + "STORES": "Magasins", + "FILTER_BY_TAGS": "Filtrer par tags", + "CONNECT_WITH_FACEBOOK": "Se connecter avec Facebook", + "RESTAURANT_PRE_ORDER": "Pré-commander", + "RESTAURANT_CLOSED_BUT_OPENS": "Ce restaurant est fermé maintenant mais ouvre {{ datetime }}. Vous pouvez quand même commander pour plus tard !", + "RESTAURANT_CLOSED_PRE_ORDER": "Ce restaurant est fermé actuellement, vous pouvez tout même commander pour une livraison à {{ datetime }} !", + "RESTAURANT_CLOSED_AND_NOT_AVAILABLE": "Ce restaurant est fermé et non disponible jusqu'à {{ datetime }}.", + "CHECKOUT_AS_GUEST": "Commander en tant qu'invité", + "GUEST_CHECKOUT_ORDER_EMAIL_HELP": "Pour vous envoyer des mises à jour à propos de votre commande", + "GUEST_CHECK_EMAIL_FOR_ORDER_UPDATES": "Comme vous avez choisi de commander en tant qu'invité, veuillez vérifier vos e-mails pour suivre votre commande", + "MY_ORDER": "Ma commande", + "OR": "ou", + "total_packages": "Total : {{count}} paquet", + "total_packages_plural": "Total : {{count}} paquets", + "EMPTY_CARTS_TITLE": "Ajoutez des articles pour commencer un panier", + "EMPTY_CARTS_SUBTITLE": "Une fois que vous avez ajouté des articles, votre panier s'affiche ici.", + "GO_TO_SHOPPING": "Commander", + "SAVE_AND_CONTINUE": "Enregistrer et continuer", + "ENTER_NEW_ADDRESS": "Entrez votre nouvelle adresse", + "OPTION_REQUIRED": "Obligatoire", + "SOME_OPTION_IS_REQUIRED_SELECT_ONE_VALUE": "Il y a des options obligatoires, sélectionnez une valeur parmi celles-ci pour continuer", + "WHERE_ARE_YOU": "Adresse de livraison ?", + "ASK_ADDRESS_DISCLAIMER": "Nous utilisons votre adresse pour vous aider à trouver les meilleurs commerces à proximité.", + "TOP_RESULTS": "Top résultats", + "OTHER_RESULTS": "Autres résultats", + "RESULT": "{{count}} résultat", + "RESULT_plural": "{{count}} résultats", + "EMPTY_HERE": "C'est complètement vide par ici", + "CALL": "Appeler", + "HELP": "Aide", + "SHOW_DELIVERY_ZONE": "Afficher la zone de livraison", + "ORDER_TIMELINE_CREATED_TITLE": "Commande créée", + "ORDER_TIMELINE_ACCEPTED_TITLE": "Commande confirmée", + "ORDER_TIMELINE_REFUSED_TITLE": "Commande refusée", + "ORDER_TIMELINE_REFUSED_DESCRIPTION": "Votre commande ne peut pas être préparée par le restaurant", + "ORDER_TIMELINE_PICKED_TITLE": "Commande récupérée", + "ORDER_TIMELINE_DROPPED_TITLE": "Commande livrée", + "ORDER_TIMELINE_CANCELLED_TITLE": "Commande annulée", + "ORDER_TIMELINE_AFTER_ACCEPTED_TITLE": "Le restaurant prépare votre commande", + "ORDER_TIMELINE_AFTER_ACCEPTED_DESCRIPTION": "Nous vous tiendrons informé quand le livreur aura récupéré votre commande", + "ORDER_TIMELINE_AFTER_CREATED_TITLE": "Commande en attente de validation", + "ORDER_TIMELINE_AFTER_CREATED_DESCRIPTION": "Le restaurateur doit valider votre commande. Dans le cas où votre commande serait refusée vous ne serez pas débité", + "ORDER_TIMELINE_AFTER_PICKED_TITLE": "Votre livreur arrive !", + "ORDER_TIMELINE_AFTER_PICKED_DESCRIPTION": "Préparez-vous pour l'accueillir !", + "ORDER_ETA": "Arrivée estimée entre {{start}} et {{end}}", + "ORDER_ABOUT": "Informations de la commande", + "SHOW_ORDER_DETAILS": "Afficher le détail de la commande", + "SHARE_INVOICE": "Partager la facture", + "GENERATE_INVOICE": "Éditer la facture", + "BILLING_ADDRESS": "Adresse de facturation", + "TIP": "Pourboire", + "LEGAL_TEXTS_LABEL": "J'accepte les conditions générales et la politique de confidentialité", + "LEGAL_TEXTS_ERROR_MESSAGE": "Vous devez accepter les conditions générales et la politique de confidentialité", + "TERMS_AND_CONDITIONS_BUTTON_LABEL": "Lire les conditions générales", + "PRIVACY_POLICY_BUTTON_LABEL": "Lire la politique de confidentialité", + "TERMS_AND_CONDITIONS_LABEL": "J'accepte les conditions générales", + "TERMS_AND_CONDITIONS_ERROR_MESSAGE": "Vous devez accepter les conditions générales", + "PRIVACY_POLICY_LABEL": "J'accepte la politique de confidentialité", + "PRIVACY_POLICY_ERROR_MESSAGE": "Vous devez accepter la politique de confidentialité", + "AGREE": "J'accepte", + "DELETE_ACCOUNT": "Supprimer mon compte", + "DELETE_ACCOUNT_DISCLAIMER": "En cliquant sur le bouton ci-dessous, vous allez supprimer votre compte. Vous êtes sûr·e ?", + "PAY_WITH_SAVED_CREDIT_CARD": "Payer avec une carte bancaire sauvegardée", + "SELECT_SAVED_CARD_ERROR": "Veuillez sélectionner une carte bancaire pour payer", + "INVALID_CREDIT_CARD_ERROR": "Veuillez entrer des données de carte bancaire valides", + "INVALID_CARD_HOLDER_NAME_ERROR": "Veuillez entrer un titulaire de carte bancaire valide", + "CREDIT_CARD_EXPIRATION": "Expiration", + "ADD_NEW_CREDIT_CARD": "Ajouter une nouvelle carte bancaire", + "SELECT_SAVED_CARD": "Sélectionner une carte bancaire sauvegardée", + "SAVE_CARD": "Sauvegarder cette carte bancaire", + "SEARCH_TAB": "Rechercher", + "SEARCH_INPUT_PLACEHOLDER": "Une boutique, un plat…", + "SEARCH_SHOPS": "Boutiques", + "SEARCH_PRODUCTS": "Produits", + "SEARCH_PRODUCT_ITEM_SUBTITLE": "Dans {{shop}}", + "SEARCH_WITHOUT_RESULTS": "Aucun résultat. Essayez de chercher autre chose.", + "APPLE_SIGN_IN_HIDE_MY_EMAIL_ERROR": "Désolé, nous ne pouvons pas vous authentifier si vous choisissez de masquer votre e-mail", + "SOCIAL_SIGN_IN_UNKNOWN_EMAIL": "Désolé, nous nʼavons pas trouvé de compte correspondant à l'e-mail associé à votre compte {{provider}}.", + "CHECKOUT_LOOPEAT_CONNECT_ACCOUNT": "Connecter mon compte {{name}}", + "CHECKOUT_LOOPEAT_OPTION_ENABLED": "Gérer ma consigne", + "CHECKOUT_LOOPEAT_INSUFFICIENT_WALLET_AMOUNT": "Cagnotte consigne insuffisante", + "CHECKOUT_LOOPEAT_VALIDATE": "Valider et retourner au panier", + "CHECKOUT_LOOPEAT_RETURN_CONTAINERS": "Rendre des boîtes", + "CHECKOUT_LOOPEAT_RETURN_CONTAINER": "Rendre la boîte", + "CHECKOUT_LOOPEAT_WALLET": "Cagnotte", + "CHECKOUT_LOOPEAT_TOTAL_RETURNS": "Total retours", + "CHECKOUT_LOOPEAT_DIFF": "Différence", + "CHECKOUT_LOOPEAT_ADD_CREDITS": "Créditer ma cagnotte", + "RESTAURANT_LOOPEAT_DISCLAIMER": "Changez le nombre d'emballages utilisés pour emballer la commande", + "ZERO_WASTE": "Zéro déchet", + "FEATURED": "À la une", + "EXCLUSIVE": "Exclusivités", + "NEW": "Nouveautés", + "RESTAURANT_LOOPEAT_UPDATE_FORMATS": "Modifier les emballages", + "FAILURE_REASON": "Raison de l'échec", + "CHECKOUT_LOOPEAT_CONNECT_ACCOUNT_TEXT": "Connectez votre compte {{name}} pour continuer, ou désactivez l'option zéro déchet", + "CHECKOUT_LOOPEAT_MISSING_AMOUNT": "Il manque {{amount}} pour finaliser votre commande", + "CHECKOUT_LOOPEAT_WALLET_AMOUNT": "Ma cagnotte ({{amount}})", + "CART_ZERO_WASTE_POPUP_TEXT": "Bravo !\nVotre panier contient un plat éligible à la livraison zéro-déchet avec notre partenaire « {{name}} ».\nFaites-vous livrer en boîtes consignées et retournez vos boîtes partout dans le réseau « {{name}} » ou à votre coursier à la prochaine commande.\nVous pouvez désactiver cette option à tout moment.", + "CART_ZERO_WASTE_POPUP_BUTTON_TEXT": "Jʼai compris", + "TERMS_OF_SERVICE": "Conditions générales", + "PRIVACY": "Confidentialité", + "RESTAURANT_MORE_INFOS": "En savoir plus", + "PRICE_EXCLUDING_TAX": "Prix hors taxes", + "PRICE_TOTAL": "Prix total", "PRICE_CALCULATION_FAILED": "Impossible de calculer le prix", "PRICE_CALCULATION_FAILED_DISCLAIMER": "Le prix n’a pas pu être calculé, veuillez nous contacter pour résoudre le problème.", - "HIDE_INCIDENTS_TASKS": "Cacher les incidents", - "CART_TIME_RANGE_CHANGED_MODAL_TITLE": "Le créneau horaire sélectionné nʼest plus disponible pour aujourdʼhui", - "CART_TIME_RANGE_CHANGED_MODAL_MESSAGE": "Les horaires dʼouverture ne permettent pas de commander pour aujourdʼhui", - "CART_TIME_RANGE_CHANGED_MODAL_CHOOSE_TIME_RANGE_TEXT": "Sélectionnez un autre créneau horaire", - "CART_TIME_RANGE_CHANGED_MODAL_SELECT_TIME_RANGE_ACTION": "Modifier le créneau horaire", - "CART_TIME_RANGE_CHANGED_MODAL_CHOOSE_RESTAURANT_ACTION": "Choisir un autre restaurant", - "ORDER__SHIPPED_AT__NOT_AVAILABLE": "L'horaire de livraison n'est plus disponible", - "ORDER__SHIPPED_AT__EXPIRED": "L'horaire de livraison est dépassé", - "ORDER__SHIPPING_TIME_RANGE__NOT_AVAILABLE": "Indisponible pour l'instant", - "EDENRED_ELIGIBLE_AMOUNT": "Montant éligible Ticket Restaurant®", - "EDENRED_COMPLEMENT": "Complément" - } + "HIDE_INCIDENTS_TASKS": "Cacher les incidents", + "CART_TIME_RANGE_CHANGED_MODAL_TITLE": "Le créneau horaire sélectionné nʼest plus disponible pour aujourdʼhui", + "CART_TIME_RANGE_CHANGED_MODAL_MESSAGE": "Les horaires dʼouverture ne permettent pas de commander pour aujourdʼhui", + "CART_TIME_RANGE_CHANGED_MODAL_CHOOSE_TIME_RANGE_TEXT": "Sélectionnez un autre créneau horaire", + "CART_TIME_RANGE_CHANGED_MODAL_SELECT_TIME_RANGE_ACTION": "Modifier le créneau horaire", + "CART_TIME_RANGE_CHANGED_MODAL_CHOOSE_RESTAURANT_ACTION": "Choisir un autre restaurant", + "ORDER__SHIPPED_AT__NOT_AVAILABLE": "L'horaire de livraison n'est plus disponible", + "ORDER__SHIPPED_AT__EXPIRED": "L'horaire de livraison est dépassé", + "ORDER__SHIPPING_TIME_RANGE__NOT_AVAILABLE": "Indisponible pour l'instant", + "EDENRED_ELIGIBLE_AMOUNT": "Montant éligible Ticket Restaurant®", + "EDENRED_COMPLEMENT": "Complément" + } } diff --git a/src/navigation/checkout/Restaurant.js b/src/navigation/checkout/Restaurant.js index 584e81cb1..061f1f02f 100644 --- a/src/navigation/checkout/Restaurant.js +++ b/src/navigation/checkout/Restaurant.js @@ -221,7 +221,7 @@ function Restaurant(props) { {i18n.t('RESTAURANT_OPENING_HOURS')} { export const TimingBadge = ({ restaurant }) => { const color = useBaseTextColor(); - const isAvailable = isRestaurantAvailable(restaurant); - const shippingTime = getNextShippingTimeAsText(restaurant); + const isAvailable = isRestaurantOrderingAvailable(restaurant); const showPreOrder = shouldShowPreOrder(restaurant); + const shippingTime = getNextShippingTimeAsText(restaurant); return ( diff --git a/src/navigation/checkout/components/RestaurantProfile.js b/src/navigation/checkout/components/RestaurantProfile.js index 63fccc1c2..c8b1e6c16 100644 --- a/src/navigation/checkout/components/RestaurantProfile.js +++ b/src/navigation/checkout/components/RestaurantProfile.js @@ -15,8 +15,8 @@ import DangerAlert from '../../../components/DangerAlert'; import i18n from '../../../i18n'; import moment from 'moment/moment'; import { - isRestaurantAvailable, - isRestaurantOpeningSoon, + getNextShippingTime, + isRestaurantOrderingAvailable, shouldShowPreOrder, } from '../../../utils/checkout'; import { RestaurantBanner } from '../../../components/RestaurantBanner'; @@ -96,17 +96,26 @@ const styles = StyleSheet.create({ }); function OpeningHoursWarning({ - currentTimeSlot, - isAvailable, - isOpeningSoon, + openingHoursSpecification, + isOrderingAvailable, showPreOrder, + nextShippingTime, }) { - if (showPreOrder) { + if (!isOrderingAvailable) { + //FIXME: this is based on the regular opening hours + // and does not take closing rules ("Holidays") into account + const nextOpeningHours = openingHoursSpecification.currentTimeSlot.timeSlot; + + if (!nextOpeningHours) { + // when restaurant is disabled it will be shown on the banner + // and in the 'timing' section + return null; + } + return ( ; } @@ -152,28 +161,26 @@ function RestaurantProfile({ restaurant, openingHoursSpecification, onInfo }) { const stroke = useBaseTextColor(); const textSecondary = useSecondaryTextColor(); - const currentTimeSlot = useMemo( - () => openingHoursSpecification.currentTimeSlot, - [openingHoursSpecification], - ); - - const isAvailable = useMemo( - () => isRestaurantAvailable(restaurant), - [restaurant], - ); - const isOpeningSoon = useMemo( - () => isRestaurantOpeningSoon(restaurant), + const isOrderingAvailable = useMemo( + () => isRestaurantOrderingAvailable(restaurant), [restaurant], ); const showPreOrder = useMemo( () => shouldShowPreOrder(restaurant), [restaurant], ); + const nextShippingTime = useMemo( + () => getNextShippingTime(restaurant), + [restaurant], + ); return ( - + @@ -200,10 +207,10 @@ function RestaurantProfile({ restaurant, openingHoursSpecification, onInfo }) { diff --git a/src/navigation/checkout/components/TimeRangeChangedModal.js b/src/navigation/checkout/components/TimeRangeChangedModal.js index 6e5525e52..d26fc4b23 100644 --- a/src/navigation/checkout/components/TimeRangeChangedModal.js +++ b/src/navigation/checkout/components/TimeRangeChangedModal.js @@ -20,6 +20,7 @@ import { useGetOrderTimingQuery } from '../../../redux/api/slice'; import { useNavigation } from '@react-navigation/native'; import { useIsModalVisible } from '../../../hooks/useIsModalVisible'; import tracker from '../../../analytics/Tracker'; +import { DatadogLogger } from '../../../Datadog' const styles = StyleSheet.create({ modalContent: { @@ -140,6 +141,7 @@ export default function TimeRangeChangedModal() { isVisible={isModalVisible} onModalHide={onModalHide} onModalShow={() => { + DatadogLogger.info('TimeRangeChangedModal opened'); tracker.logEvent('Checkout', 'openTimeRangeChangedModal'); }}> state.app.isInternetReachable, ); const isLoading = useSelector(selectIsLoading); - const isCentrifugoConnected = useSelector(selectIsCentrifugoConnected); const { navigate } = navigation; @@ -65,12 +63,6 @@ export default function DashboardPage({ navigation, route }) { }; }, []); - useEffect(() => { - if (!isCentrifugoConnected) { - dispatch(connectCentrifugo()); - } - }, [dispatch, isCentrifugoConnected]); - useEffect(() => { if (route.params?.loadOrders ?? true) { dispatch( @@ -178,7 +170,7 @@ export default function DashboardPage({ navigation, route }) { } /> )} - + ( - - - {connected ? t('WAITING_FOR_ORDER') : t('CONN_LOST')} - - - -); +const WebSocketIndicator = () => { + const connecting = useSelector(selectIsCentrifugoConnecting); + const connected = useSelector(selectIsCentrifugoConnected); + + const dispatch = useDispatch(); + + const { t } = useTranslation(); + + return ( + + + {connected + ? t('WAITING_FOR_ORDER') + : connecting + ? t('CONN_LOST') + : t('CONN_LOST_IDLE')} + + {connected ? ( + + ) : null} + {!connected && !connecting ? ( + + ) : null} + + ); +}; const styles = StyleSheet.create({ container: { @@ -41,4 +71,4 @@ const styles = StyleSheet.create({ }, }); -export default withTranslation()(WebSocketIndicator); +export default WebSocketIndicator; diff --git a/src/redux/App/__tests__/reducers.test.js b/src/redux/App/__tests__/reducers.test.js index a4e65d2c5..900cda30b 100644 --- a/src/redux/App/__tests__/reducers.test.js +++ b/src/redux/App/__tests__/reducers.test.js @@ -1,14 +1,14 @@ import { omit } from 'lodash'; import { - connected, - disconnected, + centrifugoConnected, + centrifugoDisconnected, } from '../../middlewares/CentrifugoMiddleware'; import reducer from '../reducers'; describe('Redux | App | Reducers', () => { - test(`${connected}`, () => { + test(`${centrifugoConnected}`, () => { const initialState = reducer(undefined, {}); - const newState = reducer(initialState, connected()); + const newState = reducer(initialState, centrifugoConnected()); const restOldState = omit(initialState, ['isCentrifugoConnected']); const restNewState = omit(newState, ['isCentrifugoConnected']); @@ -21,12 +21,12 @@ describe('Redux | App | Reducers', () => { expect(restNewState).toEqual(restOldState); }); - test(`${disconnected}`, () => { + test(`${centrifugoDisconnected}`, () => { const initialState = { ...reducer(undefined, {}), isCentrifugoConnected: true, }; - const newState = reducer(initialState, disconnected()); + const newState = reducer(initialState, centrifugoDisconnected()); const restOldState = omit(initialState, ['isCentrifugoConnected']); const restNewState = omit(newState, ['isCentrifugoConnected']); diff --git a/src/redux/App/actions.js b/src/redux/App/actions.js index b515c772b..f8fa659bc 100644 --- a/src/redux/App/actions.js +++ b/src/redux/App/actions.js @@ -174,6 +174,8 @@ const loadMyRestaurantsFailure = createFsAction(LOAD_MY_RESTAURANTS_FAILURE); const setSettings = createFsAction(SET_SETTINGS); +export const appStateChanged = createAction('@app/APP_STATE_CHANGED'); + export const setInternetReachable = createFsAction(SET_INTERNET_REACHABLE); export const setBackgroundGeolocationEnabled = createFsAction( diff --git a/src/redux/App/reducers.js b/src/redux/App/reducers.js index dcf3509c1..fc1a43bd4 100644 --- a/src/redux/App/reducers.js +++ b/src/redux/App/reducers.js @@ -2,13 +2,13 @@ /* * App reducer, dealing with non-domain specific state */ - +import { AppState } from 'react-native'; import Config from 'react-native-config'; import { - CENTRIFUGO_MESSAGE, - CONNECTED, - DISCONNECTED, + centrifugoConnected, + centrifugoDisconnected, + connectCentrifugo, } from '../middlewares/CentrifugoMiddleware'; import { @@ -54,6 +54,7 @@ import { SET_SERVERS, SET_SETTINGS, SET_USER, + appStateChanged, setSpinnerDelayEnabled, } from './actions'; @@ -68,6 +69,7 @@ const initialState = { httpClient: null, user: null, currentRoute: null, + appState: AppState.currentState, pushNotificationToken: null, pushNotificationTokenSaved: null, loading: false, @@ -95,6 +97,7 @@ const initialState = { loginByEmailErrors: {}, isBackgroundGeolocationEnabled: false, hasDisclosedBackgroundPermission: false, + isCentrifugoConnecting: false, isCentrifugoConnected: false, modal: { show: false, @@ -170,15 +173,28 @@ export default (state = initialState, action = {}) => { loading: action.payload, }; - case CONNECTED: + case connectCentrifugo.type: { + if (state.isCentrifugoConnected) { + return state; + } + + return { + ...state, + isCentrifugoConnecting: true, + }; + } + + case centrifugoConnected.type: return { ...state, + isCentrifugoConnecting: false, isCentrifugoConnected: true, }; - case DISCONNECTED: + case centrifugoDisconnected.type: return { ...state, + isCentrifugoConnecting: false, isCentrifugoConnected: false, }; @@ -449,6 +465,11 @@ export default (state = initialState, action = {}) => { isSpinnerDelayEnabled: action.payload, }; + case appStateChanged.type: + return { + ...state, + appState: action.payload, + }; } return state; diff --git a/src/redux/App/selectors.js b/src/redux/App/selectors.js index 70b5c02d8..1e747493a 100644 --- a/src/redux/App/selectors.js +++ b/src/redux/App/selectors.js @@ -72,6 +72,9 @@ export const selectIsLoading = createSelector( }, ); +export const selectIsCentrifugoConnecting = state => + state.app.isCentrifugoConnecting; + export const selectIsCentrifugoConnected = state => state.app.isCentrifugoConnected; diff --git a/src/redux/Dispatch/actions.js b/src/redux/Dispatch/actions.js index 0f7cb37a6..3ee6f715a 100644 --- a/src/redux/Dispatch/actions.js +++ b/src/redux/Dispatch/actions.js @@ -5,7 +5,7 @@ import { createAction } from 'redux-actions'; import _ from 'lodash'; import NavigationHolder from '../../NavigationHolder'; import i18n from '../../i18n'; -import { connect } from '../middlewares/CentrifugoMiddleware/actions'; +import { connectCentrifugo } from '../middlewares/CentrifugoMiddleware/actions'; import { createTaskListFailure, @@ -191,7 +191,7 @@ export function initialize() { dispatch(loadUsersSuccess(users['hydra:member'])); dispatch(loadUnassignedTasksSuccess(unassignedTasks['hydra:member'])); dispatch(loadTaskListsSuccess(taskLists['hydra:member'])); - dispatch(connect()); + dispatch(connectCentrifugo()); dispatch(_initialize()); }) .catch(e => dispatch(loadUnassignedTasksFailure(e))); diff --git a/src/redux/Restaurant/__tests__/middlewares.test.js b/src/redux/Restaurant/__tests__/middlewares.test.js index e6d7df7b1..e83ac2ece 100644 --- a/src/redux/Restaurant/__tests__/middlewares.test.js +++ b/src/redux/Restaurant/__tests__/middlewares.test.js @@ -8,12 +8,6 @@ import { notifyOnNewOrderCreated } from '../middlewares'; import restaurantReducer from '../reducers'; describe('notifyOnNewOrderCreated', () => { - beforeEach(() => { - jest.mock('react-native/Libraries/AppState/AppState', () => ({ - currentState: 'active', - })); - }); - it('does nothing with action type "LOAD_ORDERS_SUCCESS"', () => { const preloadedState = { app: { diff --git a/src/redux/middlewares/AppStateMiddleware/index.js b/src/redux/middlewares/AppStateMiddleware/index.js new file mode 100644 index 000000000..9d6d2d553 --- /dev/null +++ b/src/redux/middlewares/AppStateMiddleware/index.js @@ -0,0 +1,20 @@ +import { AppState } from 'react-native'; +import { appStateChanged } from '../../App/actions'; + +let initialized = false; +let appState = AppState.currentState; + +export default ({ getState, dispatch }) => { + return next => action => { + if (!initialized) { + AppState.addEventListener('change', nextAppState => { + if (appState !== nextAppState) { + appState = nextAppState; + dispatch(appStateChanged(nextAppState)); + } + }); + initialized = true; + } + return next(action); + }; +}; diff --git a/src/redux/middlewares/CentrifugoMiddleware/actions.js b/src/redux/middlewares/CentrifugoMiddleware/actions.js index e19bb5e2a..ca285a8f6 100644 --- a/src/redux/middlewares/CentrifugoMiddleware/actions.js +++ b/src/redux/middlewares/CentrifugoMiddleware/actions.js @@ -1,16 +1,13 @@ -import { createAction } from 'redux-actions'; - +import { createAction } from '@reduxjs/toolkit'; import { updateTask } from '../../Dispatch/actions'; -export const CONNECT = '@centrifugo/CONNECT'; export const CENTRIFUGO_MESSAGE = '@centrifugo/MESSAGE'; -export const CONNECTED = '@centrifugo/CONNECTED'; -export const DISCONNECTED = '@centrifugo/DISCONNECTED'; -export const connect = createAction(CONNECT); +export const connectCentrifugo = createAction('@centrifugo/CONNECT'); +export const disconnectCentrifugo = createAction('@centrifugo/DISCONNECT'); -export const connected = createAction(CONNECTED); -export const disconnected = createAction(DISCONNECTED); +export const centrifugoConnected = createAction('@centrifugo/CONNECTED'); +export const centrifugoDisconnected = createAction('@centrifugo/DISCONNECTED'); export const _message = createAction(CENTRIFUGO_MESSAGE); diff --git a/src/redux/middlewares/CentrifugoMiddleware/index.js b/src/redux/middlewares/CentrifugoMiddleware/index.js index f02cd3a23..9b85570fe 100644 --- a/src/redux/middlewares/CentrifugoMiddleware/index.js +++ b/src/redux/middlewares/CentrifugoMiddleware/index.js @@ -3,11 +3,10 @@ import parseUrl from 'url-parse'; import { CENTRIFUGO_MESSAGE, - CONNECT, - CONNECTED, - DISCONNECTED, - connected, - disconnected, + centrifugoConnected, + centrifugoDisconnected, + connectCentrifugo, + disconnectCentrifugo, message, } from './actions'; @@ -16,81 +15,147 @@ import { selectHttpClient, selectHttpClientHasCredentials, selectIsAuthenticated, + selectLoggedInUser, selectUser, } from '../../App/selectors'; -import { LOGOUT_SUCCESS } from '../../App/actions'; +import { LOGOUT_SUCCESS, appStateChanged } from '../../App/actions'; -const isCentrifugoAction = ({ type }) => [CONNECT].some(x => x === type); +function shouldManageConnectionBasedOnAppState(getState, action) { + const state = getState(); -export default ({ getState, dispatch }) => { - let centrifuge = null; + const user = selectLoggedInUser(state); + + if (!user) { + return false; + } + + // For now this method is used only for restaurant users + return user.hasRole('ROLE_RESTAURANT'); +} + +function shouldConnectBasedOnAppState(getState, action) { + if (!shouldManageConnectionBasedOnAppState(getState, action)) { + return false; + } + + if (action.type !== appStateChanged.type) { + return false; + } + + return action.payload === 'active'; +} + +function shouldDisconnectBasedOnAppState(getState, action) { + if (!shouldManageConnectionBasedOnAppState(getState, action)) { + return false; + } + + if (action.type !== appStateChanged.type) { + return false; + } + + return action.payload !== 'active'; +} + +let centrifuge = null; +let subscription = null; +export default ({ getState, dispatch }) => { return next => action => { if (action.type === LOGOUT_SUCCESS) { - const result = next(action); + dispatch(disconnectCentrifugo()); + return next(action); + } + + if ( + action.type === connectCentrifugo.type || + shouldConnectBasedOnAppState(getState, action) + ) { + const state = getState(); + + if ( + !selectIsAuthenticated(state) || + !selectHttpClientHasCredentials(state) + ) { + return next(action); + } if (centrifuge && centrifuge.isConnected()) { - centrifuge.disconnect(); - centrifuge = null; + return next(action); } - return result; - } + const httpClient = selectHttpClient(state); + const baseURL = selectBaseURL(state); + const user = selectUser(state); + + httpClient.get('/api/centrifugo/token').then(tokenResponse => { + const url = parseUrl(baseURL); + const protocol = url.protocol === 'https:' ? 'wss' : 'ws'; + + centrifuge = new Centrifuge( + `${protocol}://${url.hostname}/centrifugo/connection/websocket`, + { + debug: __DEV__, + onRefresh: function (ctx, cb) { + httpClient + .post('/api/centrifugo/token/refresh') + .then(refreshResponse => { + // @see https://github.com/centrifugal/centrifuge-js#refreshendpoint + // Data must be like {"status": 200, "data": {"token": "JWT"}} - see + // type definitions in dist folder. Note that setting status to 200 is + // required at moment. Any other status will result in refresh process + // failure so client will eventually be disconnected by server. + cb({ status: 200, data: { token: refreshResponse.token } }); + }); + }, + }, + ); + + centrifuge.setToken(tokenResponse.token); + + centrifuge.on('connect', context => + dispatch(centrifugoConnected(context)), + ); + centrifuge.on('disconnect', context => + dispatch(centrifugoDisconnected(context)), + ); + + subscription = centrifuge.subscribe( + `${tokenResponse.namespace}_events#${user.username}`, + msg => dispatch(message(msg.data.event)), + ); + + centrifuge.connect(); + }); - if (!isCentrifugoAction(action)) { return next(action); } - const state = getState(); - if ( - !selectIsAuthenticated(state) || - !selectHttpClientHasCredentials(state) + action.type === disconnectCentrifugo.type || + shouldDisconnectBasedOnAppState(getState, action) ) { + if (subscription) { + subscription.unsubscribe(); + subscription.removeAllListeners(); + subscription = null; + } + + if (centrifuge && centrifuge.isConnected()) { + centrifuge.disconnect(); + centrifuge = null; + } return next(action); } - const httpClient = selectHttpClient(state); - const baseURL = selectBaseURL(state); - const user = selectUser(state); - - httpClient.get('/api/centrifugo/token').then(tokenResponse => { - const url = parseUrl(baseURL); - const protocol = url.protocol === 'https:' ? 'wss' : 'ws'; - - centrifuge = new Centrifuge( - `${protocol}://${url.hostname}/centrifugo/connection/websocket`, - { - debug: __DEV__, - onRefresh: function (ctx, cb) { - httpClient - .post('/api/centrifugo/token/refresh') - .then(refreshResponse => { - // @see https://github.com/centrifugal/centrifuge-js#refreshendpoint - // Data must be like {"status": 200, "data": {"token": "JWT"}} - see - // type definitions in dist folder. Note that setting status to 200 is - // required at moment. Any other status will result in refresh process - // failure so client will eventually be disconnected by server. - cb({ status: 200, data: { token: refreshResponse.token } }); - }); - }, - }, - ); - centrifuge.setToken(tokenResponse.token); - - centrifuge.on('connect', context => dispatch(connected(context))); - centrifuge.on('disconnect', context => dispatch(disconnected(context))); - - centrifuge.subscribe( - `${tokenResponse.namespace}_events#${user.username}`, - msg => dispatch(message(msg.data.event)), - ); - - centrifuge.connect(); - }); - return next(action); }; }; -export { CENTRIFUGO_MESSAGE, CONNECTED, DISCONNECTED, connected, disconnected }; +export { + CENTRIFUGO_MESSAGE, + centrifugoConnected, + centrifugoDisconnected, + connectCentrifugo, + disconnectCentrifugo, +}; diff --git a/src/redux/store.js b/src/redux/store.js index 9e8094634..03280342c 100644 --- a/src/redux/store.js +++ b/src/redux/store.js @@ -21,11 +21,13 @@ import { notifyOnNewOrderCreated } from './Restaurant/middlewares'; import { apiSlice } from './api/slice'; import { setupListeners } from '@reduxjs/toolkit/query'; import { setupListenersReactNative } from './setupListenersReactNative'; +import AppStateMiddleware from './middlewares/AppStateMiddleware'; const middlewares = [ thunk, ReduxAsyncQueue, NetInfoMiddleware, + AppStateMiddleware, HttpMiddleware, apiSlice.middleware, PushNotificationMiddleware, diff --git a/src/utils/checkout.js b/src/utils/checkout.js index 3e80f8772..bfd650a93 100644 --- a/src/utils/checkout.js +++ b/src/utils/checkout.js @@ -11,13 +11,6 @@ function round5(x) { } function timingAsText(timing, now) { - // FIXME - // This hotfixes a bug on the API - // https://github.com/coopcycle/coopcycle-web/issues/2213 - if (timing.range[0] === timing.range[1]) { - return i18n.t('NOT_AVAILABLE_ATM'); - } - const lower = moment.parseZone(timing.range[0]); if (timing.fast) { @@ -43,18 +36,38 @@ function timingAsText(timing, now) { return lower.calendar(now); } +export function getNextShippingTime(restaurant) { + + const timing = restaurant.timing.delivery || restaurant.timing.collection; + + if (!timing) { + return null; + } + + // FIXME + // This hotfixes a bug on the API + // https://github.com/coopcycle/coopcycle-web/issues/2213 + if (!Array.isArray(timing.range)) { + return null; + } + if (timing.range[0] === timing.range[1]) { + return null; + } + + return timing; +} + + export function getNextShippingTimeAsText(restaurant, now) { now = now || moment(); - if (restaurant.timing.delivery) { - return timingAsText(restaurant.timing.delivery, now); - } + const timing = getNextShippingTime(restaurant); - if (restaurant.timing.collection) { - return timingAsText(restaurant.timing.collection, now); + if (!timing) { + return i18n.t('NOT_AVAILABLE_ATM'); } - return i18n.t('NOT_AVAILABLE_ATM'); + return timingAsText(timing, now); } export function getRestaurantCaption(restaurant) { @@ -65,23 +78,9 @@ export function getRestaurantCaption(restaurant) { * While the restaurant might be available (for ordering) * it might be either opened or closed at the moment */ -export function isRestaurantAvailable(restaurant) { - if (!restaurant.timing.delivery && !restaurant.timing.collection) { - return false; - } - - // FIXME - // This hotfixes a bug on the API - // https://github.com/coopcycle/coopcycle-web/issues/2213 - if ( - restaurant.timing.delivery && - Array.isArray(restaurant.timing.delivery.range) && - restaurant.timing.delivery.range[0] === restaurant.timing.delivery.range[1] - ) { - return false; - } - - return true; +export function isRestaurantOrderingAvailable(restaurant) { + const timing = getNextShippingTime(restaurant); + return Boolean(timing); } /** @@ -105,7 +104,7 @@ export function isRestaurantOpeningSoon(restaurant) { * If the pre-order is soon, we show a regular order button */ export function shouldShowPreOrder(restaurant) { - if (!isRestaurantAvailable(restaurant)) { + if (!isRestaurantOrderingAvailable(restaurant)) { return false; }