diff --git a/.eslintrc.json b/.eslintrc.json
index 159df0193..4014fd2b4 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -34,6 +34,7 @@
"react/destructuring-assignment": 0,
"react/forbid-prop-types": 0,
"react/jsx-wrap-multilines": 0,
+ "react/style-prop-object": [2, { "allow": ["FormattedNumber"] }],
"react-hooks/exhaustive-deps": 1,
"import/prefer-default-export": 0,
"function-paren-newline": 0,
diff --git a/docs/contribution-guide.md b/docs/contribution-guide.md
index 88a935833..229c5afb7 100644
--- a/docs/contribution-guide.md
+++ b/docs/contribution-guide.md
@@ -22,6 +22,20 @@ All displayed text must support translation - for this we use `react-intl`. Tran
If you want to help translate the project, that is very much appreciated and needed, but please don't do it by manually editing files in `/locale`. Your changes will wind up getting overwritten by Lokalise.
+## Lodash
+
+We frequently use a utility library called [lodash](https://lodash.com/docs/). When importing a utility function, be sure to use named imports rather than importing the entire library to reduce our bundle size. One incorrect import will cause the entire library to be bundled with the application.
+
+```
+import _ from 'lodash-es'; // incorrect, please don't do this
+_.random(10);
+
+import { random } from 'lodash-es'; // correct!
+random(10);
+```
+
+In situations where a lodash utility and a native utility exist, we should use the native utility unless there is a reason to use lodash (eg. native JS `map` instead of `_.map()`).
+
## Conventions
- Any file with a React component should have the suffix `.jsx`
diff --git a/locale/en.json b/locale/en.json
index f283d0a5d..f85844035 100644
--- a/locale/en.json
+++ b/locale/en.json
@@ -7,18 +7,25 @@
"COMMUNITY_FORUMS": "Community forums",
"ENCOUNTERS": "Encounters",
"ADD_NEW": "Add new",
+ "ADD_SOCIAL_GROUP": "Add social group",
+ "SOCIAL_GROUP_SUBHEADER": "Social group created on {dateCreated}",
+ "ALL_SOCIAL_GROUPS": "All social groups",
"BULK_IMPORT": "Bulk import",
"LIME": "Lime",
"LABEL": "Label",
"TYPE": "Type",
"FOREST": "Forest",
+ "NAME_OF_GROUP": "Name of group",
"STAGE": "Stage",
"NO_PENDING_PUBLIC_SIGHTINGS": "There are no pending public sightings",
"DATA_UNAVAILABLE": "Data unavailable",
- "DATA_MANAGER": "Data Manager",
"ANNOTATION_CLASS": "Annotation class",
"INDIVIDUAL_METADATA": "Individual profile",
"COMMIT": "Commit",
+ "UNNAMED_SOCIAL_GROUP": "Unnamed social group",
+ "SOCIAL_GROUP_NOT_FOUND": "Social group not found",
+ "SOCIAL_GROUP_NOT_FOUND_DESCRIPTION": "Social group lookup failed. There may be a problem with the URL or the social group may no longer exist.",
+ "SOCIAL_GROUP_DELETE_CONFIRMATION": "Are you sure you want to delete this social group? This action cannot be undone.",
"REGION_MATCHING_SET_DESCRIPTION": "Select the region you would like to match against. Sub-regions will be included in the matching set. If no region is selected, all regions will be matched against.",
"CREATED": "Created",
"BLANK": " ",
@@ -68,11 +75,11 @@
"ALL_NOTIFICATION_EMAILS": "All notification emails",
"COLLABORATION_EDIT_REQUESTS": "Edit requests",
"COLLABORATION_REQUESTS": "Collaboration requests",
- "INDIVIDUAL_MERGE_REQUESTS": "Individual merge requests",
+ "MERGE_OF_INDIVIDUAL": "Merge of individual",
"ALL_NOTIFICATION_EMAILS_DESCRIPTION": "Receive emails for notifications per the settings below.",
"COLLABORATION_EDIT_REQUESTS_DESCRIPTION": "Receive an email whenever someone requests to upgrade our collaboration for edit access.",
"COLLABORATION_REQUESTS_DESCRIPTION": "Receive an email whenever someone sends me a collaboration request.",
- "INDIVIDUAL_MERGE_REQUESTS_DESCRIPTION": "Receive an email whenever someone attempts to merge an individual that I created.",
+ "MERGE_OF_INDIVIDUAL_DESCRIPTION": "Receive email notifications for merge activity involving any individual that I created.",
"GRANT_ACCESS": "Grant access",
"REVOKE_ACCESS": "Revoke access",
"COLLABORATION_REQUEST": "Collaboration request",
@@ -130,8 +137,6 @@
"DELETE_ANNOTATION": "Delete annotation",
"DELETE_ANIMAL": "Delete animal",
"PENDING_BULK_IMPORT": "Bulk import in progress",
- "SIGHTING_STATE_IN_PROGRESS": "In progress ({timeDelta} so far).",
- "IN_PROGRESS": "In progress.",
"NEW_INDIVIDUAL_CREATED_DESCRIPTION": "New individual created.",
"PENDING_BULK_IMPORT_MESSAGE": "You reported {sightingCount} sightings on {date}. You must finish processing this bulk import before starting another.",
"PENDING_IMAGE_PROCESSING": "Image processing in progress",
@@ -141,22 +146,11 @@
"HISTORY": "History",
"SIGHTING_PREPARATION_ALERT_TITLE": "This sighting has not finished processing",
"SIGHTING_PREPARATION_ALERT_MESSAGE": "Until this step is complete, images cannot be viewed and some functionality is unavailable.",
- "SIGHTING_PREPARATION_SKIPPED_MESSAGE": "Sighting preparation skipped.",
- "DETECTION_SKIPPED_MESSAGE": "Detection skipped.",
- "DETECTION_SKIPPED_NO_IMAGES_MESSAGE": "Detection skipped - no images to annotate.",
- "DETECTION_SKIPPED_NO_MODEL_MESSAGE": "Detection skipped - no detection model was specified.",
- "CURATION_SKIPPED_MESSAGE": "Curation skipped - no images to curate.",
- "IDENTIFICATION_SKIPPED_MESSAGE": "Identification skipped.",
- "IDENTIFICATION_SKIPPED_NO_IMAGES_MESSAGE": "Identification skipped - no images to match.",
- "IDENTIFICATION_SKIPPED_MIGRATED_MESSAGE_DEFAULT": "Identification skipped - this sighting was migrated from a Wildbook database.",
- "IDENTIFICATION_SKIPPED_MIGRATED_MESSAGE_SITE": "Identification skipped - this sighting was migrated from {migratedSiteName}.",
- "CURATION_INSTRUCTIONS": "All annotations must be assigned to animals before identification can begin.",
"IDENTIFICATION": "Identification",
"WAITING_ELLIPSES": "Waiting...",
- "CURATION_ASSIGN_ANNOTATIONS": "Assign annotations",
- "CURATION_ANNOTATE_PHOTOS": "Annotate photographs",
- "IDENTIFICATION_VIEW_RESULTS": "View match results",
"REPORTED_BY": "Reported by ",
+ "REPORTED_BY_USER": "Reported by {name}",
+ "REPORTED_BY_UNNAMED_USER": "Reported by Unnamed User",
"GENERAL_SETTINGS": "General settings",
"ONE_SIGHTING_TOGGLE_DESCRIPTION": "This sighting has only one individual and I would like to report metadata about it now.",
"FRONT_PAGE": "Front page",
@@ -202,10 +196,11 @@
"FIRST_NAME": "First name",
"ADOPTION_NAME": "Adoption name",
"ALIAS": "Alias",
- "SETTINGS_AND_PRIVACY": "My settings & privacy",
+ "PREFERENCES": "Preferences",
"SEARCH_ITIS_SPECIES": "Search ITIS species",
"SEARCH_INDIVIDUALS_INSTRUCTION": "Search for an individual by name or guid",
"SEARCH_SIGHTINGS_INSTRUCTION": "Search for a sighting by location, owner or guid",
+ "SEARCH_USER_INSTRUCTION": "Search for a user by name or email",
"PRAIRIE": "Prairie",
"EDIT_USER_METADATA": "Edit user metadata",
"EDIT_SIGHTING_METADATA": "Edit sighting metadata",
@@ -225,16 +220,6 @@
"TIME_SECONDS": "Time (seconds)",
"TIMEZONE": "Timezone",
"ADD_TAG": "Add tag",
- "SIGHTING_PREPARATION_FINISHED_MESSAGE": "Sighting preparation finished on {date}. ({photoCount} {photoCount, plural, =0 {photographs} one {photograph} other {photographs}})",
- "DETECTION_FINISHED_MESSAGE": "Image detection finished on {date}.",
- "CURATION_FINISHED_MESSAGE": "Image curation finished on {date}.",
- "MIGRATION_FINISHED_MESSAGE_DEFAULT": "Sighting migration from a Wildbook database finished on {date}.",
- "MIGRATION_FINISHED_MESSAGE_SITE": "Sighting migration from {migratedSiteName} finished on {date}.",
- "IDENTIFICATION_FINISHED_MESSAGE": "Identification finished on {date}.",
- "SIGHTING_PREPARATION_FAILED": "Sighting preparation failed.",
- "DETECTION_FAILED": "Image detection failed.",
- "CURATION_FAILED": "Image curation failed.",
- "IDENTIFICATION_FAILED": "Image identification failed.",
"SIGHTING_START": "Sighting start",
"SIGHTING_END": "Sighting end",
"NOT_READY_FOR_COMMIT": "This sighting is pending",
@@ -452,6 +437,7 @@
"PRIVATE_SITE": "Private",
"PRIVATE_SITE_DESCRIPTION": "Future versions of Codex will support public sites.",
"SITE_NAME": "Site name",
+ "SITE_NAME_IS_REQUIRED": "Site name is required.",
"SITE_NAME_DESCRIPTION": "The name of this site, excluding domain name suffix (ie. PandaMatcher, NOT pandamatcher.org)",
"FEET": "feet",
"SITE_SETTINGS": "Site configuration",
@@ -493,7 +479,6 @@
"ADD_ANOTHER_FIELD": "Add another field",
"FEET_METERS_SELECTOR": "Feet/meters selector",
"INDIVIDUAL_SELECTOR": "Individual selector",
- "RELATIONSHIPS_SELECTOR": "Relationships selector",
"INTEGER_LABEL": "Whole number",
"FILE_UPLOADER": "File uploader",
"LAT_LONG_SELECTOR": "Lat/long selector",
@@ -568,7 +553,6 @@
"SEX_IS": "Sex: {sex}",
"REGIONS_DESCRIPTION": "These regions will be available to choose from during submission and search. Region assignment is used to improve performance by allowing for matching against a reduced set up submissions. Click ↳ on a region to create a nested subregion.",
"DROPDOWN_CHOICE_HELPER_TEXT": "Each option needs a value and a label. Use the label field for a brief and well-formatted, English-language name (eg. 'Some scars'). The value field specifies how the option will be stored in databases and appear in data exports. Values cannot contain spaces or special characters (eg. 'somescars').",
- "RELATIONSHIP_OPTIONS_HELPER_TEXT": "Each relationship type needs a value and a label. Use the label field for a brief and well-formatted, English-language name (eg. 'Best friend'). The value field specifies how the option will be stored in databases and appear in data exports. Values cannot contain spaces or special characters (eg. 'bestfriend').",
"FILETYPE_OPTIONS_HELPER_TEXT": "Enter the file types you wish to allow for this input, including the dot (ie. '.jpg' or '.wav'). If you skip this step, all file types will be permitted.",
"CHOOSE_FIELDS_DESCRIPTION": "The following fields are available for data import. Each field should be a column in your spreadsheet. Note that region, time, and time precision are required. For sighting time, use whatever columns are known. For example, if the full date is known but not the time of day, leave the hours, minutes, and seconds columns blank.",
"GENERATE_DISABLED_NO_LOCATION": "At least one location field must be selected (region, exact location, or location freeform).",
@@ -587,7 +571,6 @@
"CUSTOM_FIELD_SAME_LABEL_ERROR": "Error: two or more {fieldsetName}s have the same value.",
"CUSTOM_FIELD_SAME_VALUE_ERROR": "Error: two or more {fieldsetName}s have the same value.",
"FINISH": "Finish",
- "RELATIONSHIP_OPTIONS": "Relationship options",
"MALE": "Male",
"FEMALE": "Female",
"OPTION": "Option",
@@ -631,6 +614,7 @@
"BACK_TO_PHOTOS": "Back to photographs",
"BACK_TO_SELECTION": "Back to selection",
"INCOMPLETE_FIELD": "{fieldName} is a required field.",
+ "INCOMPLETE_TIME_SPECIFICITY": "Enter the Year information as well as the Time Specificity.",
"CONTINUE_WITHOUT_PHOTOGRAPHS": "Continue without photographs",
"LAST_SIGHTING_DATE_RANGE": "Last sighting date",
"LAST_SIGHTING_DATE_RANGE_DESCRIPTION": "Date of this individual's most recent sighting.",
@@ -833,7 +817,6 @@
"STATUS_INDIVIDUALS_DESCRIPTION": "Last known status of the individual.",
"STATUS_SIGHTING_DESCRIPTION": "Status at the time of the sighting.",
"SIGHTING_REPORTED_ON": "Sighting reported on {date}",
- "SIGHTING_SUBMISSION_REPORT_DATE": "Sighting reported on {date}",
"MEMBER_COUNT": "{memberCount} {memberCount, plural, =0 {members} one {member} other {members}}",
"SEND_INVITATION": "Send invitation",
"SEX": "Sex",
@@ -1005,6 +988,7 @@
"COMPONENT_COMMIT_HASH": "{component} commit hash: ",
"INDIVIDUAL_SEARCH_NO_RESULTS": "Your search \"{searchTerm}\" did not match any individuals.",
"SIGHTING_SEARCH_NO_RESULTS": "Your search \"{searchTerm}\" did not match any sightings.",
+ "POTENTIAL_COLLABORATOR_SEARCH_NO_RESULTS": "Your search \"{searchTerm}\" did not match any potential collaborators.",
"SEARCH_SERVER_ERROR": "A server error occurred while attempting to search.",
"CONFIGURATION_SITE_NAME_LABEL": "Site name",
"CONFIGURATION_SITE_NAME_DESCRIPTION": "The name of this site, excluding domain name suffix (ie. PandaMatcher, NOT pandamatcher.org).",
@@ -1023,20 +1007,23 @@
"GPS_TITLE": "Sighting Location",
"INVALID_GPS": "Invalid GPS Coordinates",
"UNNAMED_USER": "Unnamed User",
+ "UNNAMED_USER_WITH_EMAIL": "Unnamed User ({email})",
+ "UNNAMED_USER_WITH_UNKNOWN_EMAIL": "Unnamed User (unknown email)",
+ "USER_WITH_EMAIL": "{fullName} ({email})",
+ "USER_WITH_UNKNOWN_EMAIL": "{fullName} (unknown email)",
"REGION_NAME_REMOVED": "Region Name Has Been Removed",
"SELECT_COLLABORATOR_1": "Select Collaborator 1",
"SELECT_COLLABORATOR_2": "Select Collaborator 2",
"CREATE_COLLABORATIONS": "Create Collaborations",
"CREATE_COLLABORATION": "Create Collaboration",
"COLLABORATION_CREATED": "Collaboration Created",
- "EDIT_COLLABORATIONS": "Edit Collaborations",
+ "USER_MANAGEMENT_COLLABORATIONS": "Collaborations",
"USER_ONE": "User 1",
"USER_TWO": "User 2",
+ "USER_X": "User {userNumber, number}",
+ "COLLABORATION_CURRENT_ACCESS": "Current access",
+ "COLLABORATION_REQUESTED_ACCESS": "Requested access",
"COLLABORATION_STATUS": "Status",
- "USER_ONE_VIEW_STATUS": "User 1 Status",
- "USER_TWO_VIEW_STATUS": "User 2 Status",
- "USER_ONE_EDIT_STATUS": "User 1 Edit",
- "USER_TWO_EDIT_STATUS": "User 2 Edit",
"COLLABORATION_ALREADY_EXISTS": "A collaboration between these two users has already been created",
"SIGHTING_ON_MAP": "Sighting",
"COLLABORATION_DATA_ERROR": "Error fetching collaboration data",
@@ -1044,9 +1031,10 @@
"COLLABORATION_REVOKE_SUCCESS": "Successfully revoked the collaboration",
"UNKNOWN_ERROR": "Unknown error",
"REVOKED_COLLAB_EXISTS": "Could not create collaboration - a revoked collaboration between those users already exists",
- "COLLAB_REVOKE_ERROR_SUPPLEMENTAL": "Note that collaborations cannot be revoked unless they are mutually approved.",
+ "COLLAB_REVOKE_ERROR_SUPPLEMENTAL": "{error}. Note that collaborations cannot be revoked unless they are mutually approved.",
"PENDING_SIGHTINGS": "Pending Sightings",
"UNKNOWN_DATE": "Unknown date",
+ "UNKNOWN_ENCOUNTER_DATE": "unknown encounter date",
"CREATED_ON_DATE": "Created on {createdDate}",
"SENT_YOU_A_COLLABORATION_REQUEST": "{userName} sent you a collaboration request",
"A_COLLABORATION_WAS_CREATED_ON_YOUR_BEHALF": "A collaboration was created on your behalf",
@@ -1065,7 +1053,7 @@
"INDIVIDUALS_MERGE_PENDING_TITLE": "Merge pending",
"INDIVIDUALS_MERGE_PENDING_DESCRIPTION": "Your merge could not be completed immediately because you do not have edit access to all of the sightings associated with these individuals. A notification has been sent to the researchers who own these sightings so they can approve or block the merge. They must respond by {deadlineDate} to block the merge or it will proceed.",
"DATE_MISSING": "DATE MISSING",
- "COLLABORATION_ESTABLISHED_BY_USER_MANAGER": "Collaboration established by your user manager",
+ "COLLABORATION_ESTABLISHED_BY_USER_MANAGER": "Collaboration established by a user manager",
"COLLABORATION_REVOKE_TITLE": "Revoke collaboration",
"COLLABORATION_EDIT_REQUEST_TITLE": "Edit collaboration request",
"COLLABORATION_EDIT_APPROVED_TITLE": "Collaboration edit approved",
@@ -1092,12 +1080,15 @@
"BLOCK_MERGE": "Block",
"SELECT_RELATIONSHIP_TYPE": "Select relationship type",
"SELECT_RELATIONSHIP_ROLE": "Select role from {ind}'s perspective",
- "NO_RELATIONSHIPS": "This individual currently has no known relationships",
+ "NO_RELATIONSHIPS": "This individual does not have any relationships.",
+ "NO_SOCIAL_GROUPS_ON_INDIVIDUAL": "This individual is not part of any social groups.",
+ "NO_SOCIAL_GROUPS_ON_SITE": "There are no social groups on this site.",
"NO_PERMISSIONS_ERROR_SUBTITLE": "Permissions error",
"NOT_AUTHENTICATED_ERROR_SUBTITLE": "Authentication error",
"NO_PERMISSIONS_ERROR_DETAILS": "You do not have permissions to access the requested resource.",
"NOT_AUTHENTICATED_ERROR_DETAILS": "Your request could not be processed due to an authentication error. Log in and try again.",
"CONFIRM_REMOVE_RELATIONSHIP": "Are you sure you want to delete this relationship?",
+ "CONFIRM_REMOVE_INDIVIDUAL_FROM_SOCIAL_GROUP": "Are you sure you want to remove this individual from this social group?",
"TWITTER_HANDLE": "Twitter handle",
"CONFIGURATION_INTELLIGENT_AGENT_TWITTERBOT_ENABLED_LABEL": "Allow Twitter submissions?",
"CONFIGURATION_INTELLIGENT_AGENT_TWITTERBOT_ENABLED_DESCRIPTION": "Any Twitter user will be able to create submissions to this platform via tweet. Platform users can add their Twitter handle on their user profile to maintain ownership of the sightings. Otherwise, the data will be public. Make sure that the bio of the platform twitter account reminds users to include image(s), a hashtag of the species, region/location id, and time/date.",
@@ -1125,11 +1116,9 @@
"PASSWORD_RESET_ERROR": "Password reset error",
"PASSWORD_RESET_SUCCESS": "Password reset success",
"RETURN_TO_LOGIN_PAGE": "Return to login page",
- "RESTORE": "Restore",
- "COLLABORATION_RESTORE_SUCCESS": "Successfully restored the collaboration",
- "COLLABORATION_RESTORE_ERROR": "Error restoring the collaboration",
- "COLLAB_RESTORE_ERROR_SUPPLEMENTAL": "Collaboration was not successfully restored.",
+ "NO_INDIVIDUALS_IN_SOCIAL_GROUP": "This group contains no individuals.",
"PENDING": "pending",
+ "UNKNOWN_ROLE": "Unknown role",
"PENDING_CITIZEN_SCIENCE_SIGHTINGS": "Pending citizen science sightings",
"CANDIDATE_ANNOTATIONS": "Candidate annotations",
"SIGHTING_DELETE_VULNERABLE_INDIVIDUAL_MESSAGE": "Deleting this sighting would result in assigned individuals being deleted. Are you sure you want to continue?",
@@ -1141,12 +1130,86 @@
"PAGINATION_PREVIOUS_PAGE": "previous page",
"PAGINATION_NEXT_PAGE": "next page",
"SOCIAL_GROUPS": "Social groups",
- "CONFIGURATION_SOCIAL_GROUP_ROLES_DESCRIPTION": "These roles populate the dropdown menu for any social groups made. It is recommended to include a generic role, such as, \"Member\"",
+ "SOCIAL_GROUP": "Social group",
+ "ADD_TO_SOCIAL_GROUP": "Add to social group",
+ "CONFIGURATION_SOCIAL_GROUP_ROLES_DESCRIPTION": "These roles populate the dropdown menu for any social groups made. It is recommended to include a generic role, such as \"Member\".",
"CONFIGURATION_SOCIAL_GROUP_ROLES_LABEL": "Social group roles",
"NEW_SOCIAL_GROUP_ROLE": "Add role",
"ALLOW_MULTIPLE_OF_THIS_ROLE": "Allow multiple of this role",
"ONE_OR_MORE_ROLES_MISSING_LABELS": "One or more roles are missing labels",
"TWO_OR_MORE_ROLES_SAME_LABEL": "Two or more roles have the same label. Make sure each label is different",
"ROLE_GUID_MISSING": "Role is missing ID",
- "UNFINISHED_OPTIONS": "Options must have valid values and labels and unique values"
+ "UNFINISHED_OPTIONS": "Options must have valid values and labels and unique values",
+ "PROGRESS_STATISTICS_UNKNOWN_PROGRESS": "unknown progress",
+ "PROGRESS_STATISTICS_UNKNOWN_ETA_&_UNKNOWN_QUEUE": "Unknown time remaining. Queued behind an unknown number of jobs.",
+ "PROGRESS_STATISTICS_UNKNOWN_ETA_&_QUEUE": "Unknown time remaining. Queued behind {ahead, number} {ahead, plural, one {job} other {jobs}}.",
+ "PROGRESS_STATISTICS_WRAPPING_ETA_&_UNKNOWN_QUEUE": "Wrapping up... Queued behind an unknown number of jobs.",
+ "PROGRESS_STATISTICS_WRAPPING_ETA_&_QUEUE": "Wrapping up... Queued behind {ahead, number} {ahead, plural, one {job} other {jobs}}.",
+ "PROGRESS_STATISTICS_ETA_&_UNKNOWN_QUEUE": "{timeRemaining} left. Queued behind an unknown number of jobs.",
+ "PROGRESS_STATISTICS_ETA_&_QUEUE": "{timeRemaining} left. Queued behind {ahead, number} {ahead, plural, one {job} other {jobs}}.",
+ "STATUS_SUBMISSION_REPORT_ON": "Sighting reported on {date}.",
+ "STATUS_SUBMISSION_REPORT_ON_UNKNOWN": "Sighting reported on unknown date.",
+ "STATUS_PREPARATION_STARTED_ON": "Sighting preparation started on {date}.",
+ "STATUS_PREPARATION_STARTED_ON_UNKNOWN": "Sighting preparation started on unknown date.",
+ "STATUS_PREPARATION_FINISHED_ON": "Sighting preparation finished on {date}. ({photoCount} {photoCount, plural, one {photograph} other {photographs}})",
+ "STATUS_PREPARATION_FINISHED_ON_UNKNOWN": "Sighting preparation finished on unknown date. ({photoCount} {photoCount, plural, one {photograph} other {photographs}})",
+ "STATUS_PREPARATION_SKIPPED": "Sighting preparation skipped.",
+ "STATUS_PREPARATION_FAILED": "Sighting preparation failed.",
+ "STATUS_DETECTION_STARTED_ON": "Image detection started on {date}.",
+ "STATUS_DETECTION_STARTED_ON_UNKNOWN": "Image detection started on unknown date.",
+ "STATUS_DETECTION_FINISHED_ON": "Image detection finished on {date}.",
+ "STATUS_DETECTION_FINISHED_ON_UNKNOWN": "Image detection finished on unknown date.",
+ "STATUS_DETECTION_SKIPPED": "Image detection skipped.",
+ "STATUS_DETECTION_SKIPPED_NO_IMAGES": "Image detection skipped - no images to annotate.",
+ "STATUS_DETECTION_SKIPPED_NO_MODEL": "Image detection skipped - no detection model was specified.",
+ "STATUS_DETECTION_FAILED": "Image detection failed.",
+ "STATUS_CURATION_CURRENT_EDIT": "All annotations must be assigned to animals before identification can begin.",
+ "STATUS_CURATION_CURRENT_VIEW": "Image curation in progress.",
+ "STATUS_CURATION_CURRENT_ASSIGN_ANNOTATIONS": "Assign annotations",
+ "STATUS_CURATION_CURRENT_ANNOTATE_PHOTOS": "Annotate photographs",
+ "STATUS_CURATION_FINISHED_ON": "Image curation finished on {date}.",
+ "STATUS_CURATION_FINISHED_ON_UNKNOWN": "Image curation finished on unknown date.",
+ "STATUS_CURATION_MIGRATED_FROM_SITE_FINISHED_ON": "Sighting migration from {migratedSiteName} finished on {date}.",
+ "STATUS_CURATION_MIGRATED_FROM_SITE_FINISHED_ON_UNKNOWN": "Sighting migration from {migratedSiteName} finished on unknown date.",
+ "STATUS_CURATION_MIGRATED_FROM_UNKNOWN_SITE_FINISHED_ON": "Sighting migration from a Wildbook database finished on {date}.",
+ "STATUS_CURATION_MIGRATED_FROM_UNKNOWN_SITE_FINISHED_ON_UNKNOWN": "Sighting migration from a Wildbook database finished on unknown date.",
+ "STATUS_CURATION_SKIPPED": "Image curation skipped - no images to curate.",
+ "STATUS_CURATION_FAILED": "Image curation failed.",
+ "STATUS_IDENTIFICATION_STARTED_ON": "Identification started on {date}.",
+ "STATUS_IDENTIFICATION_STARTED_ON_UNKNOWN": "Identification started on unknown date.",
+ "STATUS_IDENTIFICATION_FINISHED_ON": "Identification finished on {date}.",
+ "STATUS_IDENTIFICATION_FINISHED_ON_UNKNOWN": "Identification finished on unknown date.",
+ "STATUS_IDENTIFICATION_FINISHED_VIEW_RESULTS": "View match results",
+ "STATUS_IDENTIFICATION_SKIPPED": "Identification skipped.",
+ "STATUS_IDENTIFICATION_SKIPPED_NO_IMAGES": "Identification skipped - no images to match.",
+ "STATUS_IDENTIFICATION_SKIPPED_MIGRATED_FROM_SITE": "Identification skipped - this sighting was migrated from {migratedSiteName}.",
+ "STATUS_IDENTIFICATION_SKIPPED_MIGRATED_FROM_UNKNOWN_SITE": "Identification skipped - this sighting was migrated from a Wildbook database.",
+ "STATUS_IDENTIFICATION_FAILED": "Identification failed.",
+ "INDIVIDUAL_GALLERY_SEE_ALL": "See all",
+ "INDIVIDUAL_GALLERY_TITLE": "{name}'s gallery",
+ "INDIVIDUAL_GALLERY_IMAGE_ALT": "{annotationCount} {annotationCount, plural, one {annotation} other {annotations}} with {assetClassCount, plural, one {class} other {classes}} {assetClassesList}",
+ "INDIVIDUAL_GALLERY_UNABLE_TO_DISPLAY_ANNOTATIONS": "We are currently unable to show annotations for uploaded images with either a width or a height of 4,096 pixels or more.",
+ "INDIVIDUAL_BACK_TO_PROFILE": "Return to {name}'s profile",
+ "ERROR_FETCHING_IMAGE": "Error fetching image",
+ "COLLABORATION_STATE_VIEW": "View",
+ "COLLABORATION_STATE_EDIT": "Edit",
+ "COLLABORATION_STATE_REVOKED": "Revoked",
+ "EDIT_COLLABORATION": "Edit collaboration",
+ "EDIT_COLLABORATION_CURRENT_STATE_LABEL": "Current state",
+ "EDIT_COLLABORATION_CURRENT_STATE_DESCRIPTION": "Changing the state will cancel any pending requests.",
+ "COLLABORATIONS": "Collaborations",
+ "URLS_MUST_INCLUDE_HTTPS": "All URLs must include https:// to be valid",
+ "ADD_COLLABORATION": "Add collaboration",
+ "EMAIL": "Email",
+ "EDIT_COLLABORATION_REVOKED_BY_USER_MANAGER": "Edit collaboration revoked by a user manager.",
+ "EDIT_COLLABORATION_WAS_REVOKED_BY_A_USER_MANAGER": "An edit-level collaboration with {otherUserNameForManagerNotifications} was revoked by a user manager {managerName}.",
+ "COLLABORATION_EDIT_DENIED": "Collaboration edit denied",
+ "EDIT_COLLABORATION_DENIED_MESSAGE": "{userName} denied your collaboration edit request",
+ "COLLABORATION_DENIED_BY_USER_MANAGER": "Collaboration denied by a user manager",
+ "COLLABORATION_DENIED_BY_USER_MANAGER_MESSAGE": "Your collaboration with {otherUserNameForManagerNotifications} was denied by a user manager {managerName}.",
+ "EDIT_COLLABORATION_DENIED_BY_USER_MANAGER": "Edit-level collaboration denied by a user manager",
+ "EDIT_COLLABORATION_DENIED_BY_USER_MANAGER_MESSAGE": "An edit-level collaboration with {otherUserNameForManagerNotifications} was denied by a user manager {managerName}.",
+ "EDIT_COLLABORATION_APPROVED_BY_USER_MANAGER": "Edit collaboration approved by a user manager",
+ "EDIT_COLLABORATION_WAS_APPROVED_BY_A_USER_MANAGER": "An edit-level collaboration with {otherUserNameForManagerNotifications} was approved by a user manager {managerName}.",
+ "UNNAMED_MANAGER": "Unnamed manager"
}
diff --git a/package.json b/package.json
index c73292333..09e514620 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "codex-frontend",
- "version": "1.0.2",
+ "version": "1.1.0",
"description": "",
"main": "index.js",
"scripts": {
@@ -10,6 +10,7 @@
"lint": "NODE_ENV=development BABEL_ENV=development ./node_modules/.bin/eslint --fix \"src/**/*.{js,jsx}\"",
"format": "./node_modules/.bin/prettier --write \"src/**/*.{js,jsx}\"",
"test": "echo \"Error: no test specified\" && exit 1",
+ "print-unused-keys": "node ./scripts/printUnusedKeys.js",
"clean": "rm -rf dist",
"build": "npm run clean && NODE_ENV=production webpack --config ./config/webpack/webpack.common.js",
"prepare": "husky install"
@@ -68,6 +69,7 @@
"eslint-plugin-react": "~7.28.0",
"eslint-plugin-react-hooks": "~4.3.0",
"file-loader": "^6.2.0",
+ "find-in-files": "^0.5.0",
"html-webpack-plugin": "^5.0.0",
"husky": "^7.0.4",
"lint-staged": ">=8",
diff --git a/scripts/printUnusedKeys.js b/scripts/printUnusedKeys.js
new file mode 100644
index 000000000..6a425e178
--- /dev/null
+++ b/scripts/printUnusedKeys.js
@@ -0,0 +1,45 @@
+const { find } = require('find-in-files');
+const englishTranslations = require('../locale/en.json');
+
+const translationKeys = Object.keys(englishTranslations);
+let unusedCount = 0;
+
+function getLinesWithMatch(lines, substring) {
+ return lines.filter(line => line.includes(substring));
+}
+
+async function printUnusedKeys() {
+ /* eslint-disable */
+ for await (const translationKey of translationKeys) {
+ const results = await find(translationKey, './src');
+ const matches = Object.values(results);
+ const trueMatches = matches.filter(match => {
+ const matchingLines = match?.line || [];
+ const linesWithSingleQuoteMatch = getLinesWithMatch(
+ matchingLines,
+ `"${translationKey}"`,
+ );
+ const linesWithDoubleQuoteMatch = getLinesWithMatch(
+ matchingLines,
+ `\'${translationKey}\'`,
+ );
+ return (
+ linesWithSingleQuoteMatch.length > 0 ||
+ linesWithDoubleQuoteMatch.length > 0
+ );
+ });
+ if (trueMatches.length === 0) {
+ unusedCount += 1;
+ console.log(`"${translationKey}" may be unused.`);
+ }
+ }
+ /* eslint-enable */
+ console.log(
+ `Script finished. ${unusedCount} potentially unused translation keys found.`,
+ );
+}
+
+console.log(
+ 'FYI, this script takes forever to run! Recommendation is to leave it running overnight or in the background.',
+);
+printUnusedKeys();
diff --git a/src/AuthenticatedSwitch.jsx b/src/AuthenticatedSwitch.jsx
index 2b645ddb3..080b6334f 100644
--- a/src/AuthenticatedSwitch.jsx
+++ b/src/AuthenticatedSwitch.jsx
@@ -15,10 +15,13 @@ import ControlPanel from './pages/controlPanel/ControlPanel';
import AssignEncounters from './pages/assignEncounters/AssignEncounters';
import CreateIndividual from './pages/createIndividual/CreateIndividual';
import Individual from './pages/individual/Individual';
+import IndividualGallery from './pages/individualGallery/IndividualGallery';
// import PictureBook from './pages/individual/PictureBook';
import Sighting from './pages/sighting/Sighting';
import AssetGroupSighting from './pages/sighting/AssetGroupSighting';
import Splash from './pages/splash/Splash';
+import SocialGroups from './pages/socialGroups/SocialGroups';
+import SocialGroup from './pages/socialGroups/SocialGroup';
import AssetGroup from './pages/assetGroup/AssetGroup';
import PendingCitizenScienceSightings from './pages/pendingCitizenScienceSightings/PendingCitizenScienceSightings';
import User from './pages/user/User';
@@ -39,7 +42,7 @@ import AuditLog from './pages/devTools/AuditLog';
import Welcome from './pages/auth/Welcome';
import EmailVerified from './pages/auth/EmailVerified';
import Home from './pages/home/Home';
-import Settings from './pages/settings/Settings';
+import Preferences from './pages/preferences/Preferences';
import ResendVerificationEmail from './pages/auth/ResendVerificationEmail';
import Footer from './components/Footer';
import { defaultCrossfadeDuration } from './constants/defaults';
@@ -94,36 +97,42 @@ export default function AuthenticatedSwitch({
) : (
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
+
+
+
-
+
+
+
+
@@ -133,6 +142,9 @@ export default function AuthenticatedSwitch({
{/* */}
+
+
+
@@ -172,8 +184,8 @@ export default function AuthenticatedSwitch({
-
-
+
+
diff --git a/src/components/ActionIcon.jsx b/src/components/ActionIcon.jsx
index 8b1a4cfb1..d4e57db3c 100644
--- a/src/components/ActionIcon.jsx
+++ b/src/components/ActionIcon.jsx
@@ -5,9 +5,9 @@ import IconButton from '@material-ui/core/IconButton';
import EditIcon from '@material-ui/icons/Edit';
import ViewIcon from '@material-ui/icons/Launch';
import DeleteIcon from '@material-ui/icons/Delete';
+import RemoveCircleIcon from '@material-ui/icons/RemoveCircle';
import DownloadIcon from '@material-ui/icons/GetApp';
import CopyIcon from '@material-ui/icons/FileCopy';
-import RestoreIcon from '@material-ui/icons/Restore';
import Link from './Link';
@@ -22,11 +22,7 @@ const variantMap = {
},
revoke: {
labelId: 'MUTUAL_REVOKE',
- component: DeleteIcon,
- },
- restore: {
- labelId: 'RESTORE',
- component: RestoreIcon,
+ component: RemoveCircleIcon,
},
delete: {
labelId: 'DELETE',
diff --git a/src/components/AuthenticatedAppHeader/ActionsPane.jsx b/src/components/AuthenticatedAppHeader/ActionsPane.jsx
index c944a9aca..230d25410 100644
--- a/src/components/AuthenticatedAppHeader/ActionsPane.jsx
+++ b/src/components/AuthenticatedAppHeader/ActionsPane.jsx
@@ -7,7 +7,6 @@ import Popover from '@material-ui/core/Popover';
import MenuItem from '@material-ui/core/MenuItem';
import MenuList from '@material-ui/core/MenuList';
import Divider from '@material-ui/core/Divider';
-import SettingsIcon from '@material-ui/icons/Settings';
import PublicIcon from '@material-ui/icons/SupervisedUserCircle';
import ControlPanelIcon from '@material-ui/icons/PermDataSetting';
import BulkImportIcon from '@material-ui/icons/PostAdd';
@@ -27,22 +26,13 @@ const actions = [
{
id: 'pending-citizen-science-sightings',
href: '/pending-citizen-science-sightings',
- permissionsTest: userData =>
- userData?.is_admin || userData?.is_data_manager,
+ permissionsTest: userData => userData?.is_admin,
messageId: 'PENDING_CITIZEN_SCIENCE_SIGHTINGS',
icon: PublicIcon,
},
- {
- id: 'settings',
- href: '/settings',
- messageId: 'SETTINGS_AND_PRIVACY',
- icon: SettingsIcon,
- },
{
id: 'control-panel',
- href: '/admin',
- permissionsTest: userData =>
- userData?.is_admin || userData?.is_user_manager,
+ href: '/settings',
messageId: 'CONTROL_PANEL',
icon: ControlPanelIcon,
},
diff --git a/src/components/AuthenticatedAppHeader/NotificationPaneDisplayText.jsx b/src/components/AuthenticatedAppHeader/NotificationPaneDisplayText.jsx
index 65cecfedd..886c60e20 100644
--- a/src/components/AuthenticatedAppHeader/NotificationPaneDisplayText.jsx
+++ b/src/components/AuthenticatedAppHeader/NotificationPaneDisplayText.jsx
@@ -15,6 +15,9 @@ export default function NotificationPaneDisplayText({
theirIndividualName,
theirIndividualGuid,
formattedDeadline,
+ otherUserGuidForManagerNotifications,
+ otherUserNameForManagerNotifications,
+ managerName,
timeSince,
}) {
const theme = useTheme();
@@ -54,6 +57,17 @@ export default function NotificationPaneDisplayText({
),
formattedDeadline,
+ otherUserNameForManagerNotifications: (
+
+
+ {otherUserNameForManagerNotifications}
+
+
+ ),
+ managerName,
}}
/>
diff --git a/src/components/AuthenticatedAppHeader/index.js b/src/components/AuthenticatedAppHeader/index.js
index f13247f23..0aed4a728 100644
--- a/src/components/AuthenticatedAppHeader/index.js
+++ b/src/components/AuthenticatedAppHeader/index.js
@@ -142,6 +142,7 @@ export default function AppHeader() {
refreshNotifications={refreshNotifications}
shouldOpen={shouldOpenNotificationPane}
setShouldOpen={setShouldOpenNotificationPane}
+ currentUserGuid={meData?.guid}
/>
setUserMenuAnchorEl(e.currentTarget)}
diff --git a/src/components/BannerLogo.jsx b/src/components/BannerLogo.jsx
index 3cfcf5449..96d6c1269 100644
--- a/src/components/BannerLogo.jsx
+++ b/src/components/BannerLogo.jsx
@@ -41,11 +41,13 @@ export default function BannerLogo({
>