diff --git a/.env.sample b/.env.sample index ea2c227dd..8aec51422 100644 --- a/.env.sample +++ b/.env.sample @@ -1,2 +1,3 @@ BACKUP_ZIP_PASSWORD= SB_DOMAIN=sb.localhost +EBP_API_TOKEN= diff --git a/compose.yml b/compose.yml index 46637fc87..4e52c8fc5 100644 --- a/compose.yml +++ b/compose.yml @@ -41,6 +41,11 @@ services: build: . entrypoint: ./entrypoint.sh command: start + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" # env_file: # - sb-server.env environment: @@ -51,6 +56,7 @@ services: REDIS_HOST: redis REDIS_PORT: 6379 REDIS_DB: 0 + EBP_API_TOKEN: ${EBP_API_TOKEN} depends_on: redis: condition: service_healthy diff --git a/i18n/bg.json b/i18n/bg.json index 206185ded..29ec43a21 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -41,17 +41,17 @@ "PUBLIC_HOME_ABOUT_TITLE": "За проекта", "PUBLIC_HOME_ABOUT_BODY": "SmartBirds.org e електронна база данни разработена от {{bspbLinkStart}}Българско дружество за защита на птиците{{bspbLinkEnd}}. Целта и е да служи като електронен бележник за съхранение на записи от наблюдения на птици, бозайници, земноводни, влечуги, защитени и други безгръбначни животни. Записването на всяко едно наблюдение подпомага природозащитните дейности в България. Затова записвайте наблюденията си директно тук в страницата или чрез мобилното приложение {{mobileLinkStart}}SmartBirds Pro{{mobileLinkEnd}}. Ако обичате природата и желаете да се включите в инициативата за преброяване на птиците около нас, регистрирайте се в SmartBirds {{registerLinkStart}}тук{{registerLinkEnd}} и изберете своето лично място за броене на птици.", "PUBLIC_HOME_FUNDING_TITLE": "Финансиране", - "PUBLIC_HOME_FUNDING_BODY1": "Модулът за Мониторинг на обикновените видове птици (МОВП) към портала SmartBirds.org е финансиран по проект „Птиците за хората и хората за птиците: с добра воля за активно опазване на природата” в рамките на {{linkStart}}Програми BG03 в България{{linkEnd}} по Финансовия механизъм на Европейското икономическо пространство 2009–2014 г.", - "PUBLIC_HOME_FUNDING_BODY2": "Останалите модули на SmartBirds.org са осъществени в рамките на проект {{linkStart}}„Опазване на ключови горски хабитати на малкия креслив орел в България”{{linkEnd}} (LIFE12 NAT/BG/001218), финансиран от програма LIFE+ на Европейския съюз.", + "PUBLIC_HOME_FUNDING_BODY1": "Информационна система с биологична информация SmartBirds.org и мобилните приложения {{linkSmartBirdsProStart}}SmartBirds Pro{{linkEnd}} и {{linkSmartBirdsStart}}SmartBirds{{linkEnd}} са разработени и обновявани от БДЗП с финансовата подкрепа на:", + "PUBLIC_HOME_FUNDING_BODY2": "Част от модулите на SmartBirds.org и SmartBirds Pro са разработени в партньорство с {{linkNMNHSStart}}Национален природонаучен музей при БАН{{linkEnd}}.\nИнформационна система с биологична информация на БДЗП е част от {{linkEuroBirdPortalStart}}EuroBirdPortal{{linkEnd}} – инициатива на {{linkEuroBirdCensusStart}}European Bird Census Council{{linkEnd}}.", "PUBLIC_BIRDS_TITLE": "Форма Птици", "PUBLIC_BIRDS_SUBTITLE": "Наблюдения от стандартна форма птици", - "PUBLIC_BIRDS_TEXT": "Тази част от SmartBirds.org е осъществена в рамките на проект {{linkStart}}„Опазване на ключови горски хабитати на малкия креслив орел в България”{{linkEnd}} (LIFE12 NAT/BG/001218), финансиран от програма LIFE+ на Европейския съюз.", + "PUBLIC_BIRDS_TEXT": "Тази част от SmartBirds.org е осъществена с финансовата подкрепа на Програма LIFE на Европейския съюз.", "PUBLIC_CBM_TITLE": "МОВП", "PUBLIC_CBM_SUBTITLE": "Площадки за мониторинг на обикновените видове птици", - "PUBLIC_CBM_TEXT": "Модулът за Мониторинг на обикновените видове птици (МОВП) към портала SmartBirds.org е финансиран по проект „Птиците за хората и хората за птиците: с добра воля за активно опазване на природата” в рамките на {{linkStart}}Програми BG03{{linkEnd}} в България по Финансовия механизъм на Европейското икономическо пространство 2009–2014 г.", + "PUBLIC_CBM_TEXT": "Модулът за Мониторинг на обикновените видове птици (МОВП) към портала SmartBirds.org е финансиран по проект „Птиците за хората и хората за птиците: с добра воля за активно опазване на природата” в рамките на {{linkStart}}Програми BG03{{linkEnd}} в България по Финансовия механизъм на Европейското икономическо пространство 2009–2014 г. и проект „Осъществяване на схемата за мониторинг на обикновените видове птици, като част от Националната система за мониторинг на биологичното разнообразие“, финансиран от ПУДООС.", "PUBLIC_CICONIA_TITLE": "Форма бял щъркел", "PUBLIC_CICONIA_SUBTITLE": "Наблюдения на бял щъркел", - "PUBLIC_CICONIA_TEXT": "Тази част от SmartBirds.org е осъществена в рамките на проект {{linkStart}}„Опазване на ключови горски хабитати на малкия креслив орел в България”{{linkEnd}} (LIFE12 NAT/BG/001218), финансиран от програма LIFE+ на Европейския съюз.", + "PUBLIC_CICONIA_TEXT": "Тази част от SmartBirds.org е осъществена с финансовата подкрепа на Програма LIFE на Европейския съюз. Модулът е подпомогнат и в рамките на проект \"SmartStorkBelozem - развитие на гражданска наука за щъркелите в Белозем - Европейско село на щъркелите в България\", финансиран от фондация EuroNatur, Германия.", "PUBLIC_HERP_TITLE": "Форма ЗВБ", "PUBLIC_HERP_SUBTITLE": "Наблюдения на земноводни, влечуги и бозайници", "PUBLIC_HERP_TEXT": "Тази част от SmartBirds.org е осъществена в рамките на проект {{linkStart}}„Опазване на ключови горски хабитати на малкия креслив орел в България”{{linkEnd}} (LIFE12 NAT/BG/001218), финансиран от програма LIFE+ на Европейския съюз.", @@ -134,7 +134,7 @@ "CBM_LIST_TITLE_SECONDARY": "Mониторинг на обикновените видове птици", "CBM_LIST_BUTTON_DELETE": "Изтриване", "CBM_LIST_BUTTON_NEW_ENTRY": "Ново наблюдение", - "CBM_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} записа", + "CBM_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} запис(а)", "CBM_LIST_FOUND_RECORDS_COUNT_TOOLTIP": "Намерени записи отговарящи на избраните филтри", "CBM_LIST_BUTTON_CSV_EXPORT_TOOLTIP": "Генериране на csv файл с всички намерени записи", "CBM_LIST_BUTTON_ZIP_EXPORT_TOOLTIP": "Генериране на zip файл с всички намерени записи, техните снимки и gpx файлове", @@ -194,7 +194,7 @@ "BIRDS_LIST_TITLE_SECONDARY": "Наблюдения от стандартна форма птици", "BIRDS_LIST_BUTTON_DELETE": "Изтриване", "BIRDS_LIST_BUTTON_NEW_ENTRY": "Ново наблюдение", - "BIRDS_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} записа", + "BIRDS_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} запис(а)", "BIRDS_LIST_FOUND_RECORDS_COUNT_TOOLTIP": "Намерени записи отговарящи на избраните филтри", "BIRDS_LIST_BUTTON_CSV_EXPORT_TOOLTIP": "Генериране на csv файл с всички намерени записи", "BIRDS_LIST_BUTTON_ZIP_EXPORT_TOOLTIP": "Генериране на zip файл с всички намерени записи, техните снимки и gpx файлове", @@ -220,7 +220,6 @@ "BIRDS_DETAIL_TITLE_NEW": "Ново наблюдение", "BIRDS_DETAIL_TITLE_EDIT": "Редакция на запис", "BIRDS_DETAIL_SPECIES": "Вид научно име", - "BIRDS_DETAIL_CONFIDENTIAL": "Поверително", "BIRDS_DETAIL_LATITUDE": "Географска ширина", "BIRDS_DETAIL_LONGITUDE": "Географска дължина", "BIRDS_DETAIL_COUNT_UNIT": "Единица на броя", @@ -269,7 +268,7 @@ "HERP_LIST_TITLE_SECONDARY": "Наблюдения на земноводни, влечуги и бозайници", "HERP_LIST_BUTTON_DELETE": "Изтриване", "HERP_LIST_BUTTON_NEW_ENTRY": "Ново наблюдение", - "HERP_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} записа", + "HERP_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} запис(а)", "HERP_LIST_FOUND_RECORDS_COUNT_TOOLTIP": "Намерени записи отговарящи на избраните филтри", "HERP_LIST_BUTTON_CSV_EXPORT_TOOLTIP": "Генериране на csv файл с всички намерени записи", "HERP_LIST_BUTTON_ZIP_EXPORT_TOOLTIP": "Генериране на zip файл с всички намерени записи, техните снимки и gpx файлове", @@ -296,8 +295,8 @@ "HERP_DETAIL_TITLE_EDIT": "Редакция на запис", "HERP_DETAIL_LATITUDE": "Географска ширина", "HERP_DETAIL_LONGITUDE": "Географска дължина", - "HERP_DETAIL_SPECIES": "Вид научно име", - "HERP_DETAIL_COUNT": "Брой", + "FIELD_SPECIES": "Вид научно име", + "FIELD_COUNT": "Брой", "HERP_DETAIL_AGE": "Възраст", "HERP_DETAIL_SEX": "Пол", "HERP_DETAIL_HABITAT": "Местообитание", @@ -319,7 +318,7 @@ "CICONIA_LIST_TITLE_SECONDARY": "Наблюдения на бял щъркел", "CICONIA_LIST_BUTTON_DELETE": "Изтриване", "CICONIA_LIST_BUTTON_NEW_ENTRY": "Ново наблюдение", - "CICONIA_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} записа", + "CICONIA_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} запис(а)", "CICONIA_LIST_FOUND_RECORDS_COUNT_TOOLTIP": "Намерени записи отговарящи на избраните филтри", "CICONIA_LIST_BUTTON_CSV_EXPORT_TOOLTIP": "Генериране на csv файл с всички намерени записи", "CICONIA_LIST_BUTTON_ZIP_EXPORT_TOOLTIP": "Генериране на zip файл с всички намерени записи, техните снимки и gpx файлове", @@ -464,12 +463,12 @@ "FORM_MAMMALS_SHORT": "Бозайници", "PUBLIC_HERPTILES_TITLE": "Форма З и В", "PUBLIC_HERPTILES_SUBTITLE": "Наблюдения на земноводни и влечуги", - "PUBLIC_HERPTILES_TEXT": "Тази част от SmartBirds.org е осъществена в рамките на проект {{linkStart}}„Опазване на ключови горски хабитати на малкия креслив орел в България”{{linkEnd}} (LIFE12 NAT/BG/001218), финансиран от програма LIFE+ на Европейския съюз.", + "PUBLIC_HERPTILES_TEXT": "Тази част от www.SmartBirds.org е осъществена с финансовата подкрепа на Програма LIFE на Европейския съюз.", "PUBLIC_HERPTILES_MAP_SPECIES": "видове", "PUBLIC_HERPTILES_MAP_INDIVIDUALS": "индивиди", "PUBLIC_MAMMALS_TITLE": "Форма Бозайници", "PUBLIC_MAMMALS_SUBTITLE": "Наблюдения на бозайници", - "PUBLIC_MAMMALS_TEXT": "Тази част от SmartBirds.org е осъществена в рамките на проект {{linkStart}}„Опазване на ключови горски хабитати на малкия креслив орел в България”{{linkEnd}} (LIFE12 NAT/BG/001218), финансиран от програма LIFE+ на Европейския съюз.", + "PUBLIC_MAMMALS_TEXT": "Тази част от www.SmartBirds.org е осъществена с финансовата подкрепа на Програма LIFE на Европейския съюз.", "PUBLIC_MAMMALS_MAP_SPECIES": "видове", "PUBLIC_MAMMALS_MAP_INDIVIDUALS": "индивиди", "DASHBOARD_CARD_HERPTILES": "З и В", @@ -478,7 +477,7 @@ "HERPTILES_LIST_TITLE_SECONDARY": "Наблюдения на земноводни и влечуги", "HERPTILES_LIST_BUTTON_DELETE": "Изтриване", "HERPTILES_LIST_BUTTON_NEW_ENTRY": "Ново наблюдение", - "HERPTILES_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} записа", + "HERPTILES_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} запис(а)", "HERPTILES_LIST_FOUND_RECORDS_COUNT_TOOLTIP": "Намерени записи отговарящи на избраните филтри", "HERPTILES_LIST_BUTTON_CSV_EXPORT_TOOLTIP": "Генериране на csv файл с всички намерени записи", "HERPTILES_LIST_BUTTON_ZIP_EXPORT_TOOLTIP": "Генериране на zip файл с всички намерени записи, техните снимки и gpx файлове", @@ -528,7 +527,7 @@ "MAMMALS_LIST_TITLE_SECONDARY": "Наблюдения на бозайници", "MAMMALS_LIST_BUTTON_DELETE": "Изтриване", "MAMMALS_LIST_BUTTON_NEW_ENTRY": "Ново наблюдение", - "MAMMALS_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} записа", + "MAMMALS_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} запис(а)", "MAMMALS_LIST_FOUND_RECORDS_COUNT_TOOLTIP": "Намерени записи отговарящи на избраните филтри", "MAMMALS_LIST_BUTTON_CSV_EXPORT_TOOLTIP": "Генериране на csv файл с всички намерени записи", "MAMMALS_LIST_BUTTON_ZIP_EXPORT_TOOLTIP": "Генериране на zip файл с всички намерени записи, техните снимки и gpx файлове", @@ -625,20 +624,20 @@ "DOWNLOAD_TITLE": "Свалени данни", "You will be notified by email when your export is ready": "Ще бъдете уведомени с емайл, когато експортът Ви е готов.", "Error during export": "Грешка по време на експорт", - "FORM_INVERTEBRATES_LONG": "Защитени безгръбначни", - "FORM_INVERTEBRATES_SHORT": "Защитени безгръбначни", - "FORM_LABEL_INVERTEBRATES": "Защитени безгръбначни", - "DASHBOARD_CARD_INVERTEBRATES": "Защитени безгръбначни", - "PUBLIC_INVERTEBRATES_TITLE": "Форма Защитени безгръбначни", - "PUBLIC_INVERTEBRATES_SUBTITLE": "Наблюдения на защитени безгръбначни", - "PUBLIC_INVERTEBRATES_TEXT": "Тази част от SmartBirds.org е осъществена в рамките на проект {{linkStart}}„Опазване на ключови горски хабитати на малкия креслив орел в България”{{linkEnd}} (LIFE12 NAT/BG/001218), финансиран от програма LIFE+ на Европейския съюз.", + "FORM_INVERTEBRATES_LONG": "Безгръбначни животни", + "FORM_INVERTEBRATES_SHORT": "Безгръбначни животни", + "FORM_LABEL_INVERTEBRATES": "Безгръбначни животни", + "DASHBOARD_CARD_INVERTEBRATES": "Безгръбначни животни", + "PUBLIC_INVERTEBRATES_TITLE": "Форма Безгръбначни животни", + "PUBLIC_INVERTEBRATES_SUBTITLE": "Наблюдения на безгръбначни животни", + "PUBLIC_INVERTEBRATES_TEXT": "Тази част от www.SmartBirds.org е осъществена в партньорство с Националния природонаучен музей при БАН и с финансовата подкрепа на Програма LIFE на Европейския съюз.", "PUBLIC_INVERTEBRATES_MAP_SPECIES": "видове", "PUBLIC_INVERTEBRATES_MAP_INDIVIDUALS": "индивиди", - "INVERTEBRATES_LIST_TITLE_MAIN": "Форма Защитени безгръбначни", - "INVERTEBRATES_LIST_TITLE_SECONDARY": "Наблюдения на защитени безгръбначни", + "INVERTEBRATES_LIST_TITLE_MAIN": "Форма Безгръбначни животни", + "INVERTEBRATES_LIST_TITLE_SECONDARY": "Наблюдения на безгръбначни животни", "INVERTEBRATES_LIST_BUTTON_DELETE": "Изтриване", "INVERTEBRATES_LIST_BUTTON_NEW_ENTRY": "Ново наблюдение", - "INVERTEBRATES_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} записа", + "INVERTEBRATES_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} запис(а)", "INVERTEBRATES_LIST_FOUND_RECORDS_COUNT_TOOLTIP": "Намерени записи отговарящи на избраните филтри", "INVERTEBRATES_LIST_BUTTON_CSV_EXPORT_TOOLTIP": "Генериране на csv файл с всички намерени записи", "INVERTEBRATES_LIST_BUTTON_ZIP_EXPORT_TOOLTIP": "Генериране на zip файл с всички намерени записи, техните снимки и gpx файлове", @@ -686,7 +685,7 @@ "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES": "Наблюдавани интересни видове за последните 30 дни", "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES_COL_SPECIES": "Вид", "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES_COL_DATE": "Дата", - "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES_COL_LOCATION": "Място", + "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES_COL_LOCATION": "Селище", "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES_COL_COUNT": "Брой", "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Наблюдател", "USER_DETAIL_PRIVACY": "Публични данни и статистика", @@ -698,9 +697,6 @@ "WARNING_EXPORT_LIMIT": "Системата ограничава единичен експорт до 20 000 записа", "SPECIES_COL_INTERESTING": "Интересен", "SPECIES_COL_SENSITIVE": "Чувствителен", - "HERPTILES_DETAIL_CONFIDENTIAL": "Поверително", - "MAMMALS_DETAIL_CONFIDENTIAL": "Поверително", - "INVERTEBRATES_DETAIL_CONFIDENTIAL": "Поверително", "WARNING_EXPORT_DISABLED": "Експортът на данни от други потребители не е разрешен", "NAV_FRIENDS": "Приятели", "FRIENDS_TITLE": "Модул за управление на приятели", @@ -714,7 +710,6 @@ "FRIENDS_UNSAVED_DATA_ALERT_MESSAGE": "Имате незаписани промени. Сигурни ли сте, че искате да продължите?", "FRIENDS_SAVE_SUCCESS": "Приятелите са успешно добавени", "FRIENDS_SAVE_ERROR": "Грешка при добавяне на приятели", - "CBM_DETAIL_CONFIDENTIAL": "Поверително", "PUBLIC_BIRDS_STATISTIC_TABLE_COL_COUNT_SPECIES": "{{count}} вида", "PUBLIC_BIRDS_STATISTIC_TABLE_COL_COUNT_RECORDS": "{{count}} записа", "NAV_PRIVATE_FORMS": "Лични и споделени данни", @@ -727,8 +722,8 @@ "HERPTILES_PUBLIC_LIST_TITLE_SECONDARY": "Наблюдения на земноводни и влечуги", "MAMMALS_PUBLIC_LIST_TITLE_MAIN": "Форма Бозайници", "MAMMALS_PUBLIC_LIST_TITLE_SECONDARY": "Наблюдения на бозайници", - "INVERTEBRATES_PUBLIC_LIST_TITLE_MAIN": "Форма Защитени безгръбначни", - "INVERTEBRATES_PUBLIC_LIST_TITLE_SECONDARY": "Наблюдения на защитени безгръбначни", + "INVERTEBRATES_PUBLIC_LIST_TITLE_MAIN": "Форма Безгръбначни животни", + "INVERTEBRATES_PUBLIC_LIST_TITLE_SECONDARY": "Наблюдения на безгръбначни животни", "CICONIA_PUBLIC_LIST_TITLE_MAIN": "Форма бял щъркел", "CICONIA_PUBLIC_LIST_TITLE_SECONDARY": "Наблюдения на бял щъркел", "PUBLIC_LIST_FILTER_USER": "Наблюдател", @@ -749,7 +744,7 @@ "DASHBOARD_CARD_PLANTS": "Защитени растения", "FORM_PLANTS_LONG": "Защитени растения", "FORM_PLANTS_SHORT": "Защитени растения", - "PLANTS_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} записа", + "PLANTS_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} запис(а)", "PLANTS_LIST_BUTTON_DELETE": "Изтриване", "PLANTS_LIST_BUTTON_NEW_ENTRY": "Ново наблюдение", "PLANTS_LIST_TITLE_MAIN": "Форма Защитени растения", @@ -778,7 +773,6 @@ "PLANTS_DETAIL_UNSAVED_DATA_ALERT_TITLE": "Имате незаписани промени!", "PLANTS_DETAIL_UNSAVED_DATA_ALERT_MESSAGE": "Имате незаписани промени. Сигурни ли сте че искате да продължите?", "PLANTS_DETAIL_SPECIES": "Вид научно име", - "PLANTS_DETAIL_CONFIDENTIAL": "Поверително", "PLANTS_DETAIL_ELEVATION": "Надморска височина", "PLANTS_DETAIL_HABITAT": "Местообитание", "PLANTS_DETAIL_REPORTING_UNIT": "Отчетна единица", @@ -789,7 +783,7 @@ "PLANTS_DETAIL_ACCOMPANYING_SPECIES": "Съпътстващи видове", "PUBLIC_PLANTS_TITLE": "Форма защитени растения", "PUBLIC_PLANTS_SUBTITLE": "Наблюдения на защитени растения", - "PUBLIC_PLANTS_TEXT": "Защитените растения са част от Smartbirds.org", + "PUBLIC_PLANTS_TEXT": "Тази част от www.SmartBirds.org е осъществена в партньорство с Националния природонаучен музей при БАН и с финансовата подкрепа на Програма LIFE на Европейския съюз.", "PUBLIC_PLANTS_MAP_SPECIES": "видове", "PUBLIC_PLANTS_MAP_INDIVIDUALS": "индивиди", "LOGO_NMNHS_ALT": "НПМ-БАН", @@ -841,8 +835,8 @@ "USER_LIST_TABLE_GDPR_CONSENT": "GDPR", "USER_DETAIL_GDPR_CONSENT_GRANTED": "Разрешено", "USER_DETAIL_GDPR_CONSENT_NOT_GRANTED": "Неразрешено", - "CAMPAIGN1_HEADER": "Отброяваме заедно до милионния запис!", - "CAMPAIGN1_SUBHEADER": "Спечели таблет! Направи милионния запис!", + "CAMPAIGN1_HEADER": "Отброяваме заедно до двумилионния запис!", + "CAMPAIGN1_SUBHEADER": "Спечели ваучер за магазини Стената! Направи двумилионния запис!", "CAMPAIGN1_BUTTON": "РЕГИСТРИРАЙ СЕ!", "CAMPAIGN1_TEXT": "Записвай данни!", "PUBLIC_HERPTILES_TOP_OBSERVERS_RECORDS_MONTH": "Най-много въведени наблюдения за последните 30 дни", @@ -855,7 +849,7 @@ "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES": "Наблюдавани интересни видове за последните 30 дни", "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES_COL_SPECIES": "Вид", "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES_COL_DATE": "Дата", - "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES_COL_LOCATION": "Място", + "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES_COL_LOCATION": "Селище", "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES_COL_COUNT": "Брой", "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Наблюдател", "PUBLIC_MAMMALS_TOP_OBSERVERS_RECORDS_MONTH": "Най-много въведени наблюдения за последните 30 дни", @@ -868,7 +862,7 @@ "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES": "Наблюдавани интересни видове за последните 30 дни", "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES_COL_SPECIES": "Вид", "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES_COL_DATE": "Дата", - "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES_COL_LOCATION": "Място", + "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES_COL_LOCATION": "Селище", "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES_COL_COUNT": "Брой", "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Наблюдател", "PUBLIC_INVERTEBRATES_TOP_OBSERVERS_RECORDS_MONTH": "Най-много въведени наблюдения за последните 30 дни", @@ -881,7 +875,7 @@ "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES": "Наблюдавани интересни видове за последните 30 дни", "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES_COL_SPECIES": "Вид", "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES_COL_DATE": "Дата", - "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES_COL_LOCATION": "Място", + "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES_COL_LOCATION": "Селище", "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES_COL_COUNT": "Брой", "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Наблюдател", "PUBLIC_PLANTS_TOP_OBSERVERS_RECORDS_MONTH": "Най-много въведени наблюдения за последните 30 дни", @@ -894,7 +888,7 @@ "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES": "Наблюдавани интересни видове за последните 30 дни", "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES_COL_SPECIES": "Вид", "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES_COL_DATE": "Дата", - "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES_COL_LOCATION": "Място", + "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES_COL_LOCATION": "Селище", "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES_COL_COUNT": "Брой", "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Наблюдател", "PUBLIC_STATISTIC_TABLE_COL_COUNT_SPECIES": "{{count}} вида", @@ -908,7 +902,7 @@ "STATS_MAMMALS_SUBTITLE": "Статистики за наблюденията", "STATS_PLANTS_TITLE": "Защитени растения", "STATS_PLANTS_SUBTITLE": "Статистики за наблюденията", - "STATS_INVERTEBRATES_TITLE": "Защитени безгръбначни", + "STATS_INVERTEBRATES_TITLE": "Безгръбначни животни", "STATS_INVERTEBRATES_SUBTITLE": "Статистики за наблюденията", "STATS_INTERESTING_SPECIES_TITLE_TAB": "Интересни видове", "STATS_TOP_SPECIES_MONTH_TITLE_TAB": "Най-често наблюдавани видове", @@ -942,7 +936,7 @@ "DASHBOARD_CARD_THREATS": "Заплахи", "FORM_THREATS_LONG": "Заплахи", "FORM_THREATS_SHORT": "Заплахи", - "THREATS_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} записа", + "THREATS_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} запис(а)", "THREATS_LIST_BUTTON_DELETE": "Изтриване", "THREATS_LIST_BUTTON_NEW_ENTRY": "Ново наблюдение", "THREATS_LIST_TITLE_MAIN": "Форма Заплахи", @@ -1004,6 +998,363 @@ "THREATS_LIST_FILTER_CATEGORY": "Категория", "PUBLIC_THREATS_TITLE": "Форма заплахи", "PUBLIC_THREATS_SUBTITLE": "Наблюдения на заплахи", - "PUBLIC_THREATS_TEXT": "Заплахите са част от Smartbirds.org", - "FORM_LABEL_THREATS": "Заплахи" -} \ No newline at end of file + "PUBLIC_THREATS_TEXT": "Тази част от SmartBirds.org е осъществена в рамките на проект {{linkStart}}„Нова Надежда За Египетския Лешояд”{{linkEnd}} (LIFE16 NAT/BG/000874), финансиран от програма LIFE+ на Европейския съюз и ко-финасиран от фондация „Левентис”.", + "FORM_LABEL_THREATS": "Заплахи", + "LOGO_LIFENEOPHRON_ALT": "Програма Life+", + "LOGO_BIRDLIFE_ALT": "Birdlife International", + "LOGO_LEVENTIS_ALT": "Фонадация \"Левентис\"", + "LANGUAGE_ALBANIAN": "Албански", + "LANGUAGE_MACEDONIAN": "Македонски", + "PUBLIC_THREATS_FILTER_BY_THREAT": "Заплаха:", + "PUBLIC_THREATS_FILTER_ALL_THREATS": "- всички заплахи -", + "PUBLIC_THREATS_FILTER_BY_DATE": "Период:", + "PUBLIC_THREATS_FILTER_ALL_TIME": "- за целия период -", + "PUBLIC_THREATS_FILTER_1_MONTH": "последния месец", + "PUBLIC_THREATS_FILTER_3_MONTHS": "последните 3 месеца", + "PUBLIC_THREATS_FILTER_6_MONTHS": "последните 6 месеца", + "PUBLIC_THREATS_FILTER_1_YEAR": "последната година", + "NAV_ORGANIZATIONS": "Организации", + "ORGANIZATIONS_TITLE": "Организации", + "ORGANIZATIONS_BUTTON_SAVE": "Запис", + "CHANGE_ORGANIZATION_WARNING_MESSAGE": "Промяната на организация ще смени ролята на наблюдател", + "LIST_TABLE_ORGANIZATION": "Организация", + "LIST_FILTER_ORGANIZATION": "Организация", + "USER_DETAIL_ORGANIZATION": "Организация", + "LIST_FILTER_SETTLEMENT": "Селище", + "LIST_TABLE_SETTLEMENT": "Селище", + "DASHBOARD_CARD_RETRY": "Повторен опит", + "DASHBOARD_CARD_MODERATOR_REVIEW": "за преглед", + "FIELD_MODERATOR_REVIEW": "За проверка от модератор", + "FIELD_MODERATOR_REVIEW_HELP": "Отбележете ако желаете записа да бъде прегледан от модератор. Необходима е снимка!", + "LIST_FILTER_MODERATOR_REVIEW": "За проверка от модератор", + "FIELD_CONFIDENTIAL": "Поверително", + "FORM_DETAIL_BUTTON_CONFIRM": "Потвърждаване", + "SETTLEMENT_VALUE_UNKNOWN": "Неизвестно", + "SETTLEMENT_VALUE_PENDING": "Обработва се…", + "NAV_ATLAS": "Атлас", + "NAV_ATLAS_DASHBOARD": "Преглед", + "NAV_ATLAS_REQUEST": "Избор на квадрати", + "ATLAS_DASHBOARD_TITLE": "Преглед данните по квадрати", + "ATLAS_REQUEST_TITLE": "Избор на квадрати", + "ATLAS_REQUEST_SUBTITLE": "Изберете квадратите, които искате да проучите. По възможност, предпочетете по-слабо проучени. Може да изберете до 10 квадрата.", + "ATLAS_REQUEST_BTN_SAVE": "Запис", + "ATLAS_REQUEST_ZONES_HEADING": "Избрани квадрати. За всеки избран квадрат е посочено колко процента от видовете установени в рамките на предния атлас (Янков, 2007) са установени след 2016г.", + "ATLAS_REQUEST_ZONES_TEXT": "Избрани квадрати (10 X 10 km) за атласно проучване.", + "ATLAS_LEGEND_LOW": "Слабо проучен", + "ATLAS_LEGEND_MEDIUM": "Средно проучен", + "ATLAS_LEGEND_HIGH": "Силно проучен", + "ATLAS_REQUEST_ZONES_HINT": "Изберете желаните от вас квадрати.", + "ATLAS_DASHBOARD_ZONE_HEADING": "Информация за квадрат", + "ATLAS_DASHBOARD_ZONE_HEADER_SPECIES": "Вид", + "ATLAS_DASHBOARD_ZONE_HEADER_ATLAS": "Атлас 2007", + "ATLAS_DASHBOARD_ZONE_HEADER_OBSERVATION": "Лични наблюдения", + "ATLAS_DASHBOARD_ZONE_TEXT": "Съпоставка на видове установени в квадрата през предишен период според Атласа на гнездящите птици в България (Янков, 2007), видовете установени от потребителя и всички видове установени в квадрата след 01.01.2016 г.", + "ATLAS_DASHBOARD_ZONE_NO_ROWS": "Няма известни видове птици нито в атласа нито в наблюденията на потребителя", + "PUBLIC_ATLAS_TITLE": "Атлас на птиците", + "PUBLIC_ATLAS_SUBTITLE": "Степен на проученост след 01.01.2016 г. (UTM грид 10 x 10 km)", + "PUBLIC_ATLAS_PAGE_TITLE": "Атлас на птиците", + "ATLAS_DASHBOARD_ZONE_HEADER_OTHER": "Всички наблюдения", + "PUBLIC_ATLAS_TEXT": "Този модул е разработен в рамките на проект „Наука на гражданите в полза на местните общности и природата“ с финансова подкрепа на {{linkStart}} фонд Активни граждани {{linkEnd}} по линия на Финансовия механизъм на ЕИП.“", + "PUBLIC_ATLAS_LONG": "Атлас на гнездящите птици в България", + "PUBLIC_ATLAS_SHORT": "Атлас", + "ATLAS_STATS_TOP_OBSERVERS_HEADING": "Изберете квадрат от картата", + "ATLAS_STATS_TOP_OBSERVERS_ROW_VALUE": "{{ species }} вида", + "ATLAS_STATS_TOP_OBSERVERS_TEXT": "Общ брой видове: {{ species }} вида", + "ATLAS_STATS_TOP_OBSERVERS_HINT": "Изберете квадрата от картата за да видите информация за него", + "ATLAS_STATS_TITLE": "Атлас статистики", + "ATLAS_STATS_SUBTITLE": "Наблюдатели въвели данни за най-голям брой УТМ (10х10 km) квадрати", + "ATLAS_STATS_TOP_OBSERVERS": "Най-много наблюдения на квадрат", + "ATLAS_STATS_TOP_OBSERVERS_TITLE_TAB": "Наблюдатели по квадрати", + "ATLAS_STATS_RANKING_LABEL_COUNT": "{{ count }} квадрата", + "ATLAS_STATS_RANKING_TITLE_TAB": "Най-много квадрати", + "ATLAS_STATS_RANKING": "Най-много посетени квадрати от наблюдател", + "ATLAS_MISSING_SPECIES_TITLE": "Неустановени видове по квадрати", + "NAV_ATLAS_MISSING_SPECIES": "Неустановени видове", + "ATLAS_MISSING_SPECIES_ZONE_HEADING": "Информация за неустановени видове", + "ATLAS_MISSING_SPECIES_ZONE_TEXT": "Изберете квадрат от картата за да видите неустановените видове", + "ATLAS_MISSING_SPECIES_ZONE_HEADER_SPECIES": "Вид", + "ATLAS_MISSING_SPECIES_ZONE_NO_ROWS": "Липсват неустановени видове за този квадрат", + "FORM_ATLAS_SHORT": "Атлас Птици", + "LANGUAGE_GREEK": "Гръцки", + "LANGUAGE_TURKISH": "Турски", + "LANGUAGE_ARABIC": "Арабски", + "LANGUAGE_FRENCH": "Френски", + "PUBLIC_HOME_FUNDING_LIST": "
  • {{linkLIFEStart}}Програма LIFE на Европейския съюз{{linkEnd}}\n
  • {{linkUS4BGStart}}Фондация Америка за България{{linkEnd}}\n
  • Програма Мтел еко грант\n
  • {{linkEEAGrantsStart}}Програма BG03 в България по Финансовия механизъм на Европейското икономическо пространство 2009–2014 г.{{linkEnd}}\n
  • {{linkPUDOOSStart}}Предприятие за управление на дейностите по опазване на околната среда{{linkEnd}}\n
  • {{linkLeventisStart}}Фондация A. G. Leventis{{linkEnd}}\n
  • {{linkEurNaturStart}}Фондация EuroNatur, Германия.{{linkEnd}}\n
  • {{linkActiveCitizensFundStart}}Фонд Активни граждани {{linkEnd}}по линия на Финансовия механизъм на ЕИП.", + "LOGO_ACTIVECITIZENSFUND_ALT": "Фонд Активни граждани", + "LOGO_PUDOOS_ALT": "Предприятие за управление на дейностите по опазване на околната среда", + "FIELD_OBSERVATION_METHODOLOGY": "Методика на наблюдение", + "NAV_ATLAS_INTEREST_MAP": "Степен на интерес", + "ATLAS_INTEREST_MAP_TITLE": "Карта степен на интерес", + "ATLAS_INTEREST_MAP_NONE": "Свободен квадрат (0)", + "ATLAS_INTEREST_MAP_LOW": "Заявен (1)", + "ATLAS_INTEREST_MAP_MEDIUM": "Среден интерес (2)", + "ATLAS_INTEREST_MAP_HIGH": "Висок интерес (3+)", + "NAV_ATLAS_MODERATOR_PROGRESS": "Проученост на квадрати (за модератори)", + "ATLAS_MODERATOR_PROGRESS_TITLE": "Проученост на квадрати", + "ATLAS_MODERATOR_PROGRESS_SUBTITLE": "Установени видове спрамо стария атлас", + "ATLAS_LEGEND_VHIGH": "Добре проучен", + "ATLAS_MODERATOR_PROGRESS_ZONE_HEADING": "Избрани квадрати", + "ATLAS_MODERATOR_PROGRESS_ZONE_TEXT": "Статус на кваратите на база \"Наблюдател\" и \"Методика\"", + "ATLAS_MODERATOR_PROGRESS_ZONE_COMPLETE": "Завършен квадрат", + "ATLAS_MODERATOR_PROGRESS_ZONE_UNCOMPLETE": "Отворен квадрат", + "ATLAS_MODERATOR_PROGRESS_ZONE_COMPLETE_CONFIRM": "Моля потвърдете завършването на квадрата. Тази операция ще премахне квадрата от всички потребители. Опреацията може да бъде върната само от Администратора.", + "ATLAS_MODERATOR_PROGRESS_ZONE_UNCOMPLETE_CONFIRM": "Моля потвърдете за повторно отваряне на квадрата", + "ATLAS_LEGEND_COMPLETED": "Приключен", + "ATLAS_NEW_SPECIES_TITLE": "Нови видове по квадрати", + "NAV_ATLAS_NEW_SPECIES": "Нови видове", + "ATLAS_NEW_SPECIES_ZONE_HEADING": "Информация за нови видове", + "ATLAS_NEW_SPECIES_ZONE_TEXT": "Изберете квадрат от картата за да видите новите видове", + "ATLAS_NEW_SPECIES_ZONE_HEADER_SPECIES": "Вид", + "ATLAS_NEW_SPECIES_ZONE_NO_ROWS": "Липсват нови видове за този квадрат", + "FORM_LABEL_PYLONS": "Проучване електропроводи", + "FORM_PYLONS_LONG": "Проучване електропроводи", + "FORM_PYLONS_SHORT": "Проучване електропроводи", + "FORM_LABEL_PYLONS_CASUALTIES": "Жертви електропроводи", + "FORM_PYLONS_CASUALTIES_LONG": "Жертви електропроводи", + "FORM_PYLONS_CASUALTIES_SHORT": "Жертви електропроводи", + "PYLONS_LIST_TITLE_MAIN": "Проучване електропроводи", + "PYLONS_LIST_TITLE_SECONDARY": "Форма за проучване на електропроводи", + "MONITORING_LIST_FILTER_USER": "Наблюдател", + "MONITORING_LIST_FILTER_FROM_DATE": "От", + "MONITORING_LIST_FILTER_TO_DATE": "До", + "MONITORING_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Потвърдете изтриването на {{count}} запис(а)", + "MONITORING_LIST_BUTTON_DELETE": "Изтриване", + "MONITORING_LIST_BUTTON_NEW_ENTRY": "Ново наблюдение", + "MONITORING_LIST_FILTER_SPECIES": "Вид", + "MONITORING_LIST_TAB_LIST": "Списък", + "MONITORING_LIST_TAB_MAP": "Карта", + "MONITORING_LIST_MAP_BUTTON_SHOW_ALL": "Покажи всички", + "MONITORING_LIST_MAP_SHOWN_RECORDS_WARNING": "Показани са само последните {{count}} записа!", + "MONITORING_LIST_TABLE_USER": "Наблюдател", + "MONITORING_LIST_TABLE_DATE": "Дата/час", + "MONITORING_LIST_TABLE_UPDATED": "Променен", + "MONITORING_LIST_TABLE_SPECIES": "Вид", + "MONITORING_LIST_TABLE_COUNT": "Брой", + "PYLONS_CASUALTIES_LIST_TITLE_SECONDARY": "Форма за проучване на жертвите от елелтропреносната мрежа", + "PYLONS_CASUALTIES_LIST_TITLE_MAIN": "Жертви електропроводи", + "MONITORING_DETAIL_UNSAVED_DATA_ALERT_TITLE": "Имате незаписани промени!", + "MONITORING_DETAIL_UNSAVED_DATA_ALERT_MESSAGE": "Имате незаписани промени. Сигурни ли сте че искате да продължите?", + "MONITORING_DETAIL_TITLE_NEW": "Ново наблюдение", + "MONITORING_DETAIL_TITLE_EDIT": "Редакция на запис", + "FIELD_SPECIES_HELP_PROTECTED_SPECIES": "Видовете отбелязани с * са „защитени“", + "PYLONS_CASUALTIES_DETAIL_COUNT": "Брой", + "PYLONS_CASUALTIES_DETAIL_AGE": "Възраст", + "PYLONS_CASUALTIES_DETAIL_SEX": "Пол", + "PYLONS_CASUALTIES_DETAIL_CAUSE_OF_DEATH": "Причина за смъртта", + "PYLONS_CASUALTIES_DETAIL_BODY_CONDITION": "Състояние на жертвата", + "PYLONS_DETAIL_HABITAT_100M_PRIME": "Основно местообитание радиус 100 m", + "PYLONS_DETAIL_HABITAT_100M_SECOND": "Второстепенно местообитание радиус 100 m", + "PYLONS_FIELD_PYLON_TYPE": "Тип на стълба", + "PYLONS_FIELD_SPECIES_NEST": "Наличие на гнездо", + "PYLONS_FIELD_TYPE_NEST": "Тип на гнездо", + "PYLONS_FIELD_INSULATED": "Стълбът е обезопасен", + "PYLONS_LIST_TYPE": "Тип на стълба", + "PYLONS_CASUALTIES_LIST_CAUSE_OF_DEATH": "Причина за смъртта", + "PYLONS_PUBLIC_LIST_TITLE_MAIN": "Проучване електропроводи", + "PYLONS_PUBLIC_LIST_TITLE_SECONDARY": "Форма за проучване на електропроводи", + "PYLONS_CASUALTIES_PUBLIC_LIST_TITLE_MAIN": "Жертви електропроводи", + "PYLONS_CASUALTIES_PUBLIC_LIST_TITLE_SECONDARY": "Форма за проучване на жертвите от елелтропреносната мрежа", + "PYLONS_NEST_SPECIES": "Наличие на гнездо", + "PYLONS_FIELD_DAMAGED_INSULATION": "Увредена изолация", + "NAV_DAILY_REPORT": "Дневен отчет", + "DAILY_REPORT_FILTER_DATE": "Дата", + "DAILY_REPORT_TITLE_MAIN": "Дневен отчет", + "DAILY_REPORT_TITLE_SECONDARY": "Обобщен отчет на записите от избран ден", + "DAILY_REPORT_SUBMIT": "Зареди отчет", + "DAILY_REPORT_LIST_TABLE_FORM": "Форма", + "DAILY_REPORT_LIST_TABLE_LOCATION": "Селище", + "DAILY_REPORT_LIST_TABLE_SPECIES": "Вид", + "DAILY_REPORT_LIST_TABLE_COUNT": "Брой", + "DAILY_REPORT_LIST_TABLE_SPECIES_LATIN": "Латинско име", + "FORM_LABEL_BIRDS_MIGRATIONS": "Форма Миграция птици", + "FORM_BIRDS_MIGRATIONS_LONG": "Наблюдения на мигриращи реещи се птици от точка", + "FORM_BIRDS_MIGRATIONS_SHORT": "Форма Миграция птици", + "BIRDS_MIGRATIONS_LIST_TITLE_MAIN": "Форма Миграция птици", + "BIRDS_MIGRATIONS_LIST_TITLE_SECONDARY": "Наблюдения на мигриращи реещи се птици от точка", + "MONITORING_LIST_BIRDS_MIGRATION_POINT": "Точка на наблюдение", + "LIST_FILTER_BIRDS_MIGRATION_POINT": "Списък с точки на наблюдение", + "BIRDS_MIGRATIONS_DETAIL_MIGRATION_POINT": "Точка на наблюдение", + "BIRDS_MIGRATIONS_DETAIL_LOCATION_FROM_MIGRATION_POINT": "Посока от точката на миграция", + "BIRDS_MIGRATIONS_DETAIL_SEX": "Пол", + "BIRDS_MIGRATIONS_DETAIL_PLUMAGE": "Форма", + "BIRDS_MIGRATIONS_DETAIL_AGE": "Възраст", + "BIRDS_MIGRATIONS_DETAIL_VISOCHINA_POLET": "Височина на полет, m", + "BIRDS_MIGRATIONS_DETAIL_POSOKA_POLET_FROM": "Посока от", + "BIRDS_MIGRATIONS_DETAIL_POSOKA_POLET_TO": "Посока към", + "BIRDS_MIGRATIONS_DETAIL_TYPE_FLIGHT": "Тип на полета", + "DETAIL_DISTANCE_FROM_MIGRATION_POINT": "Отстояние", + "FORM_LABEL_FISHES": "Форма Риби", + "FORM_FISHES_LONG": "Форма Риби", + "FORM_FISHES_SHORT": "Форма Риби", + "FISHES_LIST_TITLE_MAIN": "Форма Риби", + "FISHES_LIST_TITLE_SECONDARY": "Наблюдения на риби", + "FISHES_DETAIL_SEX": "Пол", + "FISHES_DETAIL_NAME_WATER_BODY": "Водоем", + "FISHES_DETAIL_AGE": "Възраст", + "FISHES_DETAIL_SIZE_TL_MM": "Обща дължина на тялото (TL), mm", + "FISHES_DETAIL_SIZE_SL_MM": "Стандартна дължина на тялото (SL), mm", + "FISHES_DETAIL_MASA_GR": "Тегло, gr", + "FISHES_DETAIL_FINDINGS": "Mетод на отчитане", + "FISHES_DETAIL_MONITORING_TYPE": "Tип проучване", + "FISHES_DETAIL_TRANSECT_LENGTH_M": "Дължина на трансекта, m", + "FISHES_DETAIL_TRANSECT_WIDTH_M": "Ширина на трансекта, m", + "FISHES_DETAIL_FISHING_AREA_M": "обща риболовна площ, кв. m", + "FISHES_DETAIL_EXPOSITION": "Експозиция", + "FISHES_DETAIL_MESH_SIZE": "Големина на окото", + "FISHES_DETAIL_COUNT_NET_TRAP": "Брой мрежи/винтери", + "FISHES_DETAIL_WATER_TEMP": "Температура на водата", + "FISHES_DETAIL_CONDUCTIVITY": "Електропроводимост", + "FISHES_DETAIL_PH": "pH", + "FISHES_DETAIL_O2MG_L": "O2 mg/l", + "FISHES_DETAIL_O2PERCENT": "O2 %", + "FISHES_DETAIL_SALINITY": "Соленост", + "FISHES_DETAIL_HABITAT_DESCRIPTION_TYPE": "Тип водоем", + "FISHES_DETAIL_SUBSTRATE_MUD": "Тиня, %", + "FISHES_DETAIL_SUBSTRATE_SILT": "Ситен пясък - до 2 mm, %", + "FISHES_DETAIL_SUBSTRATE_SAND": "Едър пясък - 2-5 mm, %", + "FISHES_DETAIL_SUBSTRATE_GRAVEL": "Чакъл - 0.5 - 4 cm, %", + "FISHES_DETAIL_SUBSTRATE_SMALL_STONES": "Дребни камъни - 4-10 cm, %", + "FISHES_DETAIL_SUBSTRATE_COBBLE": "Едри камъни - 10-25 cm, %", + "FISHES_DETAIL_SUBSTRATE_BOULDER": "Валчести камъни - 25-50 cm, %", + "FISHES_DETAIL_SUBSTRATE_ROCK": "Скално легло, %", + "FISHES_DETAIL_SUBSTRATE_OTHER": "Друго, %", + "FISHES_DETAIL_WATER_LEVEL": "Водно ниво", + "FISHES_DETAIL_RIVER_CURRENT": "Скорост на течение", + "FISHES_DETAIL_TRANSECT_AV_DEPTH": "Средна дълбочина", + "FISHES_DETAIL_TRANSECT_MAX_DEPTH": "Максимална дълбочина", + "FISHES_DETAIL_SLOPE": "Бряг", + "FISHES_DETAIL_BANK_TYPE": "Вид на брега", + "FISHES_DETAIL_SHADING": "Засенченост", + "FISHES_DETAIL_RIPARIAN_VEGETATION": "Крайбрежна растителност", + "FISHES_DETAIL_SHELTERS": "Естествени укрития", + "FISHES_DETAIL_TRANSPARENCY": "Прозрачност на водата", + "FISHES_DETAIL_VEGETATION_TYPE": "Водна растителност", + "FISHES_DETAIL_NATURAL_BARRIERS": "Наличие на естествени препядствия", + "CLASS_FISHES": "Риби", + "PUBLIC_FISHES_TEXT": "Тази част от SmartBirds.org е осъществена с финансовата подкрепа на Програма LIFE на Европейския съюз.", + "PUBLIC_FISHES_TITLE": "Форма Риби", + "PUBLIC_FISHES_SUBTITLE": "Наблюдения на риби", + "PUBLIC_MAP_SPECIES": "видове", + "PUBLIC_MAP_INDIVIDUALS": "индивиди", + "USER_DETAIL_ALLOW_DATA_MOSV": "Съгласен съм събраните от мен данни да бъдат предоставени на МОСВ", + "USER_DETAIL_ALLOW_DATA_SCIENCE_PUBLICATIONS": "Съгласен съм събраните от мен данни да бъдат публикувани в научна статия", + "NAV_BIRDS_MIGRATIONS": "Форма Миграция птици", + "STATS_BIRDS_MIGRATIONS_SEASON_TOTALS_TITLE_TAB": "Общ брой мигриращи птици за всеки сезон", + "PUBLIC_BIRDS_MIGRATIONS_SEASON_TOTALS": "Общ брой мигриращи птици за всеки сезон - есенен (10 Август - 30 Октомври) и пролетен (1 март до 31 май)", + "STATS_BIRDS_MIGRATIONS_PEAK_DAILY_SPECIES_TITLE_TAB": "Пикови дневни числености на мигриращи птици", + "PUBLIC_BIRDS_MIGRATIONS_PEAK_DAILY_SPECIES": "Максимални (пикови) дневни числености на мигриращи птици (с избор на дата)", + "PUBLIC_FORM_LAST_INTERESTING_SPECIES_COL_SPECIES": "Вид", + "PUBLIC_FORM_LAST_INTERESTING_SPECIES_COL_DATE": "Дата", + "PUBLIC_BIRDS_MIGRATIONS_COL_MIGRATION_POINT": "Точка на наблюдение", + "PUBLIC_FORM_LAST_INTERESTING_SPECIES_COL_COUNT": "Брой", + "PUBLIC_FORM_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Наблюдател", + "PUBLIC_BIRDS_MIGRATIONS_SEASON_FALL": "есен", + "PUBLIC_BIRDS_MIGRATIONS_SEASON_SPRING": "пролет", + "PUBLIC_BIRDS_MIGRATIONS_FILTER_DATE": "Дата", + "PUBLIC_BIRDS_MIGRATIONS_PEAK_NO_RECORDS": "Липсват записи за избраната дата", + "PUBLIC_BIRDS_MIGRATIONS_TITLE": "Форма Миграция птици", + "PUBLIC_BIRDS_MIGRATIONS_SUBTITLE": "Наблюдения на мигриращи реещи се птици от точка", + "PUBLIC_BIRDS_MIGRATIONS_TEXT": "Тази част от www.SmartBirds.org е осъществена в партньорство с Националния природонаучен музей при БАН и с финансовата подкрепа на Програма LIFE на Европейския съюз.", + "PUBLIC_BIRDS_MIGRATIONS_TOP_SPECIES_MONTH": "Най-често наблюдавани видове за последните 30 дни", + "FISHES_PUBLIC_LIST_TITLE_MAIN": "Форма Риби", + "FISHES_PUBLIC_LIST_TITLE_SECONDARY": "Наблюдения на риби", + "BIRDS_MIGRATIONS_PUBLIC_LIST_TITLE_MAIN": "Форма Миграция птици", + "BIRDS_MIGRATIONS_PUBLIC_LIST_TITLE_SECONDARY": "Наблюдения на мигриращи реещи се птици от точка", + "STATS_BIRDS_MIGRATIONS_TITLE": "Миграция на птици", + "STATS_BIRDS_MIGRATIONS_SUBTITLE": "Статистики за наблюденията", + "STATS_FISHES_TITLE": "Форма Риби", + "STATS_FISHES_SUBTITLE": "Статистики за наблюденията", + "PUBLIC_FISHES_LAST_INTERESTING_SPECIES_COL_SPECIES": "Вид", + "PUBLIC_FISHES_LAST_INTERESTING_SPECIES_COL_DATE": "Дата", + "PUBLIC_FISHES_LAST_INTERESTING_SPECIES_COL_LOCATION": "Селище", + "PUBLIC_FISHES_LAST_INTERESTING_SPECIES_COL_COUNT": "Брой", + "PUBLIC_FISHES_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Наблюдател", + "PUBLIC_FISHES_LAST_INTERESTING_SPECIES": "Наблюдавани интересни видове за последните 30 дни", + "PUBLIC_FISHES_TOP_SPECIES_MONTH": "Най-често наблюдавани видове за последните 30 дни", + "PUBLIC_FISHES_TOP_OBSERVERS_RECORDS_YEAR": "Най-много въведени наблюдения за календарна година", + "PUBLIC_FISHES_TOP_OBSERVERS_SPECIES_YEAR": "Най-много наблюдавани видове за календарна година", + "PUBLIC_STATISTIC_TABLE_COL_SUM_COUNT": "{{count}} индивида", + "PUBLIC_BIRDS_MIGRATIONS_FILTER_MIGRATION_POINT": "Точка на наблюдение", + "PUBLIC_BIRDS_MIGRATIONS_SEASON_NO_RECORDS": "Липсват записи за избраната миграционна точка", + "PUBLIC_NO_RECORDS": "Липсват записи", + "PUBLIC_BIRDS_MIGRATIONS_FILTER_MIGRATION_POINT_PLACEHOLDER": "Всички", + "PUBLIC_BIRDS_MIGRATIONS_FILTER_SPECIES": "Вид", + "PUBLIC_BIRDS_MIGRATIONS_FILTER_SPECIES_PLACEHOLDER": "Избери вид", + "FORM_LIST_BUTTON_FULL_CSV_EXPORT_TOOLTIP": "Генериране на csv файл с всички намерени записи и всички полета", + "NAV_MAP_LAYERS": "Слоеве", + "MAP_LAYERS_TITLE": "Слоеве", + "MAP_LAYERS_UNSAVED_DATA_TITLE": "Имате незаписани промени!", + "MAP_LAYERS_UNSAVED_DATA_MESSAGE": "Имате незаписани промени. Сигурни ли сте, че искате да продължите?", + "MAP_LAYERS_COL_LABEL": "Име", + "MAP_LAYERS_COL_SUMMARY": "Описание", + "MAP_LAYERS_COL_URL": "Линк", + "MAP_LAYERS_ROW_WIDTH": "Ширина (px)", + "MAP_LAYERS_COL_WIDTH": "Височина (px)", + "MAP_LAYERS_BUTTON_ADD": "Добавяне", + "MAP_LAYERS_BUTTON_SAVE": "Запис", + "FORM_LIST_BUTTON_FULL_ZIP_EXPORT_TOOLTIP": "Генериране на zip файл с всички намерени записи и всички полета, техните снимки и gpx файлове", + "CLASS_BATS": "Прилепи", + "FORM_LABEL_BATS": "Форма Прилепи", + "FORM_BATS_LONG": "Форма Прилепи", + "FORM_BATS_SHORT": "Форма Прилепи", + "BATS_LIST_TITLE_MAIN": "Форма Прилепи", + "BATS_LIST_TITLE_SECONDARY": "Наблюдения на прилепи", + "BATS_DETAIL_METODOLOGY": "Метод", + "BATS_DETAIL_T_CAVE": "T°C на убежището", + "BATS_DETAIL_H_CAVE": "H - Влажност на убежището (%)", + "BATS_DETAIL_TYPE_LOCATION": "Тип на мястото", + "BATS_DETAIL_SUBLOCALITY": "Описание на мястото", + "BATS_DETAIL_SWARMING": "Суорминг", + "BATS_DETAIL_AGE": "Възраст", + "BATS_DETAIL_SEX": "Пол", + "BATS_DETAIL_HABITAT": "Местообитание", + "BATS_DETAIL_CONDITION": "Състояние", + "BATS_DETAIL_TYPE_CONDITION": "Тип на броя", + "BATS_DETAIL_REPRODUCTIVE_STATUS": "Репродуктивен статус", + "BATS_DETAIL_RING": "Пръстен", + "BATS_DETAIL_RING_N": "Номер на пръстена", + "BATS_DETAIL_BODY_LENGTH": "L - дължина на тялото (mm)", + "BATS_DETAIL_TAIL_LENGTH": "C - дължина на опашката (mm)", + "BATS_DETAIL_EAR_LENGTH": "А - дължина на ухото (mm)", + "BATS_DETAIL_FOREARM_LENGTH": "FA - Дължина на совалката (mm)", + "BATS_DETAIL_LENGTH_THIRD_DIGIT": "D3 - Дължина на трети пръст +китката (mm)", + "BATS_DETAIL_LENGTH_FIFTH_DIGIT": "D5 - Дължина на пети пръст +китката (mm)", + "BATS_DETAIL_LENGTH_WS": "WS - Дължина на горен зъбен ред (mm)", + "BATS_DETAIL_WEIGHT": "Тегло (g)", + "BATS_DETAIL_SWARMING_HELP": " ", + "BATS_DETAIL_AGE_HELP": "Възраст на индивидите", + "BATS_DETAIL_SEX_HELP": "Пол на индивидите", + "BATS_DETAIL_CONDITION_HELP": "Състояние", + "BATS_DETAIL_TRAGUS": "Tr - Трагус", + "BATS_DETAIL_UPPER_MOLAR": "CM3 - Горен зъбен ред", + "BATS_PUBLIC_LIST_TITLE_MAIN": "Форма Прилепи", + "BATS_PUBLIC_LIST_TITLE_SECONDARY": "Наблюдения на прилепи", + "IMPORT_RECORDS_MODAL_TITLE": "Импортитане от файл", + "IMPORT_RECORDS_CHOOSE_FILE": "Изберете файл", + "IMPORT_RECORDS_READING_FILE": "Зареждане на файла...", + "IMPORT_RECORDS_LANGUAGE": "Език", + "BTN_IMPORT": "Импортирай", + "FORM_LIST_BUTTON_CSV_IMPORT_TOOLTIP": "Импорт от CSV файл", + "FISHES_DETAIL_TOTAL_LENGTH": "Обща дължина на тялото (TL) в категории, cm", + "IMPORT_RECORDS_IGNORE_ERRORS": "Игнорирай грешките", + "You will be notified by email when your import is ready": "Ще бъдете уведомени с емайл, когато импортът Ви е готов.", + "EBP_SETTINGS_BUTTON_SAVE": "Запис", + "EBP_SETTINGS_BUTTON_ADD": "Добави", + "EBP_SETTINGS_TITLE": "EBP настройки", + "EBP_SETTINGS_ORGANIZATIONS": "Организации", + "EBP_SETTINGS_SOURCES": "Източници", + "EBP_SETTINGS_PROTOCOL": "Протокол", + "EBP_SETTINGS_SPECIES": "Видове", + "EBP_SETTINGS_SPECIES_STATUS": "Статус на вид", + "EBP_SETTINGS_NO_PERMISSION": "Нямате достъп до тази страница", + "EBP_SETTINGS_ORGANIZATION": "Организация", + "EBP_SETTINGS_ORGANIZATION_ENABLED": "Позволена", + "EBP_SETTINGS_SOURCE": "Източник", + "EBP_SETTINGS_SOURCE_ENABLED": "Позволен", + "EBP_SETTINGS_SPECIES_CODE": "EBP код", + "EBP_SETTINGS_SPECIES_SB_NAME": "Име в smartbirds", + "EBP_SETTINGS_SPECIES_EBP_NAME": "Име в EBP", + "EBP_SETTINGS_SPECIES_STATUS_CODE": "EBP Код", + "EBP_SETTINGS_SPECIES_STATUS_SB_NAME": "Име в smartbirds", + "NAV_EBP": "EBP" +} diff --git a/i18n/en.json b/i18n/en.json index 5ed6653f2..7ebbf3d8a 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -41,17 +41,17 @@ "PUBLIC_HOME_ABOUT_TITLE": "About the project", "PUBLIC_HOME_ABOUT_BODY": "SmartBirds.org is an electronic database developed by {{bspbLinkStart}}the Bulgarian Society for the Protection of Birds{{bspbLinkEnd}}. Its purpose is to serve as an electronic notebook for the storage of records from observations of birds, mammals, amphibians, reptiles protected and other invertebrates. The recording of each observation supports the conservation activities in Bulgaria. So write your observations directly here on the page or through the mobile application {{mobileLinkStart}} SmartBirds Pro{{mobileLinkEnd}}. If you love nature and want to join the bird counting initiative around us, register with SmartBirds {{registerLinkStart}} here {{registerLinkEnd}} and choose your personal bird counting place.", "PUBLIC_HOME_FUNDING_TITLE": "Funding", - "PUBLIC_HOME_FUNDING_BODY1": "The Common Birds Monitoring form in the portal SmartBirds.org is financed through the project \"Birds for the people and people for the birds: with a good will to actively conserve nature\" within the {{linkStart}}Programs BG03 in Bulgaria{{linkEnd}} of the Financial Mechanism of the European Economic Area 2009-2014.", - "PUBLIC_HOME_FUNDING_BODY2": "The other modules of SmartBirds.org have been implemented within the project {{linkStart}}\"LIFE for Eagles Forests - Preserve Key Forest Habitats of the Lesser Spotted Eagle (Aquila pomarina) in Bulgaria\"{{linkEnd}} (LIFE12 NAT/BG/001218) financed by the LIFE + program The European Union.", + "PUBLIC_HOME_FUNDING_BODY1": "Biological Information System (SmartBirds.org) and the {{linkSmartBirdsProStart}}SmartBirds Pro{{linkEnd}} and {{linkSmartBirdsStart}}SmartBirds{{linkEnd} mobile applications were developed and updated by BSPB with the financial support of:", + "PUBLIC_HOME_FUNDING_BODY2": "Some of the modules of SmartBirds.org and SmartBirds Pro have been developed in partnership with the {{linkNMNHSStart}}National Museum of Natural History at the Bulgarian Academy of Sciences{{linkEnd}}.\nThe BSPB Biological Information System is part of the {{linkEuroBirdPortalStart}}EuroBirdPortal{{linkEnd}} initiative of the {{linkEuroBirdCensusStart}}European Bird Census Council{{linkStart}}.", "PUBLIC_BIRDS_TITLE": "Form Birds", "PUBLIC_BIRDS_SUBTITLE": "Observations from Standard form Birds", - "PUBLIC_BIRDS_TEXT": "This module of SmartBirds.org has been implemented within the project {{linkStart}}\"LIFE for Eagles Forests - Preserve Key Forest Habitats of the Lesser Spotted Eagle (Aquila pomarina) in Bulgaria\"{{linkEnd}} (LIFE12 NAT/BG/001218) financed by the LIFE + program The European Union.", + "PUBLIC_BIRDS_TEXT": "This part of SmartBirds.org is implemented with the financial support of the LIFE Program of the European Union.", "PUBLIC_CBM_TITLE": "CBM", "PUBLIC_CBM_SUBTITLE": "Sites for monitoring of the Common Birds species", - "PUBLIC_CBM_TEXT": "The Common Birds Monitoring form in the portal SmartBirds.org is financed through the project \"Birds for the people and people for the birds: with a good will to actively conserve nature\" within the {{linkStart}}Programs BG03 in Bulgaria{{linkEnd}} of the Financial Mechanism of the European Economic Area 2009-2014.", + "PUBLIC_CBM_TEXT": "The Common Birds Monitoring form in the portal SmartBirds.org is financed through the project \"Birds for the people and people for the birds: with a good will to actively conserve nature\" within the {{linkStart}}Programs BG03 in Bulgaria{{linkEnd}} of the Financial Mechanism of the European Economic Area 2009-2014 and the project \"Implementation of the monitoring scheme for common bird species as part of the National Biodiversity Monitoring System\", funded by the EMEPA. ", "PUBLIC_CICONIA_TITLE": "Form White stork", "PUBLIC_CICONIA_SUBTITLE": "Observations of White stork", - "PUBLIC_CICONIA_TEXT": "This module of SmartBirds.org has been implemented within the project {{linkStart}}\"LIFE for Eagles Forests - Preserve Key Forest Habitats of the Lesser Spotted Eagle (Aquila pomarina) in Bulgaria\"{{linkEnd}} (LIFE12 NAT/BG/001218) financed by the LIFE + program The European Union.", + "PUBLIC_CICONIA_TEXT": "This part of www.SmartBirds.org is funded by the European Union's LIFE Program. The module is also supported within the project \"SmartStorkBelozem - development of civil science for storks in Belozem - European village of storks in Bulgaria\", funded by the EuroNatur Foundation, Germany.", "PUBLIC_HERP_TITLE": "Form ARM", "PUBLIC_HERP_SUBTITLE": "Observations of amphibians, reptiles, and mammals", "PUBLIC_HERP_TEXT": "This module of SmartBirds.org has been implemented within the project {{linkStart}}\"LIFE for Eagles Forests - Preserve Key Forest Habitats of the Lesser Spotted Eagle (Aquila pomarina) in Bulgaria\"{{linkEnd}} (LIFE12 NAT/BG/001218) financed by the LIFE + program The European Union.", @@ -220,7 +220,6 @@ "BIRDS_DETAIL_TITLE_NEW": "New observation", "BIRDS_DETAIL_TITLE_EDIT": "Edit record", "BIRDS_DETAIL_SPECIES": "Scientific name", - "BIRDS_DETAIL_CONFIDENTIAL": "Confidential", "BIRDS_DETAIL_LATITUDE": "Latitude", "BIRDS_DETAIL_LONGITUDE": "Longitude", "BIRDS_DETAIL_COUNT_UNIT": "Count unit", @@ -296,8 +295,8 @@ "HERP_DETAIL_TITLE_EDIT": "Edit record", "HERP_DETAIL_LATITUDE": "Latitude", "HERP_DETAIL_LONGITUDE": "Longitude", - "HERP_DETAIL_SPECIES": "Scientific name", - "HERP_DETAIL_COUNT": "Count", + "FIELD_SPECIES": "Scientific name", + "FIELD_COUNT": "Count", "HERP_DETAIL_AGE": "Age", "HERP_DETAIL_SEX": "Sex", "HERP_DETAIL_HABITAT": "Habitat", @@ -464,12 +463,12 @@ "FORM_MAMMALS_SHORT": "Mammals", "PUBLIC_HERPTILES_TITLE": "Form A&R", "PUBLIC_HERPTILES_SUBTITLE": "Observations of amphibians and reptiles", - "PUBLIC_HERPTILES_TEXT": "This module of SmartBirds.org has been implemented within the project {{linkStart}}\"LIFE for Eagles Forests - Preserve Key Forest Habitats of the Lesser Spotted Eagle (Aquila pomarina) in Bulgaria\"{{linkEnd}} (LIFE12 NAT/BG/001218) financed by the LIFE + program The European Union.", + "PUBLIC_HERPTILES_TEXT": "This part of SmartBirds.org is implemented with the financial support of the LIFE Program of the European Union.", "PUBLIC_HERPTILES_MAP_SPECIES": "species", "PUBLIC_HERPTILES_MAP_INDIVIDUALS": "individuals", "PUBLIC_MAMMALS_TITLE": "Form Mammals", "PUBLIC_MAMMALS_SUBTITLE": "Observations of mammals", - "PUBLIC_MAMMALS_TEXT": "This module of SmartBirds.org has been implemented within the project {{linkStart}}\"LIFE for Eagles Forests - Preserve Key Forest Habitats of the Lesser Spotted Eagle (Aquila pomarina) in Bulgaria\"{{linkEnd}} (LIFE12 NAT/BG/001218) financed by the LIFE + program The European Union.", + "PUBLIC_MAMMALS_TEXT": "This part of SmartBirds.org is implemented with the financial support of the LIFE Program of the European Union.", "PUBLIC_MAMMALS_MAP_SPECIES": "species", "PUBLIC_MAMMALS_MAP_INDIVIDUALS": "individuals", "DASHBOARD_CARD_HERPTILES": "A&R", @@ -625,17 +624,17 @@ "DOWNLOAD_TITLE": "Download data", "You will be notified by email when your export is ready": "You will be notified by email when your export is ready", "Error during export": "Error during export", - "FORM_INVERTEBRATES_LONG": "Protected invertebrates", - "FORM_INVERTEBRATES_SHORT": "Protected invertebrates", - "FORM_LABEL_INVERTEBRATES": "Protected invertebrates", - "DASHBOARD_CARD_INVERTEBRATES": "Protected invertebrates", - "PUBLIC_INVERTEBRATES_TITLE": "Form Protected invertebrates", - "PUBLIC_INVERTEBRATES_SUBTITLE": "Observations of protected invertebrates", - "PUBLIC_INVERTEBRATES_TEXT": "This module of SmartBirds.org has been implemented within the project {{linkStart}}\"LIFE for Eagles Forests - Preserve Key Forest Habitats of the Lesser Spotted Eagle (Aquila pomarina) in Bulgaria\"{{linkEnd}} (LIFE12 NAT/BG/001218) financed by the LIFE + program The European Union.", + "FORM_INVERTEBRATES_LONG": "Invertebrates", + "FORM_INVERTEBRATES_SHORT": "Invertebrates", + "FORM_LABEL_INVERTEBRATES": "Invertebrates", + "DASHBOARD_CARD_INVERTEBRATES": "Invertebrates", + "PUBLIC_INVERTEBRATES_TITLE": "Form Invertebrates", + "PUBLIC_INVERTEBRATES_SUBTITLE": "Observations of invertebrates", + "PUBLIC_INVERTEBRATES_TEXT": "\nThis part of www.SmartBirds.org is implemented in partnership with the National Museum of Natural History at the Bulgarian Academy of Sciences and with the financial support of the LIFE Program of the European Union.", "PUBLIC_INVERTEBRATES_MAP_SPECIES": "species", "PUBLIC_INVERTEBRATES_MAP_INDIVIDUALS": "individuals", - "INVERTEBRATES_LIST_TITLE_MAIN": "Form Protected invertebrates", - "INVERTEBRATES_LIST_TITLE_SECONDARY": "Observations of protected invertebrates", + "INVERTEBRATES_LIST_TITLE_MAIN": "Form Invertebrates", + "INVERTEBRATES_LIST_TITLE_SECONDARY": "Observations of invertebrates", "INVERTEBRATES_LIST_BUTTON_DELETE": "Delete", "INVERTEBRATES_LIST_BUTTON_NEW_ENTRY": "New observation", "INVERTEBRATES_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Confirm the deletion of {{count}} records", @@ -686,7 +685,7 @@ "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES": "Interesting species observed for the last 30 days", "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES_COL_SPECIES": "Species", "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES_COL_DATE": "Date", - "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES_COL_LOCATION": "Location", + "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES_COL_LOCATION": "Settlement", "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES_COL_COUNT": "Count", "PUBLIC_BIRDS_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Observer", "USER_DETAIL_PRIVACY": "Public data and statistics", @@ -698,9 +697,6 @@ "WARNING_EXPORT_LIMIT": "The system limits exporting up to 20 000 records at a time", "SPECIES_COL_INTERESTING": "Interesting", "SPECIES_COL_SENSITIVE": "Sensitive", - "HERPTILES_DETAIL_CONFIDENTIAL": "Confidential", - "MAMMALS_DETAIL_CONFIDENTIAL": "Confidential", - "INVERTEBRATES_DETAIL_CONFIDENTIAL": "Confidential", "WARNING_EXPORT_DISABLED": "Exports of other user's data is not allowed", "NAV_FRIENDS": "Friends", "FRIENDS_TITLE": "Friends Management", @@ -714,7 +710,6 @@ "FRIENDS_UNSAVED_DATA_ALERT_MESSAGE": "There are unsaved changes! Are you sure you want to continue?", "FRIENDS_SAVE_SUCCESS": "Friends saved successfully", "FRIENDS_SAVE_ERROR": "Error while saving friends", - "CBM_DETAIL_CONFIDENTIAL": "Confidential", "PUBLIC_BIRDS_STATISTIC_TABLE_COL_COUNT_SPECIES": "{{count}} species", "PUBLIC_BIRDS_STATISTIC_TABLE_COL_COUNT_RECORDS": "{{count}} observations", "NAV_PRIVATE_FORMS": "My and shared data", @@ -727,8 +722,8 @@ "HERPTILES_PUBLIC_LIST_TITLE_SECONDARY": "Observations of amphibians and reptiles", "MAMMALS_PUBLIC_LIST_TITLE_MAIN": "Form Mammals", "MAMMALS_PUBLIC_LIST_TITLE_SECONDARY": "Observations of mammals", - "INVERTEBRATES_PUBLIC_LIST_TITLE_MAIN": "Form Protected invertebrates", - "INVERTEBRATES_PUBLIC_LIST_TITLE_SECONDARY": "Observations of protected invertebrates", + "INVERTEBRATES_PUBLIC_LIST_TITLE_MAIN": "Form Invertebrates", + "INVERTEBRATES_PUBLIC_LIST_TITLE_SECONDARY": "Observations of invertebrates", "CICONIA_PUBLIC_LIST_TITLE_MAIN": "Form White stork", "CICONIA_PUBLIC_LIST_TITLE_SECONDARY": "Observations of White stork", "PUBLIC_LIST_FILTER_USER": "Observer", @@ -778,7 +773,6 @@ "PLANTS_DETAIL_UNSAVED_DATA_ALERT_TITLE": "There are unsaved changes!", "PLANTS_DETAIL_UNSAVED_DATA_ALERT_MESSAGE": "There are unsaved changes! Are you sure you want to continue?", "PLANTS_DETAIL_SPECIES": "Species", - "PLANTS_DETAIL_CONFIDENTIAL": "Confidential", "PLANTS_DETAIL_ELEVATION": "Elevation", "PLANTS_DETAIL_HABITAT": "Habitat", "PLANTS_DETAIL_REPORTING_UNIT": "Reporting unit", @@ -789,7 +783,7 @@ "PLANTS_DETAIL_ACCOMPANYING_SPECIES": "Accompanying species", "PUBLIC_PLANTS_TITLE": "Form Protected plants", "PUBLIC_PLANTS_SUBTITLE": "Observations of protected plants", - "PUBLIC_PLANTS_TEXT": "Plants are part of Smartbirds.org", + "PUBLIC_PLANTS_TEXT": "This part of www.SmartBirds.org is implemented in partnership with the National Museum of Natural History at the Bulgarian Academy of Sciences and with the financial support of the LIFE Program of the European Union.", "PUBLIC_PLANTS_MAP_SPECIES": "species", "PUBLIC_PLANTS_MAP_INDIVIDUALS": "Individuals", "LOGO_NMNHS_ALT": "NMNHS", @@ -841,8 +835,8 @@ "USER_LIST_TABLE_GDPR_CONSENT": "GDPR", "USER_DETAIL_GDPR_CONSENT_GRANTED": "Granted", "USER_DETAIL_GDPR_CONSENT_NOT_GRANTED": "Not granted", - "CAMPAIGN1_HEADER": "Let's count together up to the millionth record!", - "CAMPAIGN1_SUBHEADER": "Make the millionth record and win a tablet!", + "CAMPAIGN1_HEADER": "Let's count together up to the two millionth record!", + "CAMPAIGN1_SUBHEADER": "Make the two millionth record and win a voucher for the Sporting Goods Shops \"Stenata\"!", "CAMPAIGN1_BUTTON": "SIGN UP", "CAMPAIGN1_TEXT": "Start your data recording now!", "PUBLIC_HERPTILES_TOP_OBSERVERS_RECORDS_MONTH": "The most observations entered for the last 30 days", @@ -855,7 +849,7 @@ "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES": "Interesting species observed for the last 30 days", "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES_COL_SPECIES": "Species", "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES_COL_DATE": "Date", - "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES_COL_LOCATION": "Location", + "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES_COL_LOCATION": "Settlement", "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES_COL_COUNT": "Count", "PUBLIC_HERPTILES_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Observer", "PUBLIC_MAMMALS_TOP_OBSERVERS_RECORDS_MONTH": "The most observations entered for the last 30 days", @@ -868,7 +862,7 @@ "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES": "Interesting species observed for the last 30 days", "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES_COL_SPECIES": "Species", "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES_COL_DATE": "Date", - "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES_COL_LOCATION": "Location", + "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES_COL_LOCATION": "Settlement", "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES_COL_COUNT": "Count", "PUBLIC_MAMMALS_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Observer", "PUBLIC_INVERTEBRATES_TOP_OBSERVERS_RECORDS_MONTH": "The most observations entered for the last 30 days", @@ -881,7 +875,7 @@ "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES": "Interesting species observed for the last 30 days", "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES_COL_SPECIES": "Species", "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES_COL_DATE": "Date", - "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES_COL_LOCATION": "Location", + "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES_COL_LOCATION": "Settlement", "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES_COL_COUNT": "Count", "PUBLIC_INVERTEBRATES_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Observer", "PUBLIC_PLANTS_TOP_OBSERVERS_RECORDS_MONTH": "The most observations entered for the last 30 days", @@ -894,7 +888,7 @@ "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES": "Interesting species observed for the last 30 days", "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES_COL_SPECIES": "Species", "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES_COL_DATE": "Date", - "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES_COL_LOCATION": "Location", + "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES_COL_LOCATION": "Settlement", "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES_COL_COUNT": "Count", "PUBLIC_PLANTS_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Observer", "PUBLIC_STATISTIC_TABLE_COL_COUNT_SPECIES": "{{count}} species", @@ -908,7 +902,7 @@ "STATS_MAMMALS_SUBTITLE": "Observations statistics", "STATS_PLANTS_TITLE": "Protected plants", "STATS_PLANTS_SUBTITLE": "Observations statistics", - "STATS_INVERTEBRATES_TITLE": "Protected invertebrates", + "STATS_INVERTEBRATES_TITLE": "Invertebrates", "STATS_INVERTEBRATES_SUBTITLE": "Observations statistics", "STATS_INTERESTING_SPECIES_TITLE_TAB": "Interesting species", "STATS_TOP_SPECIES_MONTH_TITLE_TAB": "The most frequently observed species", @@ -1004,6 +998,373 @@ "THREATS_LIST_FILTER_CATEGORY": "Category", "PUBLIC_THREATS_TITLE": "Form Threats", "PUBLIC_THREATS_SUBTITLE": "Observations of threats", - "PUBLIC_THREATS_TEXT": "Threats are part of Smartbirds.org", - "FORM_LABEL_THREATS": "Threats" -} \ No newline at end of file + "PUBLIC_THREATS_TEXT": "This module of SmartBirds.org has been implemented within the project {{linkStart}}\"Egyptian Vulture New LIFE\"{{linkEnd}} (LIFE16 NAT/BG/000874) financed by the LIFE + program The European Union and co-funded by the A. G. Leventis Foundation.", + "FORM_LABEL_THREATS": "Threats", + "LOGO_LIFENEOPHRON_ALT": "Program Life+", + "LOGO_BIRDLIFE_ALT": "BirdLife International", + "LOGO_LEVENTIS_ALT": "A. G. Leventis Foundation", + "LANGUAGE_ALBANIAN": "Albanian", + "LANGUAGE_MACEDONIAN": "Macedonian", + "PUBLIC_THREATS_FILTER_BY_THREAT": "Threat:", + "PUBLIC_THREATS_FILTER_ALL_THREATS": "- all threats -", + "PUBLIC_THREATS_FILTER_BY_DATE": "Period:", + "PUBLIC_THREATS_FILTER_ALL_TIME": "- all times -", + "PUBLIC_THREATS_FILTER_1_MONTH": "last month", + "PUBLIC_THREATS_FILTER_3_MONTHS": "last 3 months", + "PUBLIC_THREATS_FILTER_6_MONTHS": "last 6 months", + "PUBLIC_THREATS_FILTER_1_YEAR": "last year", + "NAV_ORGANIZATIONS": "Organizations", + "ORGANIZATIONS_TITLE": "Organizations", + "ORGANIZATIONS_BUTTON_SAVE": "Save", + "CHANGE_ORGANIZATION_WARNING_MESSAGE": "Changing organization will reset user's role to observer", + "LIST_TABLE_ORGANIZATION": "Organization", + "LIST_FILTER_ORGANIZATION": "Organization", + "USER_DETAIL_ORGANIZATION": "Organization", + "LIST_FILTER_SETTLEMENT": "Settlement", + "LIST_TABLE_SETTLEMENT": "Settlement", + "DASHBOARD_CARD_RETRY": "Retry", + "DASHBOARD_CARD_MODERATOR_REVIEW": "for review", + "FIELD_MODERATOR_REVIEW": "Moderator review", + "FIELD_MODERATOR_REVIEW_HELP": "If you want a moderator to review your record. Requires a picture attachment!", + "LIST_FILTER_MODERATOR_REVIEW": "Moderator review", + "FIELD_CONFIDENTIAL": "Confidential", + "FORM_DETAIL_BUTTON_CONFIRM": "Confirm", + "SETTLEMENT_VALUE_UNKNOWN": "Unknown", + "SETTLEMENT_VALUE_PENDING": "Processing…", + "NAV_ATLAS": "Atlas", + "NAV_ATLAS_DASHBOARD": "Dashboard", + "NAV_ATLAS_REQUEST": "Request square", + "ATLAS_DASHBOARD_TITLE": "Atlas Dashboard", + "ATLAS_REQUEST_TITLE": "Request squares", + "ATLAS_REQUEST_SUBTITLE": "Choose up to 10 squares", + "ATLAS_REQUEST_BTN_SAVE": "Save", + "ATLAS_REQUEST_ZONES_HEADING": "For each selected square it is indicated what percentage of the species registered during the previous atlas (Yankov, 2007) were also recorded after 2016.", + "ATLAS_REQUEST_ZONES_TEXT": "Selected squares (10 X 10 km) for atlas survey", + "ATLAS_LEGEND_LOW": "Poorly studied", + "ATLAS_LEGEND_MEDIUM": "Мedium studied", + "ATLAS_LEGEND_HIGH": "Well studied", + "ATLAS_REQUEST_ZONES_HINT": "Select the squares you want", + "ATLAS_DASHBOARD_ZONE_HEADING": "Square information", + "ATLAS_DASHBOARD_ZONE_HEADER_SPECIES": "Species", + "ATLAS_DASHBOARD_ZONE_HEADER_ATLAS": "Atlas 2007", + "ATLAS_DASHBOARD_ZONE_HEADER_OBSERVATION": "Personal observation", + "ATLAS_DASHBOARD_ZONE_TEXT": "Comparison of species found in the square in the previous period according to the Atlas of breeding birds in Bulgaria (Iankov, 2007), the species identified by the user and all species identified in the square after 01.01.2016", + "ATLAS_DASHBOARD_ZONE_NO_ROWS": "There are no known bird species either in the atlas or in the user's observations", + "PUBLIC_ATLAS_TITLE": "Atlas", + "PUBLIC_ATLAS_SUBTITLE": "Level of research after 01.01.2016 (base: UTM grid 10 x 10 km)", + "PUBLIC_ATLAS_PAGE_TITLE": "Birds Atlas", + "ATLAS_DASHBOARD_ZONE_HEADER_OTHER": "All observations", + "PUBLIC_ATLAS_TEXT": "This module was developed within the project \"Citizens' Science for the Benefit of Local Communities and Nature\" with financial support from the {{linkStart}} Active Citizens Fund {{linkEnd}} under the EEA Financial Mechanism", + "PUBLIC_ATLAS_LONG": "Atlas of Breeding Birds in Bulgaria", + "PUBLIC_ATLAS_SHORT": "Atlas", + "ATLAS_STATS_TOP_OBSERVERS_HEADING": "Select a square from the map", + "ATLAS_STATS_TOP_OBSERVERS_ROW_VALUE": "{{ species }} species", + "ATLAS_STATS_TOP_OBSERVERS_TEXT": "Total number of species: {{species}} species", + "ATLAS_STATS_TOP_OBSERVERS_HINT": "Select the square on the map to see information about it", + "ATLAS_STATS_TITLE": "Atlas of statistics", + "ATLAS_STATS_SUBTITLE": "Observers that entered data for the largest number of 10 x10 km UTM squares", + "ATLAS_STATS_TOP_OBSERVERS": "Most observations per square", + "ATLAS_STATS_TOP_OBSERVERS_TITLE_TAB": "Observers by squares", + "ATLAS_STATS_RANKING_LABEL_COUNT": "{{ count }} squres", + "ATLAS_STATS_RANKING_TITLE_TAB": "Most squares", + "ATLAS_STATS_RANKING": "Most visited squares by an observer", + "ATLAS_MISSING_SPECIES_TITLE": "Unidentified species by squares", + "NAV_ATLAS_MISSING_SPECIES": "Unidentified species", + "ATLAS_MISSING_SPECIES_ZONE_HEADING": "Information on unidentified species", + "ATLAS_MISSING_SPECIES_ZONE_TEXT": "Select a square on the map to see the unidentified species", + "ATLAS_MISSING_SPECIES_ZONE_HEADER_SPECIES": "Species", + "ATLAS_MISSING_SPECIES_ZONE_NO_ROWS": "There are no unidentified species for this square", + "FORM_ATLAS_SHORT": "Atlas Birds", + "LANGUAGE_GREEK": "Greek", + "LANGUAGE_TURKISH": "Turkish", + "LANGUAGE_ARABIC": "Arabic", + "LANGUAGE_FRENCH": "French", + "PUBLIC_HOME_FUNDING_LIST": "
  • {{linkLIFEStart}} European Union LIFE Program {{linkEnd}}\n
  • {{linkUS4BGStart}} America for Bulgaria Foundation {{linkEnd}}\n
  • Mtel eco grant program\n
  • {{linkEEAGrantsStart}} Program BG03 in Bulgaria under the Financial Mechanism of the European Economic Area 2009-2014 {{linkEnd}}\n
  • {{linkPUDOOSStart}} Environmental Management Company {{linkEnd}}\n
  • {{linkLeventisStart}} A. G. Leventis Foundation {{linkEnd}}\n
  • {{linkEurNaturStart}} EuroNatur Foundation, Germany. {{linkEnd}}\n
  • {{linkActiveCitizensFundStart}} Active Citizens Fund {{linkEnd}} under the EEA Financial Mechanism.", + "LOGO_ACTIVECITIZENSFUND_ALT": "Active Citizens Fund", + "LOGO_PUDOOS_ALT": "Environmental Management Company", + "FIELD_OBSERVATION_METHODOLOGY": "Observation methodology", + "NAV_ATLAS_INTEREST_MAP": "Rate of interest", + "ATLAS_INTEREST_MAP_TITLE": "Map rate of interest", + "ATLAS_INTEREST_MAP_NONE": "Free square", + "ATLAS_INTEREST_MAP_LOW": "Select", + "ATLAS_INTEREST_MAP_MEDIUM": "Medium interest", + "ATLAS_INTEREST_MAP_HIGH": "High interest", + "NAV_ATLAS_MODERATOR_PROGRESS": "Survey of squares (for moderators)", + "ATLAS_MODERATOR_PROGRESS_TITLE": "Square progress", + "ATLAS_MODERATOR_PROGRESS_SUBTITLE": "Found species compared to the old atlas", + "ATLAS_LEGEND_VHIGH": "Very well studied", + "ATLAS_MODERATOR_PROGRESS_ZONE_HEADING": "Select square", + "ATLAS_MODERATOR_PROGRESS_ZONE_TEXT": "Square stats by user and methodology", + "ATLAS_MODERATOR_PROGRESS_ZONE_COMPLETE": "Finish square", + "ATLAS_MODERATOR_PROGRESS_ZONE_UNCOMPLETE": "Open square", + "ATLAS_MODERATOR_PROGRESS_ZONE_COMPLETE_CONFIRM": "Please confirm finishing the square. This operation will remove the cell from all users selections and can only be reverted by an administrator.", + "ATLAS_MODERATOR_PROGRESS_ZONE_UNCOMPLETE_CONFIRM": "Please confirm reopening the square", + "ATLAS_LEGEND_COMPLETED": "Completed", + "ATLAS_NEW_SPECIES_TITLE": "New species by squares", + "NAV_ATLAS_NEW_SPECIES": "New species", + "ATLAS_NEW_SPECIES_ZONE_HEADING": "Information on new species", + "ATLAS_NEW_SPECIES_ZONE_TEXT": "Select a square on the map to see the new species", + "ATLAS_NEW_SPECIES_ZONE_HEADER_SPECIES": "Species", + "ATLAS_NEW_SPECIES_ZONE_NO_ROWS": "There are no new species for this square", + "FORM_LABEL_PYLONS": "Powerlines’ study", + "FORM_PYLONS_LONG": "Powerlines’ study", + "FORM_PYLONS_SHORT": "Powerlines’ study", + "FORM_LABEL_PYLONS_CASUALTIES": "Powerlines’ casualties", + "FORM_PYLONS_CASUALTIES_LONG": "Powerlines’ casualties", + "FORM_PYLONS_CASUALTIES_SHORT": "Powerlines’ casualties", + "PYLONS_LIST_TITLE_MAIN": "Powerlines’ study", + "PYLONS_LIST_TITLE_SECONDARY": "Form for Powerlines’ study", + "MONITORING_LIST_FILTER_USER": "Observer", + "MONITORING_LIST_FILTER_FROM_DATE": "From", + "MONITORING_LIST_FILTER_TO_DATE": "To", + "MONITORING_LIST_BUTTON_DELETE_CONFIRM_MESSAGE": "Confirm the deletion of {{count}} records", + "MONITORING_LIST_BUTTON_DELETE": "Delete", + "MONITORING_LIST_BUTTON_NEW_ENTRY": "New observation", + "MONITORING_LIST_FILTER_SPECIES": "Species", + "MONITORING_LIST_TAB_LIST": "List", + "MONITORING_LIST_TAB_MAP": "Map", + "MONITORING_LIST_MAP_BUTTON_SHOW_ALL": "Show all", + "MONITORING_LIST_MAP_SHOWN_RECORDS_WARNING": "Only the last {{count}} records are shown!", + "MONITORING_LIST_TABLE_USER": "Observer", + "MONITORING_LIST_TABLE_DATE": "Date/time", + "MONITORING_LIST_TABLE_UPDATED": "Updated", + "MONITORING_LIST_TABLE_SPECIES": "Species", + "MONITORING_LIST_TABLE_COUNT": "Count", + "PYLONS_CASUALTIES_LIST_TITLE_SECONDARY": "Casualties from pylons or electric grid", + "PYLONS_CASUALTIES_LIST_TITLE_MAIN": "Powerlines’ casualties", + "MONITORING_DETAIL_UNSAVED_DATA_ALERT_TITLE": "There are unsaved changes!", + "MONITORING_DETAIL_UNSAVED_DATA_ALERT_MESSAGE": "There are unsaved changes! Are you sure you want to continue?", + "MONITORING_DETAIL_TITLE_NEW": "New observation", + "MONITORING_DETAIL_TITLE_EDIT": "Edit record", + "FIELD_SPECIES_HELP_PROTECTED_SPECIES": "Species marked with * are \"protected\"", + "PYLONS_CASUALTIES_DETAIL_COUNT": "Count", + "PYLONS_CASUALTIES_DETAIL_AGE": "Age", + "PYLONS_CASUALTIES_DETAIL_SEX": "Sex", + "PYLONS_CASUALTIES_DETAIL_CAUSE_OF_DEATH": "Cause of death", + "PYLONS_CASUALTIES_DETAIL_BODY_CONDITION": "Body condition", + "PYLONS_DETAIL_HABITAT_100M_PRIME": "Primary habitat 100 m", + "PYLONS_DETAIL_HABITAT_100M_SECOND": "Secondary habitat 100 m", + "PYLONS_FIELD_PYLON_TYPE": "Pylon type", + "PYLONS_FIELD_SPECIES_NEST": "Species nest on pylon", + "PYLONS_FIELD_TYPE_NEST": "Nest type", + "PYLONS_FIELD_INSULATED": "Pylon insulated", + "PYLONS_LIST_TYPE": "Pylon type", + "PYLONS_CASUALTIES_LIST_CAUSE_OF_DEATH": "Cause of death", + "PYLONS_PUBLIC_LIST_TITLE_MAIN": "Pylons", + "PYLONS_PUBLIC_LIST_TITLE_SECONDARY": "pylons desc.", + "PYLONS_CASUALTIES_PUBLIC_LIST_TITLE_MAIN": "Powerlines’ casualties", + "PYLONS_CASUALTIES_PUBLIC_LIST_TITLE_SECONDARY": "casualties desc.", + "PYLONS_NEST_SPECIES": "Species nest on pylon", + "PYLONS_FIELD_DAMAGED_INSULATION": "Damaged insulation", + "NAV_DAILY_REPORT": "Daily report", + "DAILY_REPORT_FILTER_DATE": "Date", + "DAILY_REPORT_TITLE_MAIN": "Daily report", + "DAILY_REPORT_TITLE_SECONDARY": "Summary of recorded observation for a given day", + "DAILY_REPORT_SUBMIT": "Load report", + "DAILY_REPORT_LIST_TABLE_FORM": "Form", + "DAILY_REPORT_LIST_TABLE_LOCATION": "Settlement", + "DAILY_REPORT_LIST_TABLE_SPECIES": "Species", + "DAILY_REPORT_LIST_TABLE_COUNT": "Count", + "DAILY_REPORT_LIST_TABLE_SPECIES_LATIN": "Latin name", + "FORM_LABEL_BIRDS_MIGRATIONS": "Form Birds migrations", + "FORM_BIRDS_MIGRATIONS_LONG": "Observations of migratory soaring birds from a viewpoint", + "FORM_BIRDS_MIGRATIONS_SHORT": "Form Birds migrations", + "BIRDS_MIGRATIONS_LIST_TITLE_MAIN": "Form Birds migrations", + "BIRDS_MIGRATIONS_LIST_TITLE_SECONDARY": "Observations of migratory soaring birds from a viewpoint", + "MONITORING_LIST_BIRDS_MIGRATION_POINT": "Migration point", + "LIST_FILTER_BIRDS_MIGRATION_POINT": "List of birds migration point", + "BIRDS_MIGRATIONS_DETAIL_MIGRATION_POINT": "Migration point", + "BIRDS_MIGRATIONS_DETAIL_LOCATION_FROM_MIGRATION_POINT": "Direction from migration point", + "BIRDS_MIGRATIONS_DETAIL_SEX": "Sex", + "BIRDS_MIGRATIONS_DETAIL_PLUMAGE": "Pulmage", + "BIRDS_MIGRATIONS_DETAIL_AGE": "Age", + "BIRDS_MIGRATIONS_DETAIL_VISOCHINA_POLET": "Flight height, m", + "BIRDS_MIGRATIONS_DETAIL_POSOKA_POLET_FROM": "Flight direction from", + "BIRDS_MIGRATIONS_DETAIL_POSOKA_POLET_TO": "Flight direction to", + "BIRDS_MIGRATIONS_DETAIL_TYPE_FLIGHT": "Type of flight", + "DETAIL_DISTANCE_FROM_MIGRATION_POINT": "Distance", + "FORM_LABEL_FISHES": "Form Fishes", + "FORM_FISHES_LONG": "Form Fishes", + "FORM_FISHES_SHORT": "Form Fishes", + "FISHES_LIST_TITLE_MAIN": "Form Fishes", + "FISHES_LIST_TITLE_SECONDARY": "Observations of Fishes", + "FISHES_DETAIL_SEX": "Sex", + "FISHES_DETAIL_NAME_WATER_BODY": "Warerbody", + "FISHES_DETAIL_AGE": "Age", + "FISHES_DETAIL_SIZE_TL_MM": "Total length (TL), mm", + "FISHES_DETAIL_SIZE_SL_MM": "Standart length (Sl), mm", + "FISHES_DETAIL_MASA_GR": "Weight, gr", + "FISHES_DETAIL_FINDINGS": "Findings", + "FISHES_DETAIL_MONITORING_TYPE": "Monitoring type", + "FISHES_DETAIL_TRANSECT_LENGTH_M": "Transect length, m", + "FISHES_DETAIL_TRANSECT_WIDTH_M": "Transect width, m", + "FISHES_DETAIL_FISHING_AREA_M": "Fishing area, sq. m", + "FISHES_DETAIL_EXPOSITION": "Exposition", + "FISHES_DETAIL_MESH_SIZE": "Mesh size", + "FISHES_DETAIL_COUNT_NET_TRAP": "Count nets/traps", + "FISHES_DETAIL_WATER_TEMP": "Water temperature", + "FISHES_DETAIL_CONDUCTIVITY": "Conductivity", + "FISHES_DETAIL_PH": "pH", + "FISHES_DETAIL_O2MG_L": "O2 mg/l", + "FISHES_DETAIL_O2PERCENT": "O2 %", + "FISHES_DETAIL_SALINITY": "Salinity", + "FISHES_DETAIL_HABITAT_DESCRIPTION_TYPE": "Waterbody type", + "FISHES_DETAIL_SUBSTRATE_MUD": "Mud, %", + "FISHES_DETAIL_SUBSTRATE_SILT": "Silt, %", + "FISHES_DETAIL_SUBSTRATE_SAND": "Sand, %", + "FISHES_DETAIL_SUBSTRATE_GRAVEL": "Gravel, %", + "FISHES_DETAIL_SUBSTRATE_SMALL_STONES": "Small stones, %", + "FISHES_DETAIL_SUBSTRATE_COBBLE": "Cobble, %", + "FISHES_DETAIL_SUBSTRATE_BOULDER": "Boulder, %", + "FISHES_DETAIL_SUBSTRATE_ROCK": "Rock, %", + "FISHES_DETAIL_SUBSTRATE_OTHER": "Other, %", + "FISHES_DETAIL_WATER_LEVEL": "Water level", + "FISHES_DETAIL_RIVER_CURRENT": "River current", + "FISHES_DETAIL_TRANSECT_AV_DEPTH": "Average depth", + "FISHES_DETAIL_TRANSECT_MAX_DEPTH": "Max. depth", + "FISHES_DETAIL_SLOPE": "Slope", + "FISHES_DETAIL_BANK_TYPE": "Bank type", + "FISHES_DETAIL_SHADING": "Shading, %", + "FISHES_DETAIL_RIPARIAN_VEGETATION": "Riparian vegetation, %", + "FISHES_DETAIL_SHELTERS": "Shelters", + "FISHES_DETAIL_TRANSPARENCY": "Water transperancy", + "FISHES_DETAIL_VEGETATION_TYPE": "Vegetation type", + "FISHES_DETAIL_NATURAL_BARRIERS": "Natural barriers", + "CLASS_FISHES": "Fishes", + "PUBLIC_FISHES_TEXT": "This part of SmartBirds.org is implemented with the financial support of the LIFE Program of the European Union.", + "PUBLIC_FISHES_TITLE": "Form Fishes", + "PUBLIC_FISHES_SUBTITLE": "Observations of Fishes", + "PUBLIC_MAP_SPECIES": "species", + "PUBLIC_MAP_INDIVIDUALS": "Individuals", + "USER_DETAIL_ALLOW_DATA_MOSV": "I agree that the data collected by me will be provided to the MOEW", + "USER_DETAIL_ALLOW_DATA_SCIENCE_PUBLICATIONS": "I agree that the data collected by me will be published in a scientific article", + "NAV_BIRDS_MIGRATIONS": "Form Birds migrations", + "STATS_BIRDS_MIGRATIONS_SEASON_TOTALS_TITLE_TAB": "Total birds migrating per sesones", + "PUBLIC_BIRDS_MIGRATIONS_SEASON_TOTALS": "Total number of migratory birds for each season - autumn (August 10 - October 30) and spring (March 1 to May 31)", + "STATS_BIRDS_MIGRATIONS_PEAK_DAILY_SPECIES_TITLE_TAB": "Peak daily numbers for a season", + "PUBLIC_BIRDS_MIGRATIONS_PEAK_DAILY_SPECIES": "Maximum (peak) daily numbers of migratory birds (with date selection)", + "PUBLIC_FORM_LAST_INTERESTING_SPECIES_COL_SPECIES": "Species", + "PUBLIC_FORM_LAST_INTERESTING_SPECIES_COL_DATE": "Date", + "PUBLIC_BIRDS_MIGRATIONS_COL_MIGRATION_POINT": "Migration point", + "PUBLIC_FORM_LAST_INTERESTING_SPECIES_COL_COUNT": "Count", + "PUBLIC_FORM_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Observer", + "PUBLIC_BIRDS_MIGRATIONS_SEASON_FALL": "fall", + "PUBLIC_BIRDS_MIGRATIONS_SEASON_SPRING": "spring", + "PUBLIC_BIRDS_MIGRATIONS_FILTER_DATE": "Date", + "PUBLIC_BIRDS_MIGRATIONS_PEAK_NO_RECORDS": "No records for the selected date", + "PUBLIC_BIRDS_MIGRATIONS_TITLE": "Form Birds migrations", + "PUBLIC_BIRDS_MIGRATIONS_SUBTITLE": "Observations of migratory soaring birds from a viewpoint", + "PUBLIC_BIRDS_MIGRATIONS_TEXT": "This part of www.SmartBirds.org is implemented in partnership with the National Museum of Natural History at the Bulgarian Academy of Sciences and with the financial support of the LIFE Program of the European Union.", + "PUBLIC_BIRDS_MIGRATIONS_TOP_SPECIES_MONTH": "Interesting species observed for the last 30 days", + "FISHES_PUBLIC_LIST_TITLE_MAIN": "Form Fishes", + "FISHES_PUBLIC_LIST_TITLE_SECONDARY": "Observations of Fishes", + "BIRDS_MIGRATIONS_PUBLIC_LIST_TITLE_MAIN": "Form Birds migrations", + "BIRDS_MIGRATIONS_PUBLIC_LIST_TITLE_SECONDARY": "Observations of migratory soaring birds from a viewpoint", + "STATS_BIRDS_MIGRATIONS_TITLE": "Birds migrations", + "STATS_BIRDS_MIGRATIONS_SUBTITLE": "Observations statistics", + "STATS_FISHES_TITLE": "Form Fishes", + "STATS_FISHES_SUBTITLE": "Observations statistics", + "PUBLIC_FISHES_LAST_INTERESTING_SPECIES_COL_SPECIES": "Species", + "PUBLIC_FISHES_LAST_INTERESTING_SPECIES_COL_DATE": "Date", + "PUBLIC_FISHES_LAST_INTERESTING_SPECIES_COL_LOCATION": "Settlement", + "PUBLIC_FISHES_LAST_INTERESTING_SPECIES_COL_COUNT": "Count", + "PUBLIC_FISHES_LAST_INTERESTING_SPECIES_COL_OBSERVER": "Observer", + "PUBLIC_FISHES_LAST_INTERESTING_SPECIES": "Interesting species observed for the last 30 days", + "PUBLIC_FISHES_TOP_SPECIES_MONTH": "The most often observed species for the last 30 days", + "PUBLIC_FISHES_TOP_OBSERVERS_RECORDS_YEAR": "Most observations recorded per calendar year", + "PUBLIC_FISHES_TOP_OBSERVERS_SPECIES_YEAR": "The most species observed per calendar year", + "PUBLIC_STATISTIC_TABLE_COL_SUM_COUNT": "{{count}} individuals", + "PUBLIC_BIRDS_MIGRATIONS_FILTER_MIGRATION_POINT": "Migration point", + "PUBLIC_BIRDS_MIGRATIONS_SEASON_NO_RECORDS": "No records for the selected migration point", + "PUBLIC_NO_RECORDS": "No records", + "PUBLIC_BIRDS_MIGRATIONS_FILTER_MIGRATION_POINT_PLACEHOLDER": "All", + "PUBLIC_BIRDS_MIGRATIONS_FILTER_SPECIES": "Species", + "PUBLIC_BIRDS_MIGRATIONS_FILTER_SPECIES_PLACEHOLDER": "Choose a species", + "FORM_LIST_BUTTON_FULL_CSV_EXPORT_TOOLTIP": "Generating a csv file with all records found and all fields", + "NAV_MAP_LAYERS": "Map layers", + "MAP_LAYERS_TITLE": "Map layers", + "MAP_LAYERS_UNSAVED_DATA_TITLE": "There are unsaved changes!", + "MAP_LAYERS_UNSAVED_DATA_MESSAGE": "There are unsaved changes! Are you sure you want to continue?", + "MAP_LAYERS_COL_LABEL": "Label", + "MAP_LAYERS_COL_SUMMARY": "Summary", + "MAP_LAYERS_COL_URL": "URL", + "MAP_LAYERS_ROW_WIDTH": "Width (px)", + "MAP_LAYERS_COL_WIDTH": "Height (px)", + "MAP_LAYERS_BUTTON_ADD": "Add", + "MAP_LAYERS_BUTTON_SAVE": "Save", + "FORM_LIST_BUTTON_FULL_ZIP_EXPORT_TOOLTIP": "Generating a zip file with all records found and all fields, along with their corresponding pictures and gpx files", + "CLASS_BATS": "Bats", + "FORM_LABEL_BATS": "Form Bats", + "FORM_BATS_LONG": "Form Bats", + "FORM_BATS_SHORT": "Form Bats", + "BATS_LIST_TITLE_MAIN": "Form Bats", + "BATS_LIST_TITLE_SECONDARY": "Observations of bats", + "BATS_DETAIL_METODOLOGY": "Methodology", + "BATS_DETAIL_T_CAVE": "T°C Roost", + "BATS_DETAIL_H_CAVE": "H - Roost humidity (%)", + "BATS_DETAIL_TYPE_LOCATION": "Type of the locality", + "BATS_DETAIL_SUBLOCALITY": "Sublocality", + "BATS_DETAIL_SWARMING": "Swarming", + "BATS_DETAIL_AGE": "Age", + "BATS_DETAIL_SEX": "Sex", + "BATS_DETAIL_HABITAT": "Habitats", + "BATS_DETAIL_CONDITION": "Condition", + "BATS_DETAIL_TYPE_CONDITION": "Type of cont", + "BATS_DETAIL_REPRODUCTIVE_STATUS": "Reproductive status", + "BATS_DETAIL_RING": "Ring", + "BATS_DETAIL_RING_N": "Ring number", + "BATS_DETAIL_BODY_LENGTH": "L - Body length (mm)", + "BATS_DETAIL_TAIL_LENGTH": "C - Tail length (mm)", + "BATS_DETAIL_EAR_LENGTH": "A - Ear length (mm)", + "BATS_DETAIL_FOREARM_LENGTH": "FA - Forearm length (mm)", + "BATS_DETAIL_LENGTH_THIRD_DIGIT": "D3 - Length third digit (mm)", + "BATS_DETAIL_LENGTH_FIFTH_DIGIT": "D5 - Length fifth digit (mm)", + "BATS_DETAIL_LENGTH_WS": "WS - Length (mm)", + "BATS_DETAIL_WEIGHT": "Weight (g)", + "BATS_DETAIL_SWARMING_HELP": " ", + "BATS_DETAIL_AGE_HELP": "Age of the individual", + "BATS_DETAIL_SEX_HELP": "Sex of the individual", + "BATS_DETAIL_CONDITION_HELP": "Condition of the ind. bat", + "BATS_DETAIL_TRAGUS": "Tr - Tragus", + "BATS_DETAIL_UPPER_MOLAR": "Upper molar - CM3", + "BATS_PUBLIC_LIST_TITLE_MAIN": "Form Bats", + "BATS_PUBLIC_LIST_TITLE_SECONDARY": "Observations of bats", + "IMPORT_SUMMARY_TITLE": "Import Summary", + "IMPORT_SUMMARY_IMPORTING": "Importing...", + "IMPORT_SUMMARY_TOTAL": "Total", + "IMPORT_SUMMARY_PROCESSED": "Processed", + "IMPORT_SUMMARY_ROWS_INSERTED": "Inserted", + "IMPORT_SUMMARY_ROWS_UPDATED": "Updated", + "IMPORT_SUMMARY_ERRORS": "Errors", + "IMPORT_SUMMARY_ROW": "Row", + "BTN_FORCE_IMPORT": "Force import", + "FORCE_IMPORT_WARNING_MESSAGE": "There are errors in the provided file. Do you want to proceed and ignore the rows with errors?", + "IMPORT_RECORDS_MODAL_TITLE": "Import from file", + "IMPORT_RECORDS_CHOOSE_FILE": "Choose file", + "IMPORT_RECORDS_READING_FILE": "Loading file...", + "IMPORT_RECORDS_LANGUAGE": "Language", + "BTN_IMPORT": "Import", + "FORM_LIST_BUTTON_CSV_IMPORT_TOOLTIP": "Import from CSV file", + "FISHES_DETAIL_TOTAL_LENGTH": "Total length (TL) class, cm", + "IMPORT_RECORDS_IGNORE_ERRORS": "Ignore errors", + "You will be notified by email when your import is ready": "You will be notified by email when your import is ready", + "EBP_SETTINGS_BUTTON_SAVE": "Save", + "EBP_SETTINGS_BUTTON_ADD": "Add", + "EBP_SETTINGS_TITLE": "Settings", + "EBP_SETTINGS_ORGANIZATIONS": "Organizations", + "EBP_SETTINGS_SOURCES": "Sources", + "EBP_SETTINGS_PROTOCOL": "Protocol", + "EBP_SETTINGS_SPECIES": "Species", + "EBP_SETTINGS_SPECIES_STATUS": "Species status", + "EBP_SETTINGS_NO_PERMISSION": "You do not have permission to access this page", + "EBP_SETTINGS_ORGANIZATION": "Organization", + "EBP_SETTINGS_ORGANIZATION_ENABLED": "Enabled", + "EBP_SETTINGS_SOURCE": "Source", + "EBP_SETTINGS_SOURCE_ENABLED": "Enabled", + "EBP_SETTINGS_SPECIES_CODE": "EBP Code", + "EBP_SETTINGS_SPECIES_SB_NAME": "SB name", + "EBP_SETTINGS_SPECIES_EBP_NAME": "EBP name", + "EBP_SETTINGS_SPECIES_STATUS_CODE": "EBP status code", + "EBP_SETTINGS_SPECIES_STATUS_SB_NAME": "SB status name", + "NAV_EBP": "EBP" +} diff --git a/server/actions/ebp.js b/server/actions/ebp.js new file mode 100644 index 000000000..1ceaaf31c --- /dev/null +++ b/server/actions/ebp.js @@ -0,0 +1,331 @@ +const paging = require('../helpers/paging') +const incremental = require('../helpers/incremental') +const { upgradeAction } = require('../utils/upgrade') +const Promise = require('bluebird') + +exports.ebbSpeciesList = upgradeAction('ah17', { + name: 'ebp:speciesList', + description: 'ebp:speciesList', + middleware: ['admin'], + inputs: paging.declareInputs(incremental.declareInputs({ + filter: {} + })), + + run: function (api, data, next) { + try { + let q = { + order: [['sbNameLa', 'ASC']] + } + q = paging.prepareQuery(q, data.params) + return api.models.ebp_birds.findAndCountAll(q).then(function (result) { + data.response.count = result.count + data.response.meta = incremental.generateMeta(data, paging.generateMeta(result.count, data)) + data.response.data = result.rows.map(function (ebpSpecies) { + return ebpSpecies.apiData(api) + }) + return next() + }).catch(function (e) { + console.error('Failure for list EBP species', e) + return next(e) + }) + } catch (e) { + console.error(e) + return next(e) + } + } +}) + +exports.ebbSpeciesUpdate = upgradeAction('ah17', { + name: 'ebp:speciesUpdate', + description: 'ebp:speciesUpdate', + middleware: ['admin'], + inputs: { + items: { required: true } + }, + + run: function (api, data, next) { + Promise.resolve() + .then(function () { + if (!(data.params.items instanceof Array)) { + data.connection.rawConnection.responseHttpCode = 400 + throw new Error('items is not array') + } + if (data.params.items.length <= 0) { + data.connection.rawConnection.responseHttpCode = 400 + throw new Error('cannot update with empty items') + } + return data.params.items.map(function (item) { + const record = api.models.ebp_birds.build({}) + record.apiUpdate(item) + return record + }) + }) + .then(function (models) { + return api.sequelize.sequelize.transaction(function (t) { + return api.models.ebp_birds + .destroy({ + where: {}, + transaction: t + }) + .then(function (deleted) { + api.log('replacing ebp_birds %d with %d', 'info', deleted, models.length) + }) + .then(function () { + return Promise.map(models, function (item) { + return item.save({ transaction: t }).then((m) => m.apiData()) + }) + }) + }) + }) + .then(function (result) { + data.response.count = result.length + data.response.data = result + }) + .then(function () { + return next() + }, function (e) { + api.log('Failure to update ebp_birds', 'error', e) + return next(e) + }) + } +}) + +exports.ebpSpeciesStatusList = upgradeAction('ah17', { + name: 'ebp:speciesStatusList', + description: 'ebp:speciesStatusList', + middleware: ['admin'], + + run: function (api, data, next) { + try { + const q = { + order: [['ebpId', 'ASC']] + } + return api.models.ebp_birds_status.findAndCountAll(q).then(function (result) { + data.response.count = result.count + data.response.data = result.rows.map(function (ebpSpeciesStatus) { + return ebpSpeciesStatus.apiData(api) + }) + return next() + }).catch(function (e) { + console.error('Failure for list EBP species statuses', e) + return next(e) + }) + } catch (e) { + console.error(e) + return next(e) + } + } +}) + +exports.ebbSpeciesStatusUpdate = upgradeAction('ah17', { + name: 'ebp:speciesStatusUpdate', + description: 'ebp:speciesStatusUpdate', + middleware: ['admin'], + inputs: { + items: { required: true } + }, + + run: function (api, data, next) { + Promise.resolve() + .then(function () { + if (!(data.params.items instanceof Array)) { + data.connection.rawConnection.responseHttpCode = 400 + throw new Error('items is not array') + } + if (data.params.items.length <= 0) { + data.connection.rawConnection.responseHttpCode = 400 + throw new Error('cannot update with empty items') + } + return data.params.items.map(function (item) { + const record = api.models.ebp_birds_status.build({}) + record.apiUpdate(item) + return record + }) + }) + .then(function (models) { + return api.sequelize.sequelize.transaction(function (t) { + return api.models.ebp_birds_status + .destroy({ + where: {}, + transaction: t + }) + .then(function (deleted) { + api.log('replacing ebp_birds_status %d with %d', 'info', deleted, models.length) + }) + .then(function () { + return Promise.map(models, function (item) { + return item.save({ transaction: t }).then((m) => m.apiData()) + }) + }) + }) + }) + .then(function (result) { + data.response.count = result.length + data.response.data = result + }) + .then(function () { + return next() + }, function (e) { + api.log('Failure to update ebp_birds_status', 'error', e) + return next(e) + }) + } +}) + +exports.ebpOrganizationsList = upgradeAction('ah17', { + name: 'ebp:organizationsList', + description: 'ebp:organizationsList', + middleware: ['admin'], + + run: function (api, data, next) { + try { + return api.models.settings.findOne({ + where: { + name: 'ebp_organizations' + } + }).then(function (ebpOrganizations) { + data.response.data = JSON.parse(ebpOrganizations?.value || '[]') + return next() + }) + } catch (e) { + console.error(e) + return next(e) + } + } +}) + +exports.ebpOrganizationsUpdate = upgradeAction('ah17', { + name: 'ebp:organizationsUpdate', + description: 'ebp:organizationsUpdate', + middleware: ['admin'], + inputs: { + items: { required: true } + }, + run: function (api, data, next) { + try { + return api.models.settings.findOne({ + where: { + name: 'ebp_organizations' + } + }).then(function (ebpOrganizations) { + if (!ebpOrganizations) { + ebpOrganizations = api.models.settings.build({ + name: 'ebp_organizations' + }) + } + ebpOrganizations.value = JSON.stringify(data.params.items) + ebpOrganizations.save() + return next() + }) + } catch (e) { + console.error(e) + return next(e) + } + } +}) + +exports.ebpSourcesList = upgradeAction('ah17', { + name: 'ebp:sourcesList', + description: 'ebp:sourcesList', + middleware: ['admin'], + + run: function (api, data, next) { + try { + return api.models.settings.findOne({ + where: { + name: 'ebp_sources' + } + }).then(function (ebpSources) { + data.response.data = JSON.parse(ebpSources?.value || '[]') + return next() + }) + } catch (e) { + console.error(e) + return next(e) + } + } +}) + +exports.ebpSourcesUpdate = upgradeAction('ah17', { + name: 'ebp:sourcesUpdate', + description: 'ebp:sourcesUpdate', + middleware: ['admin'], + inputs: { + items: { required: true } + }, + run: function (api, data, next) { + try { + return api.models.settings.findOne({ + where: { + name: 'ebp_sources' + } + }).then(function (ebpSources) { + if (!ebpSources) { + ebpSources = api.models.settings.build({ + name: 'ebp_sources' + }) + } + ebpSources.value = JSON.stringify(data.params.items) + ebpSources.save() + return next() + }) + } catch (e) { + console.error(e) + return next(e) + } + } +}) + +exports.ebpProtocolGet = upgradeAction('ah17', { + name: 'ebp:protocolGet', + description: 'ebp:protocolGet', + middleware: ['admin'], + + run: function (api, data, next) { + try { + return api.models.settings.findOne({ + where: { + name: 'ebp_protocol' + } + }).then(function (setting) { + data.response.data = { + protocol: setting?.value || '' + } + return next() + }) + } catch (e) { + console.error(e) + return next(e) + } + } +}) + +exports.ebpProtocolUpdate = upgradeAction('ah17', { + name: 'ebp:protocolUpdate', + description: 'ebp:protocolUpdate', + middleware: ['admin'], + inputs: { + protocol: {} + }, + run: function (api, data, next) { + try { + return api.models.settings.findOne({ + where: { + name: 'ebp_protocol' + } + }).then(function (setting) { + if (!setting) { + setting = api.models.settings.build({ + name: 'ebp_protocol' + }) + } + setting.value = data.params.protocol + setting.save() + return next() + }) + } catch (e) { + console.error(e) + return next(e) + } + } +}) diff --git a/server/actions/tasks.js b/server/actions/tasks.js index 2383fda45..1f83b7fc3 100644 --- a/server/actions/tasks.js +++ b/server/actions/tasks.js @@ -1,4 +1,5 @@ const { Action, api } = require('actionhero') +const differenceInDays = require('date-fns/differenceInDays') class BaseAction extends Action { constructor () { @@ -110,3 +111,48 @@ module.exports.autoTranslateNomenclatures = class EnqueueAutoTranslateNomenclatu return await api.tasks.enqueue('autoTranslateNomenclatures', { form, id, limit, force }) } } + +module.exports.etrs89Codes = class EnqueueFillEtrs89Codes extends BaseAction { + constructor () { + super() + this.name = 'tasks:enqueue:etrs89Codes' + this.description = 'Trigger filling ETRS89-LAEA codes' + } + + availableForms () { + return super.availableForms().filter(k => api.forms[k].hasEtrs89GridCode) + } + + async enqueue ({ form, id, limit, force }) { + return await api.tasks.enqueue('fill-etrs89-codes', { form, id, limit, force }) + } +} + +module.exports.ebpUpload = class EnqueueEbpUpload extends Action { + constructor () { + super() + this.name = 'tasks:enqueue:ebpUpload' + this.description = 'Trigger EBP upload' + this.middleware = ['auth', 'admin'] + this.inputs = { + startDate: {}, + endDate: {}, + mode: {} + } + } + + async run ({ params: { startDate, endDate, mode }, response }) { + if (startDate && endDate) { + // check if startDate is before endDate + if (new Date(startDate) > new Date(endDate)) { + throw new Error('startDate must be before endDate') + } + + if (differenceInDays(new Date(endDate), new Date(startDate)) > 31) { + throw new Error('Date range must be less than 31 days') + } + } + + response.result = await api.tasks.enqueue('ebpUpload', { startDate, endDate, mode: mode || 'insert' }) + } +} diff --git a/server/config/app.js b/server/config/app.js index b1d28a8fa..bc72d559d 100644 --- a/server/config/app.js +++ b/server/config/app.js @@ -27,6 +27,15 @@ exports.default = { translate: { // max records per task maxRecords: parseInt(process.env.AUTO_TRANSLATE_MAX_RECORDS, 10) || 100 + }, + // ETRS89 configuration + etrs89: { + // size of the grid cell in meters + gridSize: 10000, + // max records per task + maxRecords: parseInt(process.env.ETRS89_TASK_MAX_RECORDS, 10) || 100, + // consider records newer than this timestamp + startTimestamp: parseInt(process.env.ETRS89_TASK_START_TIME, 10) || new Date('2024-01-01').getTime() } } } diff --git a/server/config/routes.js b/server/config/routes.js index d9cc3cb70..286b76803 100644 --- a/server/config/routes.js +++ b/server/config/routes.js @@ -112,7 +112,13 @@ exports.default = { { path: '/threats', action: 'formThreats:list' }, { path: '/threats.csv', action: 'formThreats:list' }, { path: '/threats.zip', action: 'formThreats:list' }, - { path: '/threats/:id', action: 'formThreats:view' } + { path: '/threats/:id', action: 'formThreats:view' }, + + { path: '/ebp/species', action: 'ebp:speciesList' }, + { path: '/ebp/species-status', action: 'ebp:speciesStatusList' }, + { path: '/ebp/organizations', action: 'ebp:organizationsList' }, + { path: '/ebp/sources', action: 'ebp:sourcesList' }, + { path: '/ebp/protocol', action: 'ebp:protocolGet' } ], post: [ @@ -133,6 +139,8 @@ exports.default = { { path: '/zone/:id/owner', action: 'zone:requestOwnership' }, { path: '/zone/:id/owner/response', action: 'zone:respondOwnershipRequest' }, { path: '/tasks/auto-translate-nomenclatures', action: 'tasks:enqueue:autoTranslateNomenclatures' }, + { path: '/tasks/etrs89', action: 'tasks:enqueue:etrs89Codes' }, + { path: '/tasks/ebp-upload', action: 'tasks:enqueue:ebpUpload' }, // forms { path: '/bats', action: 'formBats:create' }, @@ -210,7 +218,12 @@ exports.default = { { path: '/pois/:type', action: 'poi:updateType' }, { path: '/session', action: 'session:check' }, { path: '/species/:type', action: 'species:updateType' }, - { path: '/zone/:id/owner', action: 'zone:setOwner' } + { path: '/zone/:id/owner', action: 'zone:setOwner' }, + { path: '/ebp/species', action: 'ebp:speciesUpdate' }, + { path: '/ebp/species-status', action: 'ebp:speciesStatusUpdate' }, + { path: '/ebp/organizations', action: 'ebp:organizationsUpdate' }, + { path: '/ebp/sources', action: 'ebp:sourcesUpdate' }, + { path: '/ebp/protocol', action: 'ebp:protocolUpdate' } ], patch: [ diff --git a/server/forms/_fields/etrs89GridCode.js b/server/forms/_fields/etrs89GridCode.js new file mode 100644 index 000000000..c3420179f --- /dev/null +++ b/server/forms/_fields/etrs89GridCode.js @@ -0,0 +1,16 @@ +module.exports = { + fields: { + etrs89GridCode: { + type: 'text', + length: 8, + required: false, + public: false, + uniqueHash: false, + afterApiUpdate (model) { + if (model.changed('latitude') || model.changed('longitude')) { + model.etrs89GridCode = null + } + } + } + } +} diff --git a/server/forms/birds.js b/server/forms/birds.js index 25befc902..2eb8ddbc9 100644 --- a/server/forms/birds.js +++ b/server/forms/birds.js @@ -2,6 +2,7 @@ const _ = require('lodash') const { assign } = Object const bgatlas2008 = require('./_fields/bgatlas2008') const newSpeciesModeratorReview = require('./_fields/newSpeciesModeratorReview') +const etrs89GridCode = require('./_fields/etrs89GridCode') exports = module.exports = _.cloneDeep(require('./_common')) @@ -9,10 +10,12 @@ exports.tableName = 'FormBirds' exports.hasSpecies = true exports.hasThreats = true exports.hasBgAtlas2008 = true +exports.hasEtrs89GridCode = true exports.fields = assign(exports.fields, { ...bgatlas2008.fields, ...newSpeciesModeratorReview.fields, + ...etrs89GridCode.fields, species: { type: 'choice', required: true, diff --git a/server/forms/cbm.js b/server/forms/cbm.js index 367c23266..bc613ee26 100644 --- a/server/forms/cbm.js +++ b/server/forms/cbm.js @@ -6,6 +6,7 @@ const longitude = require('./_fields/longitude') const observationDateTime = require('./_fields/observationDateTime') const bgatlas2008 = require('./_fields/bgatlas2008') const newSpeciesModeratorReview = require('./_fields/newSpeciesModeratorReview') +const etrs89GridCode = require('./_fields/etrs89GridCode') exports = module.exports = _.cloneDeep(require('./_common')) @@ -14,10 +15,12 @@ exports.tableName = 'FormCBM' exports.hasSpecies = true exports.hasThreats = true exports.hasBgAtlas2008 = true +exports.hasEtrs89GridCode = true exports.fields = { ...bgatlas2008.fields, ...newSpeciesModeratorReview.fields, + ...etrs89GridCode.fields, confidential: 'boolean', plot: { type: 'choice', diff --git a/server/forms/ciconia.js b/server/forms/ciconia.js index d14c3939d..be14502e5 100644 --- a/server/forms/ciconia.js +++ b/server/forms/ciconia.js @@ -2,6 +2,7 @@ const _ = require('lodash') const { assign } = Object const bgatlas2008 = require('./_fields/bgatlas2008') const newSpeciesModeratorReview = require('./_fields/newSpeciesModeratorReview') +const etrs89GridCode = require('./_fields/etrs89GridCode') exports = module.exports = _.cloneDeep(require('./_common')) @@ -9,10 +10,12 @@ exports.tableName = 'FormCiconia' exports.hasSpecies = false exports.hasThreats = true exports.hasBgAtlas2008 = true +exports.hasEtrs89GridCode = true exports.fields = assign(exports.fields, { ...bgatlas2008.fields, ...newSpeciesModeratorReview.fields, + ...etrs89GridCode.fields, primarySubstrateType: { type: 'choice', uniqueHash: true, diff --git a/server/migrations/20241024134104-add-birds-forms-etrs89GridCode.js b/server/migrations/20241024134104-add-birds-forms-etrs89GridCode.js new file mode 100644 index 000000000..aff0674d8 --- /dev/null +++ b/server/migrations/20241024134104-add-birds-forms-etrs89GridCode.js @@ -0,0 +1,22 @@ +'use strict' + +const tables = [ + 'FormBirds', + 'FormCBM', + 'FormCiconia' +] + +module.exports = { + up: async function (queryInterface, DataTypes) { + await Promise.all(tables.map(async (table) => { + await queryInterface.addColumn(table, 'etrs89GridCode', DataTypes.STRING(20)) + await queryInterface.addIndex(table, { fields: ['etrs89GridCode'] }) + })) + }, + + down: async function (queryInterface) { + await Promise.all(tables.map(async (table) => { + await queryInterface.removeColumn(table, 'etrs89GridCode') + })) + } +} diff --git a/server/migrations/20241025074805-create-etrs89-cells.js b/server/migrations/20241025074805-create-etrs89-cells.js new file mode 100644 index 000000000..240e618da --- /dev/null +++ b/server/migrations/20241025074805-create-etrs89-cells.js @@ -0,0 +1,59 @@ +'use strict' + +const tableName = 'etrs89_cells' + +module.exports = { + up: async function (queryInterface, Sequelize) { + await queryInterface.createTable(tableName, { + code: { + type: Sequelize.STRING(20), + primaryKey: true + }, + lat1: { + type: Sequelize.DOUBLE, + allowNull: false + }, + lon1: { + type: Sequelize.DOUBLE, + allowNull: false + }, + lat2: { + type: Sequelize.DOUBLE, + allowNull: false + }, + lon2: { + type: Sequelize.DOUBLE, + allowNull: false + }, + lat3: { + type: Sequelize.DOUBLE, + allowNull: false + }, + lon3: { + type: Sequelize.DOUBLE, + allowNull: false + }, + lat4: { + type: Sequelize.DOUBLE, + allowNull: false + }, + lon4: { + type: Sequelize.DOUBLE, + allowNull: false + } + }) + + await queryInterface.addIndex(tableName, { name: `${tableName}_lat1`, fields: ['lat1'] }) + await queryInterface.addIndex(tableName, { name: `${tableName}_lat2`, fields: ['lat2'] }) + await queryInterface.addIndex(tableName, { name: `${tableName}_lat3`, fields: ['lat3'] }) + await queryInterface.addIndex(tableName, { name: `${tableName}_lat4`, fields: ['lat4'] }) + await queryInterface.addIndex(tableName, { name: `${tableName}_lon1`, fields: ['lon1'] }) + await queryInterface.addIndex(tableName, { name: `${tableName}_lon2`, fields: ['lon2'] }) + await queryInterface.addIndex(tableName, { name: `${tableName}_lon3`, fields: ['lon3'] }) + await queryInterface.addIndex(tableName, { name: `${tableName}_lon4`, fields: ['lon4'] }) + }, + + down: async function (queryInterface, Sequelize) { + await queryInterface.dropTable(tableName) + } +} diff --git a/server/migrations/20241126151813-create-ebp-birds.js b/server/migrations/20241126151813-create-ebp-birds.js new file mode 100644 index 000000000..d44ba3c53 --- /dev/null +++ b/server/migrations/20241126151813-create-ebp-birds.js @@ -0,0 +1,32 @@ +'use strict' + +const tableName = 'ebp_birds' + +module.exports = { + up: async function (queryInterface, Sequelize) { + await queryInterface.createTable(tableName, { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true + }, + ebpId: { + type: Sequelize.INTEGER, + allowNull: false + }, + ebpNameLa: { + type: Sequelize.TEXT + }, + sbNameLa: { + type: Sequelize.TEXT + } + }) + + await queryInterface.addIndex(tableName, { unique: true, fields: ['ebpId'] }) + await queryInterface.addIndex(tableName, { unique: true, fields: ['sbNameLa'] }) + }, + + down: async function (queryInterface, Sequelize) { + await queryInterface.dropTable(tableName) + } +} diff --git a/server/migrations/20241127094732-create-ebp-birds-status.js b/server/migrations/20241127094732-create-ebp-birds-status.js new file mode 100644 index 000000000..4c23bfca7 --- /dev/null +++ b/server/migrations/20241127094732-create-ebp-birds-status.js @@ -0,0 +1,27 @@ +'use strict' + +const tableName = 'ebp_birds_status' + +module.exports = { + up: async function (queryInterface, Sequelize) { + await queryInterface.createTable(tableName, { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true + }, + ebpId: { + type: Sequelize.INTEGER, + allowNull: false + }, + sbNameEn: { + type: Sequelize.TEXT + } + }) + await queryInterface.addIndex(tableName, { unique: true, fields: ['sbNameEn'] }) + }, + + down: async function (queryInterface, Sequelize) { + await queryInterface.dropTable(tableName) + } +} diff --git a/server/migrations/20250108094110-create-settings.js b/server/migrations/20250108094110-create-settings.js new file mode 100644 index 000000000..f66959ac0 --- /dev/null +++ b/server/migrations/20250108094110-create-settings.js @@ -0,0 +1,31 @@ +'use strict' + +const { DataTypes } = require('sequelize') +const tableName = 'settings' + +module.exports = { + up: async function (queryInterface, Sequelize) { + await queryInterface.createTable(tableName, { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true + }, + name: { + type: Sequelize.TEXT, + allowNull: false + }, + value: { + type: Sequelize.TEXT + }, + createdAt: DataTypes.DATE, + updatedAt: DataTypes.DATE + }) + + await queryInterface.addIndex(tableName, { unique: true, fields: ['name'] }) + }, + + down: async function (queryInterface, Sequelize) { + await queryInterface.dropTable(tableName) + } +} diff --git a/server/models/ebp_birds.js b/server/models/ebp_birds.js new file mode 100644 index 000000000..f508e3a45 --- /dev/null +++ b/server/models/ebp_birds.js @@ -0,0 +1,42 @@ +'use strict' +module.exports = function (sequelize, DataTypes) { + return sequelize.define('ebp_birds', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + ebpId: { + type: DataTypes.INTEGER, + allowNull: false + }, + ebpNameLa: { + type: DataTypes.TEXT + }, + sbNameLa: { + type: DataTypes.TEXT + } + }, { + tableName: 'ebp_birds', + timestamps: false, + indexes: [ + { unique: true, fields: ['ebpId'] }, + { unique: true, fields: ['sbNameLa'] } + ], + instanceMethods: { + apiData: function () { + return { + id: this.id, + ebpId: this.ebpId, + ebpNameLa: this.ebpNameLa, + sbNameLa: this.sbNameLa + } + }, + apiUpdate: function (data) { + this.ebpId = data.ebpId + this.ebpNameLa = data.ebpNameLa + this.sbNameLa = data.sbNameLa + } + } + }) +} diff --git a/server/models/ebp_birds_status.js b/server/models/ebp_birds_status.js new file mode 100644 index 000000000..c967c1a9e --- /dev/null +++ b/server/models/ebp_birds_status.js @@ -0,0 +1,36 @@ +'use strict' +module.exports = function (sequelize, DataTypes) { + return sequelize.define('ebp_birds_status', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + ebpId: { + type: DataTypes.INTEGER, + allowNull: false + }, + sbNameEn: { + type: DataTypes.TEXT + } + }, { + tableName: 'ebp_birds_status', + timestamps: false, + indexes: [ + { unique: true, fields: ['sbNameEn'] } + ], + instanceMethods: { + apiData: function () { + return { + id: this.id, + ebpId: this.ebpId, + sbNameEn: this.sbNameEn + } + }, + apiUpdate: function (data) { + this.ebpId = data.ebpId + this.sbNameEn = data.sbNameEn + } + } + }) +} diff --git a/server/models/etrs89_cell.js b/server/models/etrs89_cell.js new file mode 100644 index 000000000..3e57ffa32 --- /dev/null +++ b/server/models/etrs89_cell.js @@ -0,0 +1,60 @@ +module.exports = function (sequelize, DataTypes) { + return sequelize.define('etrs89_cell', { + code: { + type: DataTypes.TEXT, + primaryKey: true + }, + lat1: { + type: DataTypes.DOUBLE, + allowNull: false + }, + lon1: { + type: DataTypes.DOUBLE, + allowNull: false + }, + lat2: { + type: DataTypes.DOUBLE, + allowNull: false + }, + lon2: { + type: DataTypes.DOUBLE, + allowNull: false + }, + lat3: { + type: DataTypes.DOUBLE, + allowNull: false + }, + lon3: { + type: DataTypes.DOUBLE, + allowNull: false + }, + lat4: { + type: DataTypes.DOUBLE, + allowNull: false + }, + lon4: { + type: DataTypes.DOUBLE, + allowNull: false + } + }, { + tableName: 'etrs89_cells', + timestamps: false, + underscored: true, + instanceMethods: { + apiData: function () { + return { + code: this.code, + coordinates: this.coordinates() + } + }, + coordinates: function () { + return [ + { latitude: this.lat1, longitude: this.lon1 }, + { latitude: this.lat2, longitude: this.lon2 }, + { latitude: this.lat3, longitude: this.lon3 }, + { latitude: this.lat4, longitude: this.lon4 } + ] + } + } + }) +} diff --git a/server/models/settings.js b/server/models/settings.js new file mode 100644 index 000000000..33bc3a674 --- /dev/null +++ b/server/models/settings.js @@ -0,0 +1,38 @@ +'use strict' + +module.exports = function (sequelize, DataTypes) { + return sequelize.define('settings', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + name: { + type: DataTypes.TEXT, + allowNull: false + }, + value: { + type: DataTypes.TEXT + }, + createdAt: DataTypes.DATE, + updatedAt: DataTypes.DATE + }, { + tableName: 'settings', + indexes: [ + { unique: true, fields: ['name'] } + ], + instanceMethods: { + apiData: function () { + return { + id: this.id, + name: this.name, + value: this.value + } + }, + apiUpdate: function (data) { + this.name = data.name + this.value = data.value + } + } + }) +} diff --git a/server/tasks/ebpUpload.js b/server/tasks/ebpUpload.js new file mode 100644 index 000000000..4e1a11ec1 --- /dev/null +++ b/server/tasks/ebpUpload.js @@ -0,0 +1,340 @@ +const { Task, api } = require('actionhero') +const sequelize = require('sequelize') +const { Op } = sequelize +const format = require('date-fns/format') +const fetch = require('node-fetch') + +// const availableForms = [api.forms.formCBM, api.forms.formBirds, api.forms.formCiconia] +const availableForms = [api.forms.formBirds] + +// eslint-disable-next-line no-unused-vars +const API_TOKEN = process.env.EBP_API_TOKEN +const API_URL = 'https://api-v2.eurobirdportal.org' +// eslint-disable-next-line no-unused-vars +const apiParams = { + partnerSource: 'BUL_SBI', + provisionMode: { + bulk: { // all data is replaced + code: 'B' + }, + standard: { // new data is added, existing data is updated + code: 'S' + }, + test: { // The data will not be persisted to the database. It is used for testing purposes. + code: 'T' + } + }, + updateMode: { + modify: { // Only updated records will be provided + code: 'M' + }, + all: { // All records will be provided + code: 'A' + } + }, + dataType: { + casual: { // Casual records + code: 'C' + }, + completeList: { // Complete list records + code: 'L' + }, + fixedList: { // Fixed list of records + code: 'F' + } + }, + locationMode: { + exactLocation: { + code: 'E' + }, + coarseLocation: { // location lowered to 10x10km level ETRS89-LAEA grid + code: 'D' + }, + aggregatedLocation: { // data aggregated at 10x10km level ETRS89-LAEA grid + code: 'A' + } + }, + eventState: { + removed: { // Provided event is removed + code: 0 + }, + modified: { // Provided event is new or has been modified + code: 1 + }, + unchanged: { // Provided event is unchanged + code: 2 + } + }, + recordState: { + removed: { // Provided record is removed + code: 0 + }, + modified: { // Provided record is new or has been modified + code: 1 + } + } +} + +const getAllowedOrganizations = async () => { + const organizationsSetting = await api.models.settings.findOne({ + where: { + name: 'ebp_organizations' + } + }) + + return JSON.parse(organizationsSetting?.value || '[]') +} + +const getAllowedSources = async () => { + const sourcesSetting = await api.models.settings.findOne({ + where: { + name: 'ebp_sources' + } + }) + + return JSON.parse(sourcesSetting?.value || '[]') +} + +const getSensitiveSpecies = async () => { + return api.models.species.findAll({ + where: { + sensitive: true, + type: 'birds' + } + } || []) +} + +const getEbpSpecies = async () => { + return api.models.ebp_birds.findAll({ + where: { + sbNameLa: { [Op.not]: null } + } + }) +} + +const getEbpSpeciesStatus = async () => { + return api.models.ebp_birds_status.findAll({ + where: { + sbNameEn: { [Op.not]: null } + } + }) +} + +const loadRecords = async (forms, startDate, endDate) => { + const allowedOrganizations = await getAllowedOrganizations() + const allowedSources = await getAllowedSources() + + const records = await Promise.allSettled(forms.map(async form => { + return form.model.findAll({ + where: { + etrs89GridCode: { [Op.not]: null }, + observationDateTime: { + [Op.and]: [ + { [Op.gte]: startDate }, + { [Op.lte]: endDate } + ] + }, + organization: { [Op.in]: allowedOrganizations }, + sourceEn: { [Op.in]: allowedSources } + } + + }) + })) + + for (const record of records) { + if (record.status === 'rejected') { + throw record.reason + } + } + + return records?.map(record => record.value).flat() +} + +const filterRecords = async (records, ebpSpecies, ebpSpeciesStatus) => { + const sensitiveSpecies = await getSensitiveSpecies() + + // filter records by species, sensitive species and species status + return records + .filter(record => ebpSpecies.find(species => { + return species.sbNameLa === record.species + })) + .filter(record => !sensitiveSpecies.find(species => { + return species.labelLa === record.species + })) + .filter(record => record.speciesStatusEn === null || ebpSpeciesStatus.find(status => status.sbNameEn === record.speciesStatusEn)) +} + +const generateEventId = (etrsCode, date) => { + return format(date, 'yyyyMMdd') + '_' + etrsCode +} + +const prepareEbpData = async (startDate, endDate, mode) => { + const ebpSpecies = await getEbpSpecies() + const ebpSpeciesStatus = await getEbpSpeciesStatus() + + // load all records for the given date + const records = await loadRecords(availableForms, startDate, endDate) + + // additional filtering + const filtered = await filterRecords(records, ebpSpecies, ebpSpeciesStatus) + + // set event and records state based on the mode + const eventState = mode === 'delete' ? apiParams.eventState.removed.code : apiParams.eventState.modified.code + const recordsUpdateMode = mode === 'replace' ? apiParams.updateMode.all.code : apiParams.updateMode.modify.code + const recordState = mode === 'delete' ? apiParams.recordState.removed.code : apiParams.recordState.modified.code + + // common event data + const ebpEvent = { + data_type: apiParams.dataType.casual.code, + location_mode: apiParams.locationMode.aggregatedLocation.code, + state: eventState, + record_updates_mode: recordsUpdateMode + } + + // group records by date + const recordsByDate = filtered.reduce((acc, record) => { + const formattedDate = format(record.observationDateTime, 'yyyy-MM-dd') + if (!acc[formattedDate]) { + acc[formattedDate] = [] + } + acc[formattedDate].push(record) + + return acc + }, {}) + + const ebpEvents = [] + + for (const [formattedDate, records] of Object.entries(recordsByDate)) { + // group records by ETRS89 grid code + const etrsRecords = records.reduce((acc, record) => { + if (!acc[record.etrs89GridCode]) { + acc[record.etrs89GridCode] = [] + } + acc[record.etrs89GridCode].push(record) + + return acc + }, {}) + + // prepare EBP events + ebpEvents.push(Object.entries(etrsRecords).reduce((acc, [etrsCode, records]) => { + const eventData = { + event: { + event_id: generateEventId(etrsCode, new Date(formattedDate)), + location: etrsCode, + date: formattedDate, + ...ebpEvent + }, + records: [] + } + + const observers = [] + const speciesUsersRecords = [] + + if (mode !== 'delete') { + // group records by species + const speciesRecords = records.reduce((acc, record) => { + // count unique species-users records + const key = `${record.species}_${record.userId}` + if (!speciesUsersRecords.includes(key)) { + speciesUsersRecords.push(key) + } + + // count unique observers + if (!observers.includes(record.userId)) { + observers.push(record.userId) + } + + if (!acc[record.species]) { + acc[record.species] = { + species: record.species, + species_code: ebpSpecies.find(species => species.sbNameLa === record.species)?.ebpId, + breeding_code: null, + users: [], + records: [] + } + } + + if (!acc[record.species].users.includes(record.userId)) { + acc[record.species].users.push(record.userId) + } + + if (record.speciesStatusEn) { + const breedingCode = ebpSpeciesStatus.find(status => status.sbNameEn === record.speciesStatusEn)?.ebpId + if (!acc[record.species].breeding_code || acc[record.species].breeding_code < breedingCode) { + acc[record.species].breeding_code = breedingCode + } + } + + acc[record.species].records.push(record) + + return acc + }, {}) + + eventData.records = Object.values(speciesRecords).map(speciesRecord => { + return { + event_id: eventData.event.event_id, + record_id: eventData.event.event_id + '_' + speciesRecord.species_code, + species_code: speciesRecord.species_code, + count: speciesRecord.records.reduce((acc, record) => acc + Math.max(record.count, record.countMin, record.countMax), 0), + records_of_species: speciesRecord.users.length, + breeding_code: speciesRecord.breeding_code, + state: recordState + } + }) + + eventData.event.records = speciesUsersRecords.length + eventData.event.observer = observers.length?.toString() + } + + acc.push(eventData) + return acc + }, [])) + } + + return { + mode: apiParams.provisionMode.standard.code, + partner_source: apiParams.partnerSource, + start_date: format(startDate, 'yyyy-MM-dd'), + end_date: format(endDate, 'yyyy-MM-dd'), + events: ebpEvents.flat().map(event => event.event), + records: mode !== 'delete' ? ebpEvents.flat().map(event => event.records).flat() : [] + } +} + +module.exports = class UploadToEBP extends Task { + constructor () { + super() + this.name = 'ebpUpload' + this.description = 'Upload records to EBP' + // use cronjob to schedule the task + // npm run enqueue upload-to-ebp + this.frequency = 0 + } + + async run ({ startDate, endDate, mode } = {}) { + const startTimestamp = new Date().getTime() + + const eventsData = await prepareEbpData(startDate ? new Date(startDate) : new Date(), endDate ? new Date(endDate) : new Date(), mode) + + const operationTime = new Date().getTime() - startTimestamp + + try { + const response = await fetch(`${API_URL}/data/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${API_TOKEN}` + }, + body: JSON.stringify(eventsData) + }) + + api.log(`Successfully uploaded ${eventsData.events.length} events and ${eventsData.records.length} records to EBP`, 'info') + api.log(`EBP response: ${response.status} ${response.statusText}`, 'info') + } catch (error) { + console.log('Failed to upload data to EBP', error) + } + + console.log('+++++ EBP DATA: ', JSON.stringify(eventsData)) + console.log('+++++ OPERATION TIME: ', operationTime) + } +} diff --git a/server/tasks/fillEtrs89Codes.js b/server/tasks/fillEtrs89Codes.js new file mode 100644 index 000000000..b115f6798 --- /dev/null +++ b/server/tasks/fillEtrs89Codes.js @@ -0,0 +1,95 @@ +const { api } = require('actionhero') +const sequelize = require('sequelize') +const { getBoundsOfDistance, isPointInLine, isPointInPolygon } = require('geolib') +const FormsTask = require('../classes/FormsTask') + +const { Op } = sequelize + +async function getEtrs89GridCode ({ latitude, longitude }) { + if (latitude == null || longitude == null) { + return '' + } + + const point = { latitude, longitude } + + // add 75% over for rounding errors + const bounds = getBoundsOfDistance(point, api.config.app.etrs89.gridSize * 1.75) + const where = {} + for (let i = 1; i <= 4; i++) { + where[`lat${i}`] = { [Op.between]: [bounds[0].latitude, bounds[1].latitude] } + where[`lon${i}`] = bounds[0].longitude <= bounds[1].longitude + ? { [Op.between]: [bounds[0].longitude, bounds[1].longitude] } + : { + [Op.or]: [ + { [Op.gte]: bounds[0].longitude }, + { [Op.lte]: bounds[1].longitude } + ] + } + } + + const cells = await api.models.etrs89_cell.findAll({ + where, + // doesn't matter the order, but makes sense to have always the same + // as overlapping cells (e.g. vertices, edges) otherwise will be + // assigned randomly + order: ['code'] + }) + + const cell = cells.reduce((found, cell) => { + if (found) return found + + const coords = cell.coordinates() + + // check for point inside + if (isPointInPolygon(point, coords)) { + return cell + } + // check for point on the vertices + for (let i = 0; i < coords.length; i++) { + if (isPointInLine(point, coords[i], coords[(i + 1) % coords.length])) { + return cell + } + } + + return found + }, null) + + if (!cell) { + return '' + } + + return cell.code +} + +module.exports = class FillEtrs89Codes extends FormsTask { + constructor () { + super() + this.name = 'fill-etrs89-codes' + this.description = 'Populate ETRS89-LAEA code based on coordinates' + // use cronjob to schedule the task + // npm run enqueue fill-etrs89-codes + this.frequency = 0 + this.defaultLimit = api.config.app.etrs89.maxRecords + } + + getForms () { + return super.getForms().filter((form) => form.hasEtrs89GridCode) + } + + filterRecords ({ force }) { + return { + ...(force ? {} : { etrs89GridCode: null }), + observationDateTime: { [Op.gte]: api.config.app.etrs89.startTimestamp } + } + } + + async processRecord (record, form) { + record.etrs89GridCode = await getEtrs89GridCode(record) + + if (!await api.forms.trySave(record, api.forms[form]) && record.etrs89GridCode !== '') { + // mark as empty string so we don't repeat it + record.etrs89GridCode = '' + await api.forms.trySave(record, api.forms[form]) + } + } +}