WFM Translations (Multi-Language Support)

From 3B Knowledge
Jump to navigation Jump to search

Intro

Version 8.0 introduces support for translating (and overriding) labels for all WFM components. This article will be kept up to date as more labels are added.

Setup

Setting up translations is done through the App Translation custom metadata. When you need to add translations to an app/component, you need to create an instance in this metadata.

Fields

The fields in the Metadata have the following purpose:

Label

No purpose for the application, use this to distinguish your translations. Recommended: follow naming convention {App_Name} - {Locale} e.g. Scheduling - ES

JSON

This is where you will add your json. If you are using IDE/Gearset/Copado, wrap the JSON in <![CDATA[{...}]]> so it is easily readable

Type

This is the application target.

Language Code

A combination of ISO 639-1 language code and ISO 3166-1 country code. You can modify the options available in the picklist.

Adding Translations

To add translation, say in Spanish, you need to crate a new JSON object that combines the global labels with the app labels. The top level key of the object must match the Language Code you selected against the metadata (e.g. es-ES for Spanish)

{
    "es-ES": {
        "common": {
            ...
        },
        "settingsPanel":{
            ...
        },
        ...
    }
}

Adding Overrides

You can override default English translations by defining App Translation record for the en-GB locale with a JSON object with a key en-GB.

App/Scheduler Configuration

From version 8.100.3, you can set the following top-level properties to the App or Scheduler configuration:

defaultLanguage - string - the default language the app will load with

availableLanguages - array strings - the available languages to the application, regardless of App Translation records

{
    "defaultLanguage": "en-US",
    "availableLanguages":["en-US", "bg-BG"],
    "timezonePreference": "client",
    ...
    "schedulables": {
        ...
    }
}

Hiding Language Switch

The language switch drop down can be hidden from the hide-language-switch property on the app component. The app language can be set from the language property.

<x-timesheet-submission 
            hide-theme-switch="false"
            hide-current-user="false"
            language="en-US"
            hide-language-switch="false"
            ...

You can set these peroperties using component configuration in Portals or via URL parameters (where available) like so:

...&hideLanguageSwitch=true&language=en-US

Adding US Override

Often, you might need to override a default translation or even set up a US locale translation. To do so, you will need two things:

  1. From version 8.100.3, you can set the "defaultLanguage" property in the app/scheduler definition. Set this to "en-US"
  2. Add the "en-US" picklist value to the App Translation => "Language Code" field (if not available)
  3. Either hide the language switch by setting the hide-language-switch property to your component embed (if available) or set the "availableLanguages" property in the app/scheduler definition to an array that only contains one option (e.g. ["en-US"])
  4. Add new App Translation, set the Language Code to "en-US" and the Type to your target application. The JSON body of the app translation should be a JSON object spliced from the specific app's translation and the global translations object (see below, or find your specific version's translation). The top level should be set to "en-US".

Below is an example of a en-US app translation for the Timesheet Submission application (version 8.100)

Definition

{
    ...
    "defaultLanguage": "en-US",
    "availableLanguages": ["en-US"],
    "schedulables": {
        ...
    }
}

App Translation

{
    "en-US": {
        "errors": {
            "messages": {
                "startFailed": "App failed to start 👹",
                "dataLoadingFailed": "There was an error loading the necessary data 😵‍💫"
            }
        },
        "tabs": {
            "shifts": "Shifts",
            "expenses": "Expenses"
        },
        "forms": {
            "approval": "Approval",
            "rejection": "Rejection",
            "submitForApproval": "Submit for Approval",
            "weekSubmissionDetail": {
                "zero": "No Records",
                "one": "Week %{startDate} - %{endDate} (1 record)",
                "other": "Week %{startDate} - %{endDate} (%{count} records)"
            }
        },
        "actions": {
            "approve": "Approve",
            "reject": "Reject",
            "approveAll": "Approve All",
            "rejectAll": "Reject All"
        },
        "shift": {
            "actions": {
                "submitForApprovalLabel": "Submit Week for Approval"
            }
        },
        "expense": {
            "actions": {
                "submitForApprovalLabel": "Submit Week for Approval"
            }
        },
        "common": {
            "attribution": {
                "poweredBy": "Powered by",
                "brand": "3B",
                "brandLink": "https://3b4sf.com"
            },
            "errors": {
                "unknownError": "There was an unknown error",
                "serverError": "There was a server error: %{error}",
                "authFailed": "Authorization Failed: %{error}",
                "apexAccessError": "Error with the %{controller}. Make sure you have set up access correctly.",
                "sessionExpired": "The session has expired",
                "fetchFailed": "Fetch Failed: %{error}",
                "upsertError": "There was an error in processing your request. %{error}",
                "queryError": "There was an error in retreiving the data for your request. %{error}",
                "deleteError": "There was an error in your request to delete these records. %{error}",
                "describeError": "There was an error in describing the database. %{error}",
                "queryWithFilterError": "Query filter cannot include the limit directive",
                "missingDescribe": "Database describe was unsuccessful",
                "missingRequiredArgument": "Missing required argument [%{type}]",
                "fileUploadFailed": "File upload failed"
            },
            "actions": {
                "edit": "Edit",
                "editing": "Editing",
                "save": "Save",
                "saving": "Saving..",
                "cancel": "Cancel",
                "canceling": "Canceling..",
                "close": "Close",
                "closing": "Closing..",
                "delete": "Delete",
                "deleting": "Deleting..",
                "select": "Select",
                "unSelect": "Un-Select",
                "selectAll": "Select All",
                "selectNone": "Select None"
            },
            "fields": {
                "number": {
                    "requiredMsg": "This is required",
                    "min": "Minimum is %{min}",
                    "max": "Maximum is %{max}"
                },
                "text": {
                    "placeholder": "",
                    "requiredMsg": "This is required",
                    "tooLong": "Please shorten this text to %{maxLength} or less",
                    "tooShort": "Please enter %{minLength} characters or more",
                    "typeNotValid": "This is not a valid %{type}"
                },
                "textArea": {
                    "placeholder": "",
                    "requiredMsg": "This is required",
                    "tooLong": "Please shorten this text to %{maxLength} or less",
                    "tooShort": "Please enter %{minLength} characters or more"
                },
                "toggle": {
                    "requiredMsg": "This is required"
                },
                "checkbox": {
                    "requiredMsg": "This is required"
                },
                "date": {
                    "placeholder": "Select date",
                    "requiredMsg": "This is required",
                    "rangeOverflow": "Value must be less than or equal to %{max}",
                    "rangeUnderflow": "Value must be more than or equal to %{min}"
                },
                "dateRange": {
                    "startLabel": "Start",
                    "endLabel": "End",
                    "invalidRange": "Invalid Range",
                    "to": "to"
                },
                "timeRange": {
                    "dateRequiredMsg": "Date is required",
                    "startEndRequiredMsg": "Start and End required",
                    "startLabel": "Start",
                    "endLabel": "End",
                    "placeholderHours": "Hours",
                    "placeholderDate": "Date",
                    "placeholderStart": "Start",
                    "placeholderEnd": "End",
                    "to": "to"
                },
                "dateTime": {
                    "enterDateAndTime": "Enter Date and Time"
                },
                "hours": {
                    "requiredMsg": "This is required",
                    "endAfterStartDate": "End date must be after start date",
                    "placeholder": "Hours"
                },
                "fileUpload": {
                    "noFileSelectedMsg": "No file selected",
                    "requiredMsg": "This is required",
                    "minimumSizeMsg": "File is too small. Minimum size is %{minFileSize} bytes. Your file size is %{fileSize}",
                    "maximumSizeMsg": "File is too large. Maximum size is %{maxFileSize} bytes. Your file size is ${fileSize}",
                    "fileUpload": "Upload File"
                },
                "select": {
                    "requiredMsg": "This is required",
                    "placeholder": "-- Select --",
                    "noneOption": "-- None --",
                    "nothingFound": "Nothing found",
                    "option": "Option",
                    "searchPlaceholder": "Seach..."
                },
                "lookup": {
                    "requiredMsg": "This is required",
                    "placeholder": "-- Select --"
                },
                "time": {
                    "placeholder": "e.g 7a"
                }
            },
            "forms": {
                "missingObjectAttribute": "Object attribute is missing",
                "missingDescribe": "Describe for object [%{object}] missing",
                "missingFields": "Fields attribute is missing",
                "invalidField": "Invalid field: %{field}",
                "errorFound": "There was an error",
                "saveParentNotice": "Save this record to view the related items.",
                "relatedRecordsHeader": "Related Records",
                "noRelatedRecords": "No Related Records"
            },
            "recordsList": {
                "confirmDelete": "Are you sure you want to delete this?"
            },
            "filters": {
                "title": "Filters",
                "clear": "Clear",
                "yes": "Yes",
                "no": "No",
                "operators": {
                    "equals": "equals"
                }
            },
            "mapComponent": {
                "markerYou": "You",
                "markerShift": "Shift",
                "errors": {
                    "notSupported": "Not Supported",
                    "notSupportedMsg": "Geolocation is not supported by this device",
                    "permissionDenied": "Location Permission Denied",
                    "permissionDeniedMsg": "You have denied the permission for geolocation 😓.\nTo re-enable it, check your browser or device settings.",
                    "noLocationInfo": "Location Information Unavailable",
                    "noLocationInfoMsg": "Location information is unavailable 😓",
                    "timeout": "Location Timeout",
                    "timeoutMsg": "The request to get your location timed out 🕑",
                    "locationUnknown": "Location Error",
                    "locationUnknownMsg": "An unknown error occurred 👹",
                    "unknownError": "Location Unknown Error",
                    "unknownErrorMsg": "An unknown error occurred 👹"
                }
            },
            "shiftMatching": {
                "tabs": {
                    "search": "Search 🔎",
                    "invites": "Invites 🚀"
                },
                "invite": "Invite",
                "invited": "Invited",
                "viewDetails": "View Details",
                "assign": "Assign",
                "toggleDetails": "Toggle Details",
                "searchAgain": "Search Again",
                "searching": "Searching...",
                "noResults": "No results found 😰",
                "noMatches": "No matches found 😰",
                "noInvites": "No invitations found 😰"
            },
            "templateShiftWeeks": {
                "addTemplate": "Add Template",
                "numberOfShifts": "Number of Shifts"
            },
            "daySelectionHeader": {
                "weeklyTotal": "Weekly Total",
                "weeklyView": "Weekly View",
                "today": "Today"
            },
            "shiftBulkCreator": {
                "shifts": "Shifts",
                "schedule": "Schedule",
                "generate": "Generate",
                "dates": "Dates"
            },
            "time": {
                "day": "Day",
                "week": "Week",
                "month": "Month",
                "monday": "Monday",
                "tuesday": "Tuesday",
                "wednesday": "Wednesday",
                "thursday": "Thursday",
                "friday": "Friday",
                "saturday": "Saturday",
                "sunday": "Sunday"
            },
            "timeUtils": {
                "today": "Today",
                "tomorrow": "Tomorrow",
                "yesterday": "Yesterday",
                "on": "On %{t}",
                "this": "This %{t}",
                "nextWeek": "Next week, %{t}",
                "last": "Last %{t}",
                "inMinutes": {
                    "zero": "In less than a minute",
                    "one": "In %{count} minute",
                    "other": "In %{count} minutes"
                },
                "minutesAgo": {
                    "zero": "now",
                    "one": "%{count} minute ago",
                    "other": "%{count} minutes ago"
                },
                "currentMinutes": {
                    "zero": "now",
                    "one": "%{count} minute",
                    "other": "%{count} minutes"
                },
                "relativeDays": {
                    "zero": "less than a day",
                    "one": "%{count} day",
                    "other": "%{count} days"
                },
                "relativeHours": {
                    "zero": "zero hours",
                    "one": "%{count} hour",
                    "other": "%{count} hours"
                },
                "relativeMinutes": {
                    "zero": "zero minutes",
                    "one": "%{count} minute",
                    "other": "%{count} minutes"
                },
                "relativeHoursAndMinutes": "%{hoursText} and %{minutesText}",
                "inRelativeHoursAndMinutes": "In %{hoursText} and %{minutesText}",
                "relativeHoursAndMinutesAgo": "%{hoursText} and %{minutesText} ago",

                "relativeDaysHoursMinutes": "%{daysText}, %{hoursText} and %{minutesText}",
                "inRelativeDaysHoursMinutes": "In %{daysText}, %{hoursText} and %{minutesText}",
                "relativeDaysHoursMinutesAgo": "%{daysText}, %{hoursText} and %{minutesText} ago"
            },
            "themes": {
                "light": "Light",
                "dark": "Dark",
                "black": "Black",
                "cmyk": "CYMK",
                "lemonade": "Lemon",
                "emerald": "Emerald",
                "pastel": "Pastel",
                "wireframe": "Wire",
                "retro": "Retro",
                "business": "Business",
                "acid": "Acid",
                "dim": "Dim",
                "nord": "Nord",
                "cyberpunk": "Cyberpunk",
                "lofi": "Lofi",
                "synthwave": "Synthwave",
                "custom": "Custom"
            },
            "quotePlaceholder": {
                "quote1": "The best time to plant a tree was 20 years ago. The second best time is now.",
                "quote2": "Time is what we want most, but what we use worst.",
                "quote3": "Time is the most valuable thing a man can spend.",
                "quote4": "You may delay, but time will not.",
                "quote5": "Lost time is never found again.",
                "quote6": "The way we spend our time defines who we are.",
                "quote7": "Time is a created thing. To say 'I don't have time' is to say 'I don't want to'.",
                "quote8": "Time slips away like grains of sand never to return again.",
                "quote9": "Don’t spend time beating on a wall, hoping to transform it into a door.",
                "quote10": "Punctuality is not just limited to arriving at a place at right time, it is also about taking actions at right time.",
                "quote11": "Time flies over us, but leaves its shadow behind.",
                "quote12": "Time is a storm in which we are all lost.",
                "quote13": "Time takes it all, whether you want to or not.",
                "quote14": "Time is a game played beautifully by children.",
                "quote15": "The only reason for time is so that everything doesn't happen at once.",
                "quote16": "They always say time changes things, but you actually have to change them yourself.",
                "quote17": "Time you enjoy wasting is not wasted time.",
                "quote18": "Time brings all things to pass.",
                "quote19": "Time and tide wait for no man.",
                "quote20": "Time is the wisest counselor of all.",
                "quote21": "Men talk of killing time, while time quietly kills them.",
                "quote22": "Time is free, but it's priceless. You can't own it, but you can use it. You can't keep it, but you can spend it. Once you've lost it you can never get it back.",
                "quote23": "Time is what keeps everything from happening at once.",
                "quote24": "There’s never enough time to do it right, but there’s always enough time to do it over.",
                "quote25": "The trouble is, you think you have time.",
                "quote26": "Time is the longest distance between two places.",
                "quote27": "Time is the most precious gift of all because you only have a set amount of it.",
                "quote28": "Time waits for no one.",
                "quote29": "Time moves in one direction, memory in another.",
                "quote30": "It’s really clear that the most precious resource we all have is time.",
                "quote31": "Time is what we want most, but what we use worst.",
                "quote32": "Time changes everything except something within us which is always surprised by change.",
                "quote33": "Time does not change us. It just unfolds us.",
                "quote34": "Time is a cruel thief to rob us of our former selves. We lose as much to life as we do to death.",
                "quote35": "Time is a great teacher, but unfortunately it kills all its pupils.",
                "quote36": "Time heals what reason cannot.",
                "quote37": "The bad news is time flies. The good news is you’re the pilot.",
                "quote38": "Time is a created thing. To say 'I don’t have time,' is like saying, 'I don’t want to.'",
                "quote39": "Time stays long enough for anyone who will use it.",
                "quote40": "Time flies over us but leaves its shadow behind.",
                "quote41": "Time expands, then contracts, all in tune with the stirrings of the heart.",
                "quote42": "Time is the most valuable thing a man can spend.",
                "quote43": "Time flies like an arrow; fruit flies like a banana.",
                "quote44": "The future is something which everyone reaches at the rate of sixty minutes an hour, whatever he does, whoever he is.",
                "quote45": "Yesterday is gone. Tomorrow has not yet come. We have only today. Let us begin.",
                "quote46": "Time is the wisest of all things that are, for it brings everything to light.",
                "quote47": "Time will pass; will you?",
                "quote48": "Better three hours too soon than a minute too late.",
                "quote49": "There’s time enough, but none to spare.",
                "quote50": "Time, as it grows old, teaches all things."
            }
        }

    }
}

App Language Determination

When an app loads, we will determine the app's language in the following order:

  1. Check if the user has previously selected a preferred language (stored in local storage / cache). If yes, we will attempt to load the app with that preferred language.
  2. Check for the url parameters language and hideLanguageSwitch or the component's language and hide-language-switch properties. If these properties are available, we will set the language
  3. Check for the App Definition defaultLanguage and availableLanguages. If available, we will use the defaultLanguage
  4. Fallback to en-GB - the default internal translation is en-GB

Note:

- The language switch is only visible if there are 2 or more translations available and the hide-language-switch / hideLanguageSwitch properties are not positive.

Metadata Translations

Adding Translation Keys (labels)

A translation key is a key you can set in metadata in order to provide custom translations to components/setup originating from the app definition. Simply define a property "translationKey" and give it a unique name within the app definition and then add your translation in the App Translation, under the property "labels".

Examples:

Clock App - Navigation Menu Items

In this example, we are adding a key "menuTimesheets" in the app definition

{
    ...,
    "navMenuItems": [
        {
            "label": "Timesheets",
            "translationKey": "menuTimesheets",
            "href": "#Timesheet_Submission?contactId=:userId"
        },
        ...
    ],
    ...
}

In the spanish translation, we have defined the key "menuTimesheets" under a new property called "labels"

{
    "es-ES": {
        "labels": {
            "menuTimesheets": "Hoja de tiempo"
        },
        ...
    }
}

Translation Key property is supported by the following metadata types: field, filter, section block, output field, navMenuItems, recordFormFields, filters, title, recordNavigate, view, history.

Adding Field Translations (fields)

Often you will have input or output fields defined in the app metadata. Unless you have added a "label" or "labelOverride" property, the label to be displayed in the UI will be that of the field's setting in Salesforce for the current Salesforce user's language preference (if a internal translation exists, fallback is English). This is why, we have added support for defining custom field translations through the new object property in the translation JSON called "fields". Define a key as the field API name and a value as the target translated value like so:

{
    "es-ES": {
        "fields": {
            "b3s__Status__c": "Estado",
            ...
        }
        
    }
}

Note: It is important to note that "fields" DO NOT support paths (e.g. "b3s__Contact__r.Name"). If you have fields that you want translated that are with a relative path, use the translationKey method (see above).

User Setting of Translation

End users (candidates & clients) can change the app language from the Header component now available in all applications. If there are two or more translations available for a given application, a new drop down will appear with the language label. Users can set a language and their preference will be saved.

Terminal App

Not Supported

Scheduler

Most Current (8.100+)

{
    "en-GB": {
        "errors": {
            "messages": {
                "startFailed": "App failed to start 👹",
                "dataLoadingFailed": "There was an error loading the necessary data 😵‍💫"
            }
        },
        "settingsPanel": {
            "filters": "Filters"
        },
        "rightSidebar": {
            "tips": {
                "groups": "Groups",
                "filters": "Filters",
                "dateChange": "Change Date",
                "reload": "Reload",
                "compliance": "Re-run Compliance",
                "settings": "Settings",
                "fullScreen": "Full Screen"
            }
        },
        "dateSelection": {
            "jumpToDate": "Go to Date",
            "selectDate": "Select Date"
        },
        "groupSelection": {
            "changeGroup": "Change Group",
            "groupExplainer": "Groups allow you narrow down your dataset. If the group you are trying to access is not available, please talk to your system admin.",
            "groupChangeHeader": "Changing Groups"
        },
        "contextMenu": {
            "clearClipboard": "Clear Clipboard",
            "paste": "Paste",
            "shiftMatch": "Match",
            "candidateMatch": "Match",
            "cancellation": "Request Cancellation",
            "copyBulk": {
                "one": "Copy one item",
                "other": "Copy %{count} items"
            },
            "cutBulk": {
                "one": "Cut one item",
                "other": "Cut %{count} items"
            },
            "deleteBulk": {
                "one": "Delete one item",
                "other": "Delete %{count} items"
            },
            "cancelBulk": {
                "one": "Cancel one item",
                "other": "Cancel %{count} items"
            },
            "editBulk": {
                "one": "Edit one item",
                "other": "Edit %{count} items"
            },
            "cancelSelection": "Cancel Selection",
            "createShift": "New Shift on %{date}",
            "createRequest": "New Request on %{date}",
            "createMultipleShifts": "Create Multiple Shifts",
            "copyShift": "Copy Shift",
            "copyRequest": "Copy Request",
            "cutShift": "Cut Shift",
            "cutRequest": "Cut Request",
            "editShift": "Edit Shift",
            "editRequest": "Edit Request",
            "deleteShift": "Delete Shift",
            "deleteRequest": "Delete Request"
        }
    }
}

Timesheet Submission & Approval

Most Current (8.100+)

{
    "en-GB": {
        "errors": {
            "messages": {
                "startFailed": "App failed to start 👹",
                "dataLoadingFailed": "There was an error loading the necessary data 😵‍💫"
            }
        },
        "tabs": {
            "shifts": "Shifts",
            "expenses": "Expenses"
        },
        "forms": {
            "approval": "Approval",
            "rejection": "Rejection",
            "submitForApproval": "Submit for Approval",
            "weekSubmissionDetail": {
                "zero": "No Records",
                "one": "Week %{startDate} - %{endDate} (1 record)",
                "other": "Week %{startDate} - %{endDate} (%{count} records)"
            }
        },
        "actions": {
            "approve": "Approve",
            "reject": "Reject",
            "approveAll": "Approve All",
            "rejectAll": "Reject All"
        },
        "shift": {
            "actions": {
                "submitForApprovalLabel": "Submit Week for Approval"
            }
        },
        "expense": {
            "actions": {
                "submitForApprovalLabel": "Submit Week for Approval"
            }
        }
    }
}

Client Scheduler

Most Current (8.100+)

{
    "en-GB": {
        "errors": {
            "messages": {
                "startFailed": "App failed to start 👹",
                "dataLoadingFailed": "There was an error loading the necessary data 😵‍💫"
            }
        }
    }
}

Clock App

Most Current (8.100+)

{
    "en-GB": {
        "errors": {
            "messages": {
                "startFailed": "App failed to start 👹",
                "dataLoadingFailed": "There was an error loading the necessary data 😵‍💫"
            }
        },
        "timeClock": {
            "accurateWithin": "Accurate within %{currentLocationAccuracy}m",
            "startAction": "Start",
            "startedAt": "Started at %{t}",
            "lateBy": "Late by %{t}",
            "earlyBy": "%{t} early",
            "errorGettingLocation": "Unknown error in getting location"
        },
        "myInvites": {
            "tabs": {
                "invites": "Invites 🚀",
                "search": "Search 🔎",
                "past": "Past"
            }
        },
        "invitationRequests": {
            "allCaughtUp": "You are all caught up 🎉",
            "inviteResponseHeader": "Response",
            "acceptButtonLabel": "Accept",
            "rejectButtonLabel": "Reject"
        },
        "searchShifts": {
            "searchError": "Unknown error occured",
            "noShiftsFound": "No shifts found 🥲",
            "noShiftsFoundPrompt": "Search again?",
            "claimOpenShiftLabel": "Claim"
        },
        "myShifts": {
            "tabs": {
                "shifts": "Shifts",
                "expenses": "Expenses",
                "availability": "Availability"
            }
        },
        "shiftTypes": {
            "adhoc": "Ad-Hoc"
        },
        "navigation": {
            "menu": {
                "logBreak": "Break ☕",
                "submitLocation": "Submit Location 📍"
            }
        }
    }
}

Global Labels

Most Current (8.100+)

{
    "en-GB": {
        "common": {
            "attribution": {
                "poweredBy": "Powered by",
                "brand": "3B",
                "brandLink": "https://3b4sf.com"
            },
            "errors": {
                "unknownError": "There was an unknown error",
                "serverError": "There was a server error: %{error}",
                "authFailed": "Authorization Failed: %{error}",
                "apexAccessError": "Error with the %{controller}. Make sure you have set up access correctly.",
                "sessionExpired": "The session has expired",
                "fetchFailed": "Fetch Failed: %{error}",
                "upsertError": "There was an error in processing your request. %{error}",
                "queryError": "There was an error in retreiving the data for your request. %{error}",
                "deleteError": "There was an error in your request to delete these records. %{error}",
                "describeError": "There was an error in describing the database. %{error}",
                "queryWithFilterError": "Query filter cannot include the limit directive",
                "missingDescribe": "Database describe was unsuccessful",
                "missingRequiredArgument": "Missing required argument [%{type}]",
                "fileUploadFailed": "File upload failed"
            },
            "actions": {
                "edit": "Edit",
                "editing": "Editing",
                "save": "Save",
                "saving": "Saving..",
                "cancel": "Cancel",
                "canceling": "Canceling..",
                "close": "Close",
                "closing": "Closing..",
                "delete": "Delete",
                "deleting": "Deleting..",
                "select": "Select",
                "unSelect": "Un-Select",
                "selectAll": "Select All",
                "selectNone": "Select None"
            },
            "fields": {
                "number": {
                    "requiredMsg": "This is required",
                    "min": "Minimum is %{min}",
                    "max": "Maximum is %{max}"
                },
                "text": {
                    "placeholder": "",
                    "requiredMsg": "This is required",
                    "tooLong": "Please shorten this text to %{maxLength} or less",
                    "tooShort": "Please enter %{minLength} characters or more",
                    "typeNotValid": "This is not a valid %{type}"
                },
                "textArea": {
                    "placeholder": "",
                    "requiredMsg": "This is required",
                    "tooLong": "Please shorten this text to %{maxLength} or less",
                    "tooShort": "Please enter %{minLength} characters or more"
                },
                "toggle": {
                    "requiredMsg": "This is required"
                },
                "checkbox": {
                    "requiredMsg": "This is required"
                },
                "date": {
                    "placeholder": "Select date",
                    "requiredMsg": "This is required",
                    "rangeOverflow": "Value must be less than or equal to %{max}",
                    "rangeUnderflow": "Value must be more than or equal to %{min}"
                },
                "dateRange": {
                    "startLabel": "Start",
                    "endLabel": "End",
                    "invalidRange": "Invalid Range",
                    "to": "to"
                },
                "timeRange": {
                    "dateRequiredMsg": "Date is required",
                    "startEndRequiredMsg": "Start and End required",
                    "startLabel": "Start",
                    "endLabel": "End",
                    "placeholderHours": "Hours",
                    "placeholderDate": "Date",
                    "placeholderStart": "Start",
                    "placeholderEnd": "End",
                    "to": "to"
                },
                "dateTime": {
                    "enterDateAndTime": "Enter Date and Time"
                },
                "hours": {
                    "requiredMsg": "This is required",
                    "endAfterStartDate": "End date must be after start date",
                    "placeholder": "Hours"
                },
                "fileUpload": {
                    "noFileSelectedMsg": "No file selected",
                    "requiredMsg": "This is required",
                    "minimumSizeMsg": "File is too small. Minimum size is %{minFileSize} bytes. Your file size is %{fileSize}",
                    "maximumSizeMsg": "File is too large. Maximum size is %{maxFileSize} bytes. Your file size is ${fileSize}",
                    "fileUpload": "Upload File"
                },
                "select": {
                    "requiredMsg": "This is required",
                    "placeholder": "-- Select --",
                    "noneOption": "-- None --",
                    "nothingFound": "Nothing found",
                    "option": "Option",
                    "searchPlaceholder": "Seach..."
                },
                "lookup": {
                    "requiredMsg": "This is required",
                    "placeholder": "-- Select --"
                },
                "time": {
                    "placeholder": "e.g 7a"
                }
            },
            "forms": {
                "missingObjectAttribute": "Object attribute is missing",
                "missingDescribe": "Describe for object [%{object}] missing",
                "missingFields": "Fields attribute is missing",
                "invalidField": "Invalid field: %{field}",
                "errorFound": "There was an error",
                "saveParentNotice": "Save this record to view the related items.",
                "relatedRecordsHeader": "Related Records",
                "noRelatedRecords": "No Related Records"
            },
            "recordsList": {
                "confirmDelete": "Are you sure you want to delete this?"
            },
            "filters": {
                "title": "Filters",
                "clear": "Clear",
                "yes": "Yes",
                "no": "No",
                "operators": {
                    "equals": "equals"
                }
            },
            "mapComponent": {
                "markerYou": "You",
                "markerShift": "Shift",
                "errors": {
                    "notSupported": "Not Supported",
                    "notSupportedMsg": "Geolocation is not supported by this device",
                    "permissionDenied": "Location Permission Denied",
                    "permissionDeniedMsg": "You have denied the permission for geolocation 😓.\nTo re-enable it, check your browser or device settings.",
                    "noLocationInfo": "Location Information Unavailable",
                    "noLocationInfoMsg": "Location information is unavailable 😓",
                    "timeout": "Location Timeout",
                    "timeoutMsg": "The request to get your location timed out 🕑",
                    "locationUnknown": "Location Error",
                    "locationUnknownMsg": "An unknown error occurred 👹",
                    "unknownError": "Location Unknown Error",
                    "unknownErrorMsg": "An unknown error occurred 👹"
                }
            },
            "shiftMatching": {
                "tabs": {
                    "search": "Search 🔎",
                    "invites": "Invites 🚀"
                },
                "invite": "Invite",
                "invited": "Invited",
                "viewDetails": "View Details",
                "assign": "Assign",
                "toggleDetails": "Toggle Details",
                "searchAgain": "Search Again",
                "searching": "Searching...",
                "noResults": "No results found 😰",
                "noMatches": "No matches found 😰",
                "noInvites": "No invitations found 😰"
            },
            "templateShiftWeeks": {
                "addTemplate": "Add Template",
                "numberOfShifts": "Number of Shifts"
            },
            "daySelectionHeader": {
                "weeklyTotal": "Weekly Total",
                "weeklyView": "Weekly View",
                "today": "Today"
            },
            "shiftBulkCreator": {
                "shifts": "Shifts",
                "schedule": "Schedule",
                "generate": "Generate",
                "dates": "Dates"
            },
            "time": {
                "day": "Day",
                "week": "Week",
                "month": "Month",
                "monday": "Monday",
                "tuesday": "Tuesday",
                "wednesday": "Wednesday",
                "thursday": "Thursday",
                "friday": "Friday",
                "saturday": "Saturday",
                "sunday": "Sunday"
            },
            "timeUtils": {
                "today": "Today",
                "tomorrow": "Tomorrow",
                "yesterday": "Yesterday",
                "on": "On %{t}",
                "this": "This %{t}",
                "nextWeek": "Next week, %{t}",
                "last": "Last %{t}",
                "inMinutes": {
                    "zero": "In less than a minute",
                    "one": "In %{count} minute",
                    "other": "In %{count} minutes"
                },
                "minutesAgo": {
                    "zero": "now",
                    "one": "%{count} minute ago",
                    "other": "%{count} minutes ago"
                },
                "currentMinutes": {
                    "zero": "now",
                    "one": "%{count} minute",
                    "other": "%{count} minutes"
                },
                "relativeDays": {
                    "zero": "less than a day",
                    "one": "%{count} day",
                    "other": "%{count} days"
                },
                "relativeHours": {
                    "zero": "zero hours",
                    "one": "%{count} hour",
                    "other": "%{count} hours"
                },
                "relativeMinutes": {
                    "zero": "zero minutes",
                    "one": "%{count} minute",
                    "other": "%{count} minutes"
                },
                "relativeHoursAndMinutes": "%{hoursText} and %{minutesText}",
                "inRelativeHoursAndMinutes": "In %{hoursText} and %{minutesText}",
                "relativeHoursAndMinutesAgo": "%{hoursText} and %{minutesText} ago",

                "relativeDaysHoursMinutes": "%{daysText}, %{hoursText} and %{minutesText}",
                "inRelativeDaysHoursMinutes": "In %{daysText}, %{hoursText} and %{minutesText}",
                "relativeDaysHoursMinutesAgo": "%{daysText}, %{hoursText} and %{minutesText} ago"
            },
            "themes": {
                "light": "Light",
                "dark": "Dark",
                "black": "Black",
                "cmyk": "CYMK",
                "lemonade": "Lemon",
                "emerald": "Emerald",
                "pastel": "Pastel",
                "wireframe": "Wire",
                "retro": "Retro",
                "business": "Business",
                "acid": "Acid",
                "dim": "Dim",
                "nord": "Nord",
                "cyberpunk": "Cyberpunk",
                "lofi": "Lofi",
                "synthwave": "Synthwave",
                "custom": "Custom"
            },
            "quotePlaceholder": {
                "quote1": "The best time to plant a tree was 20 years ago. The second best time is now.",
                "quote2": "Time is what we want most, but what we use worst.",
                "quote3": "Time is the most valuable thing a man can spend.",
                "quote4": "You may delay, but time will not.",
                "quote5": "Lost time is never found again.",
                "quote6": "The way we spend our time defines who we are.",
                "quote7": "Time is a created thing. To say 'I don't have time' is to say 'I don't want to'.",
                "quote8": "Time slips away like grains of sand never to return again.",
                "quote9": "Don’t spend time beating on a wall, hoping to transform it into a door.",
                "quote10": "Punctuality is not just limited to arriving at a place at right time, it is also about taking actions at right time.",
                "quote11": "Time flies over us, but leaves its shadow behind.",
                "quote12": "Time is a storm in which we are all lost.",
                "quote13": "Time takes it all, whether you want to or not.",
                "quote14": "Time is a game played beautifully by children.",
                "quote15": "The only reason for time is so that everything doesn't happen at once.",
                "quote16": "They always say time changes things, but you actually have to change them yourself.",
                "quote17": "Time you enjoy wasting is not wasted time.",
                "quote18": "Time brings all things to pass.",
                "quote19": "Time and tide wait for no man.",
                "quote20": "Time is the wisest counselor of all.",
                "quote21": "Men talk of killing time, while time quietly kills them.",
                "quote22": "Time is free, but it's priceless. You can't own it, but you can use it. You can't keep it, but you can spend it. Once you've lost it you can never get it back.",
                "quote23": "Time is what keeps everything from happening at once.",
                "quote24": "There’s never enough time to do it right, but there’s always enough time to do it over.",
                "quote25": "The trouble is, you think you have time.",
                "quote26": "Time is the longest distance between two places.",
                "quote27": "Time is the most precious gift of all because you only have a set amount of it.",
                "quote28": "Time waits for no one.",
                "quote29": "Time moves in one direction, memory in another.",
                "quote30": "It’s really clear that the most precious resource we all have is time.",
                "quote31": "Time is what we want most, but what we use worst.",
                "quote32": "Time changes everything except something within us which is always surprised by change.",
                "quote33": "Time does not change us. It just unfolds us.",
                "quote34": "Time is a cruel thief to rob us of our former selves. We lose as much to life as we do to death.",
                "quote35": "Time is a great teacher, but unfortunately it kills all its pupils.",
                "quote36": "Time heals what reason cannot.",
                "quote37": "The bad news is time flies. The good news is you’re the pilot.",
                "quote38": "Time is a created thing. To say 'I don’t have time,' is like saying, 'I don’t want to.'",
                "quote39": "Time stays long enough for anyone who will use it.",
                "quote40": "Time flies over us but leaves its shadow behind.",
                "quote41": "Time expands, then contracts, all in tune with the stirrings of the heart.",
                "quote42": "Time is the most valuable thing a man can spend.",
                "quote43": "Time flies like an arrow; fruit flies like a banana.",
                "quote44": "The future is something which everyone reaches at the rate of sixty minutes an hour, whatever he does, whoever he is.",
                "quote45": "Yesterday is gone. Tomorrow has not yet come. We have only today. Let us begin.",
                "quote46": "Time is the wisest of all things that are, for it brings everything to light.",
                "quote47": "Time will pass; will you?",
                "quote48": "Better three hours too soon than a minute too late.",
                "quote49": "There’s time enough, but none to spare.",
                "quote50": "Time, as it grows old, teaches all things."
            }
        }
    }
}