├── test ├── lib │ ├── upload_tests │ │ └── upload-test.txt │ └── file-utils.js ├── package.json └── test.js ├── .eslintrc.json ├── .stylelintrc.json ├── modules └── @apostrophecms │ ├── form-widget │ ├── views │ │ ├── widget.html │ │ └── widgetBase.html │ ├── ui │ │ └── src │ │ │ ├── index.scss │ │ │ ├── recaptcha.js │ │ │ ├── errors.js │ │ │ ├── fields.js │ │ │ └── index.js │ └── index.js │ ├── form-base-field-widget │ ├── views │ │ ├── widget.html │ │ └── fragments │ │ │ └── utility.html │ └── index.js │ ├── form-divider-widget │ ├── index.js │ └── views │ │ └── widget.html │ ├── form-group-widget │ ├── views │ │ └── widget.html │ └── index.js │ ├── form-conditional-widget │ ├── views │ │ └── widget.html │ ├── ui │ │ └── src │ │ │ └── index.js │ └── index.js │ ├── form-text-field-widget │ ├── ui │ │ └── src │ │ │ └── index.js │ ├── views │ │ └── widget.html │ └── index.js │ ├── form-textarea-field-widget │ ├── ui │ │ └── src │ │ │ └── index.js │ ├── index.js │ └── views │ │ └── widget.html │ ├── form-boolean-field-widget │ ├── index.js │ ├── views │ │ └── widget.html │ └── ui │ │ └── src │ │ └── index.js │ ├── form-radio-field-widget │ ├── index.js │ ├── views │ │ └── widget.html │ └── ui │ │ └── src │ │ └── index.js │ ├── form-checkboxes-field-widget │ ├── views │ │ └── widget.html │ ├── index.js │ └── ui │ │ └── src │ │ └── index.js │ ├── form-file-field-widget │ ├── views │ │ └── widget.html │ ├── ui │ │ └── src │ │ │ └── index.js │ └── index.js │ ├── form-select-field-widget │ ├── views │ │ └── widget.html │ ├── ui │ │ └── src │ │ │ └── index.js │ └── index.js │ └── form-global-settings │ └── index.js ├── ui └── src │ ├── index.js │ └── index.scss ├── views ├── emailConfirmation.html └── emailSubmission.html ├── .gitignore ├── LICENSE.md ├── package.json ├── lib ├── recaptcha.js ├── fields.js └── processor.js ├── CHANGELOG.md ├── i18n ├── en.json ├── sk.json ├── pt-BR.json ├── it.json ├── fr.json ├── de.json └── es.json ├── index.js └── README.md /test/lib/upload_tests/upload-test.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ "apostrophe" ] 3 | } 4 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-apostrophe" 3 | } 4 | -------------------------------------------------------------------------------- /modules/@apostrophecms/form-widget/views/widget.html: -------------------------------------------------------------------------------- 1 | {% extends "widgetBase.html" %} -------------------------------------------------------------------------------- /ui/src/index.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | apos.aposForm = apos.aposForm || {}; 3 | apos.aposForm.collectors = {}; 4 | }; 5 | -------------------------------------------------------------------------------- /views/emailConfirmation.html: -------------------------------------------------------------------------------- 1 |
Thank you for your submission to the form, {{ data.form.title }}.
4 | -------------------------------------------------------------------------------- /views/emailSubmission.html: -------------------------------------------------------------------------------- 1 |20 | {{ __t("(YYYY-MM-DD)") }} 21 |
22 | {% endif %} 23 | 33 |48 | {{ __t('aposForm:widgetSubmitError') }} 49 | 50 |
51 | 52 | {% if recaptchaSite %} 53 |56 | {{ __t('aposForm:widgetCaptchaError') }} 57 |
58 | {% endif %} 59 |63 | {{ __t('aposForm:widgetSubmitting') }} 64 |
65 |Proud Mary, The Best, \"River Deep, Mountain High\").",
35 | "enableQueryParams": "Enable query parameter capture",
36 | "enableQueryParamsHtmlHelp": "If enabled, all query parameters (the key/value pairs in a query string) will be collected when the form is submitted. You may also set list of specific parameter keys that you wish to collect.",
37 | "errorEmailConfirm": "The form field {{ field }} is configured to be used for the confirmation email address, but it allows non-email formats.",
38 | "fieldLabel": "Field label",
39 | "fieldName": "Field name",
40 | "fieldNameHelp": "No spaces or punctuation other than dashes. If left blank, the form will populate this with a simplified form of the label. Changing this field after a form is in use may cause problems with any integrations.",
41 | "fieldRequired": "Is this field required?",
42 | "file": "File attachment",
43 | "fileAllowMultiple": "Allow multiple file attachments",
44 | "fileLimitSize": "Limit file size?",
45 | "fileMaxSize": "Max file attachment size",
46 | "fileMaxSizeError": "File is too large '%1$s' (max size: %2$s).",
47 | "fileMaxSizeHelp": "In Bytes",
48 | "fileMissingEarly": "Uploaded temporary file {{ path }} was already removed, this should have been the responsibility of the upload route",
49 | "fileSizeUnitB": "B",
50 | "fileSizeUnitGB": "GB",
51 | "fileSizeUnitKB": "KB",
52 | "fileSizeUnitMB": "MB",
53 | "fileUploadError": "An error occurred uploading the file. It may be too large or of an inappropriate type.",
54 | "fileUploading": "Uploading...",
55 | "form": "Form",
56 | "formContents": "Form contents",
57 | "formErrors": "Errors found in the form submission",
58 | "formName": "Form name",
59 | "forms": "Forms",
60 | "globalGroup": "Form Settings",
61 | "group": "Group",
62 | "groupAdvanced": "Advanced",
63 | "groupAfterSubmission": "After submission",
64 | "groupContents": "Group Contents",
65 | "groupContentsHelp": "Contains all form widgets except groups",
66 | "groupForm": "Form",
67 | "groupLabel": "Group Label",
68 | "notFoundForm": "No matching form was found",
69 | "queryParamKey": "Key",
70 | "queryParamLimit": "Limit Saved Parameter Value Length (characters)",
71 | "queryParamLimitHelp": "Enter a whole number to limit the length of the value saved.",
72 | "queryParamList": "Query parameter keys",
73 | "queryParamListHelp": "Create an array item for each query parameter value you wish to capture.",
74 | "radio": "Radio input",
75 | "radioChoice": "Radio input options",
76 | "recaptchaConfigError": "The reCAPTCHA verification system may be down or incorrectly configured. Please try again or notify the site owner.",
77 | "recaptchaEnable": "Enable reCAPTCHA on the form (spam prevention)",
78 | "recaptchaEnableHelp": "To use, reCAPTCHA a site ID and secret key must be configured in website code or the website global settings.",
79 | "recaptchaSecret": "reCAPTCHA secret key",
80 | "recaptchaSecretHelp": "Enter the secret key from a reCAPTCHA account",
81 | "recaptchaSite": "reCAPTCHA site key",
82 | "recaptchaSiteHelp": "Enter the site key from a reCAPTCHA account",
83 | "recaptchaSubmitError": "There was a problem submitting your reCAPTCHA verification.",
84 | "recaptchaValidationError": "There was a problem validating your reCAPTCHA verification submission.",
85 | "requiredError": "This field is required",
86 | "select": "Select input",
87 | "selectAllowMultiple": "Allow multiple options to be selected",
88 | "selectBlank": " ",
89 | "selectChoice": "Select input options",
90 | "selectSize": "Number of options in the list that should be visible",
91 | "submitLabel": "Submit button label",
92 | "templateOptional": "(Optional)",
93 | "text": "Text input",
94 | "textArea": "Text area input",
95 | "textPlaceholder": "Placeholder",
96 | "textPlaceholderHelp": "Text to display in the field before someone uses it (e.g., to provide additional directions).",
97 | "textType": "Input type",
98 | "textTypeDate": "Date",
99 | "textTypeEmail": "Email",
100 | "textTypeHelp": "If you are requesting certain formatted information (e.g., email, url, phone number), select the relevant input type here. If not, use \"Text\".",
101 | "textTypePassword": "Password",
102 | "textTypePhone": "Telephone",
103 | "textTypeText": "Text",
104 | "textTypeUrl": "URL",
105 | "thankYouBody": "Thank you message content",
106 | "thankYouTitle": "Thank you message title",
107 | "useRecaptcha": "Use Google reCAPTCHA on forms",
108 | "useRecaptchaHtmlHelp": "reCAPTCHA helps avoid spam submissions on forms. You will need a secret key and site key.",
109 | "widgetCaptchaError": "There was an error connecting to the reCAPTCHA validation service. Please reload the page.",
110 | "widgetForm": "Form",
111 | "widgetFormSelect": "Form to display",
112 | "widgetNoScript": "NOTE: The form above requires JavaScript enabled in the browser for submission.",
113 | "widgetSubmit": "Submit",
114 | "widgetSubmitError": "An error occurred submitting the form. Please try again.",
115 | "widgetSubmitting": "Submitting..."
116 | }
117 |
--------------------------------------------------------------------------------
/i18n/sk.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseWidget": "Základný widget formulára",
3 | "boolean": "Boolean/opt-in vstup",
4 | "booleanChecked": "Predvolene začiarknuté",
5 | "booleanCheckedHelp": "Ak \"áno,\" začiarkavacie políčko sa východne zobrazí začiarknuté.",
6 | "checkbox": "Vstup začiarkavacieho políčka",
7 | "checkboxChoices": "Možnosti vstupu začiarkavacieho políčka",
8 | "checkboxChoicesLabel": "Názov možnosti",
9 | "checkboxChoicesLabelHelp": "Čitateľský názov zobrazený používateľom.",
10 | "checkboxChoicesValue": "Hodnota možnosti",
11 | "checkboxChoicesValueHelp": "Hodnota, ktorá sa uloží (ako text) do databázy. Ak nie je zadaná, použije sa názov.",
12 | "conditional": "Podmienená skupina polí",
13 | "conditionalContentsHelp": "Ak je podmienka splnená, tieto polia budú aktivované.",
14 | "conditionalName": "Názov poľa formulára, ktoré sa má skontrolovať",
15 | "conditionalNameHelp": "Zadajte hodnotu \"Názov poľa\" pre výber, rádio alebo boolean pole.",
16 | "conditionalValue": "Hodnota, ktorá aktivuje túto skupinu",
17 | "conditionalValueHtmlHelp": "Ak používate boolean/opt-in pole, nastavte toto na \"true\".",
18 | "confEmailEnable": "Odoslať potvrdzovací e-mail",
19 | "confEmailEnableHelp": "Aktivujte to, aby ste odoslali správu osobe, ktorá odosiela tento formulár.",
20 | "confEmailField": "Ktoré je vaše pole pre potvrdzovací e-mail?",
21 | "confEmailFieldHelp": "Zadajte hodnotu \"názov\" poľa, kde ľudia zadajú svoju e-mailovú adresu. Pre najlepšie výsledky sa uistite, že toto pole prijíma iba e-mailové adresy.",
22 | "defaultThankYou": "Ďakujeme",
23 | "disabledInEditMode": "Prepnite na režim náhľadu alebo publikovaného, aby ste otestovali formulár.",
24 | "divider": "Čiarový oddelovač",
25 | "emailField": "Hlavná interná e-mailová adresa",
26 | "emailFieldHelp": "Môžete zadať jednu z predchádzajúceho zoznamu. Toto je adresa, ktorá sa použije ako \"od\" adresa na akýchkoľvek generovaných e-mailových správach.",
27 | "emails": "E-mailová adresa(e) pre výsledky",
28 | "emailsAddress": "E-mailová adresa pre výsledky",
29 | "emailsConditions": "Nastavte podmienky pre toto upozornenie",
30 | "emailsConditionsField": "Zadajte pole, ktoré sa má použiť ako vaša podmienka.",
31 | "emailsConditionsFieldHelp": "Použiť sa môžu iba vyberateľné (rozbaľovacie) a políčka začiarkavacieho políčka pre túto podmienku.",
32 | "emailsConditionsHelp": "Napríklad, ak chcete upozorňovať túto e-mailovú adresu, iba ak je pole \"krajina\" nastavené na \"Rakúsko\". Všetky podmienky musia byť splnené. Ak je to potrebné, pridajte e-mail znovu s iným súborom podmienok.",
33 | "emailsConditionsValue": "Zadajte hodnotu, ktorú koncový používateľ zadá na splnenie tejto podmienky.",
34 | "emailsConditionsValueHtmlHelp": "Použite hodnoty oddelené čiarkou, aby ste skontrolovali viacero hodnôt v tomto poli (vzťah OR). Hodnoty, ktoré skutočne obsahujú čiarky, by mali byť zadané v úvodzovkách (napr., Proud Mary, The Best, \"River Deep, Mountain High\").",
35 | "enableQueryParams": "Povoliť zachytávanie parametrov dotazu",
36 | "enableQueryParamsHtmlHelp": "Ak je povolené, všetky parametre dotazu (kľúčové/hodnotové páry v dotazovom reťazci) budú zozbierané pri odoslaní formulára. Môžete tiež nastaviť zoznam konkrétnych kľúčov parametrov, ktoré chcete zbierať.",
37 | "errorEmailConfirm": "Pole formulára {{ field }} je nakonfigurované na použitie pre potvrdzovaciu e-mailovú adresu, ale umožňuje ne-e-mailové formáty.",
38 | "fieldLabel": "Názov poľa",
39 | "fieldName": "Názov poľa",
40 | "fieldNameHelp": "Žiadne medzery ani interpunkcia okrem pomlčiek. Ak je prázdne, formulár ho automaticky naplní zjednodušenou formou názvu. Zmena tohto poľa po použití formulára môže spôsobiť problémy s akýmikoľvek integráciami.",
41 | "fieldRequired": "Je toto pole povinné?",
42 | "file": "Príloha súboru",
43 | "fileAllowMultiple": "Povoliť viacero príloh súboru",
44 | "fileLimitSize": "Obmedziť veľkosť súboru?",
45 | "fileMaxSize": "Maximálna veľkosť prílohy súboru",
46 | "fileMaxSizeError": "Súbor je príliš veľký '%1$s' (max. veľkosť: %2$s).",
47 | "fileMaxSizeHelp": "V Bitoch",
48 | "fileMissingEarly": "Nahratý dočasný súbor {{ path }} bol už odstránený, toto by malo byť zodpovednosťou cesty nahrávania.",
49 | "fileSizeUnitB": "B",
50 | "fileSizeUnitGB": "GB",
51 | "fileSizeUnitKB": "KB",
52 | "fileSizeUnitMB": "MB",
53 | "fileUploadError": "Pri nahrávaní súboru došlo k chybe. Môže byť príliš veľký alebo nevhodného typu.",
54 | "fileUploading": "Nahrávanie...",
55 | "form": "Formulár",
56 | "formContents": "Obsah formulára",
57 | "formErrors": "Nájdené chyby vo formulári",
58 | "formName": "Názov formulára",
59 | "forms": "Formuláre",
60 | "globalGroup": "Nastavenia formulára",
61 | "group": "Skupina",
62 | "groupAdvanced": "Rozšírené",
63 | "groupAfterSubmission": "Po odoslaní",
64 | "groupContents": "Obsah skupiny",
65 | "groupContentsHelp": "Obsahuje všetky widgety formulára okrem skupín",
66 | "groupForm": "Formulár",
67 | "groupLabel": "Názov skupiny",
68 | "notFoundForm": "Neboli nájdené zodpovedajúce formuláre",
69 | "queryParamKey": "Kľúč",
70 | "queryParamLimit": "Obmedziť dĺžku uložených hodnôt parametrov (znaky)",
71 | "queryParamLimitHelp": "Zadajte celé číslo na obmedzenie dĺžky uložených hodnôt.",
72 | "queryParamList": "Kľúče parametrov dotazu",
73 | "queryParamListHelp": "Vytvorte položku poľa pre každú hodnotu parametra dotazu, ktorú chcete zachytiť.",
74 | "radio": "Rádiový vstup",
75 | "radioChoice": "Možnosti rádiového vstupu",
76 | "recaptchaConfigError": "Systém overovania reCAPTCHA môže byť vypnutý alebo nesprávne nakonfigurovaný. Skúste to znova alebo informujte vlastníka stránky.",
77 | "recaptchaEnable": "Povoliť reCAPTCHA vo formulári (prevencia spamu)",
78 | "recaptchaEnableHelp": "Na použitie musí byť ID a tajný kľúč reCAPTCHA nakonfigurované v kóde webovej stránky alebo v globálnych nastaveniach webových stránok.",
79 | "recaptchaSecret": "Tajný kľúč reCAPTCHA",
80 | "recaptchaSecretHelp": "Zadajte tajný kľúč z účtu reCAPTCHA",
81 | "recaptchaSite": "Kľúč na stránku reCAPTCHA",
82 | "recaptchaSiteHelp": "Zadajte kľúč stránky z účtu reCAPTCHA",
83 | "recaptchaSubmitError": "Pri odosielaní sa vyskytol problém s overením reCAPTCHA.",
84 | "recaptchaValidationError": "Pri overení odoslania vášho overenia reCAPTCHA sa vyskytol problém.",
85 | "requiredError": "Toto pole je povinné",
86 | "select": "Vstup výberu",
87 | "selectAllowMultiple": "Povoliť výber viacerých možností",
88 | "selectBlank": " ",
89 | "selectChoice": "Možnosti vstupu výberu",
90 | "selectSize": "Počet možností v zozname, ktoré by mali byť viditeľné",
91 | "submitLabel": "Názov tlačidla odoslania",
92 | "templateOptional": "(Voliteľné)",
93 | "text": "Textový vstup",
94 | "textArea": "Vstup textovej oblasti",
95 | "textPlaceholder": "Zástupný text",
96 | "textPlaceholderHelp": "Text, ktorý sa má zobraziť v poli predtým, než ho niekto použije (napr. na poskytnutie dodatočných pokynov).",
97 | "textType": "Typ vstupu",
98 | "textTypeDate": "Dátum",
99 | "textTypeEmail": "E-mail",
100 | "textTypeHelp": "Ak požadujete určité formátované informácie (napr. e-mail, url, telefónne číslo), vyberte si relevantný typ vstupu tu. Ak nie, použite \"Text\".",
101 | "textTypePassword": "Heslo",
102 | "textTypePhone": "Telefón",
103 | "textTypeText": "Text",
104 | "textTypeUrl": "URL",
105 | "thankYouBody": "Obsah správy poďakovania",
106 | "thankYouTitle": "Názov správy poďakovania",
107 | "useRecaptcha": "Použiť Google reCAPTCHA na formulároch",
108 | "useRecaptchaHtmlHelp": "reCAPTCHA pomáha zabrániť spamovým odoslaním na formulároch. Budete potrebovať tajný kľúč a kľúč stránky.",
109 | "widgetCaptchaError": "Pri pripojení k službe overovania reCAPTCHA sa vyskytla chyba. Skúste znovu načítať stránku.",
110 | "widgetForm": "Formulár",
111 | "widgetFormSelect": "Formulár na zobrazenie",
112 | "widgetNoScript": "POZNÁMKA: Formulár vyššie vyžaduje JavaScript aktivovaný v prehliadači na odoslanie.",
113 | "widgetSubmit": "Odoslať",
114 | "widgetSubmitError": "Pri odosielaní formulára sa vyskytla chyba. Skúste to znova.",
115 | "widgetSubmitting": "Odosielanie..."
116 | }
117 |
--------------------------------------------------------------------------------
/i18n/pt-BR.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseWidget": "Widget de formulário base",
3 | "boolean": "Entrada booleana/opção",
4 | "booleanChecked": "Definir como pré-selecionado",
5 | "booleanCheckedHelp": "Se \"sim\", a caixa de seleção começará no estado selecionado.",
6 | "checkbox": "Entrada de caixa de seleção",
7 | "checkboxChoices": "Opções de entrada de caixa de seleção",
8 | "checkboxChoicesLabel": "Rótulo da opção",
9 | "checkboxChoicesLabelHelp": "O rótulo legível exibido para os usuários.",
10 | "checkboxChoicesValue": "Valor da opção",
11 | "checkboxChoicesValueHelp": "O valor salvo (como texto) no banco de dados. Se não inserido, o rótulo será usado.",
12 | "conditional": "Grupo de campo condicional",
13 | "conditionalContentsHelp": "Se a condição acima for atendida, esses campos serão ativados.",
14 | "conditionalName": "Campo do formulário a verificar",
15 | "conditionalNameHelp": "Insira o valor \"Nome do Campo\" para um campo de seleção, rádio ou booleano.",
16 | "conditionalValue": "Valor que ativa este grupo",
17 | "conditionalValueHtmlHelp": "Se usar um campo booleano/opção, defina isso como \"true\".",
18 | "confEmailEnable": "Enviar um e-mail de confirmação",
19 | "confEmailEnableHelp": "Ative isso para enviar uma mensagem para a pessoa que envia este formulário.",
20 | "confEmailField": "Qual é o seu campo de e-mail de confirmação?",
21 | "confEmailFieldHelp": "Insira o valor \"nome\" do campo onde as pessoas deverão inserir seu endereço de e-mail. Para melhores resultados, certifique-se de que este campo aceite apenas endereços de e-mail.",
22 | "defaultThankYou": "Obrigado",
23 | "disabledInEditMode": "Mude para o modo de Pré-visualização ou Publicado para testar o formulário.",
24 | "divider": "Divisor",
25 | "emailField": "Endereço de e-mail interno principal",
26 | "emailFieldHelp": "Você pode inserir um da lista anterior. Este é o endereço que será usado como o endereço \"de\" em qualquer mensagem de e-mail gerada.",
27 | "emails": "Endereço(s) de e-mail para Resultados",
28 | "emailsAddress": "Endereço de e-mail para Resultados",
29 | "emailsConditions": "Defina Condições para esta Notificação",
30 | "emailsConditionsField": "Insira um campo para usar como sua condição.",
31 | "emailsConditionsFieldHelp": "Somente campos de seleção (drop-down) e caixas de verificação podem ser usados para essa condição.",
32 | "emailsConditionsHelp": "Por exemplo, se você só notificar este endereço de e-mail se o campo \"país\" estiver definido como \"Áustria\". Todas as condições devem ser atendidas. Adicione o e-mail novamente com outro conjunto condicional, se necessário.",
33 | "emailsConditionsValue": "Insira o valor que um usuário final irá inserir para atender a esta condição.",
34 | "emailsConditionsValueHtmlHelp": "Use valores separados por vírgula para verificar múltiplos valores neste campo (uma relação OR). Valores que realmente contêm vírgulas devem ser inseridos entre aspas duplas (por exemplo, Proud Mary, The Best, \"River Deep, Mountain High\").",
35 | "enableQueryParams": "Ativar captura de parâmetros de consulta",
36 | "enableQueryParamsHtmlHelp": "Se ativado, todos os parâmetros de consulta (os pares chave/valor em uma string de consulta) serão coletados quando o formulário for enviado. Você também pode definir uma lista de chaves de parâmetros específicas que deseja coletar.",
37 | "errorEmailConfirm": "O campo do formulário {{ field }} está configurado para ser usado para o endereço de e-mail de confirmação, mas permite formatos não e-mail.",
38 | "fieldLabel": "Rótulo do campo",
39 | "fieldName": "Nome do campo",
40 | "fieldNameHelp": "Sem espaços ou pontuação além de traços. Se deixado em branco, o formulário preencherá isso com uma forma simplificada do rótulo. Alterar este campo após um formulário estar em uso pode causar problemas com integrações.",
41 | "fieldRequired": "Este campo é obrigatório?",
42 | "file": "Anexo de arquivo",
43 | "fileAllowMultiple": "Permitir vários anexos de arquivo",
44 | "fileLimitSize": "Limitar o tamanho do arquivo?",
45 | "fileMaxSize": "Tamanho máximo do anexo de arquivo",
46 | "fileMaxSizeError": "O arquivo é muito grande '%1$s' (tamanho máximo: %2$s).",
47 | "fileMaxSizeHelp": "Em Bytes",
48 | "fileMissingEarly": "O arquivo temporário enviado {{ path }} já foi removido, isso deveria ter sido responsabilidade da rota de upload",
49 | "fileSizeUnitB": "B",
50 | "fileSizeUnitGB": "GB",
51 | "fileSizeUnitKB": "KB",
52 | "fileSizeUnitMB": "MB",
53 | "fileUploadError": "Ocorreu um erro ao carregar o arquivo. Pode ser muito grande ou de um tipo inadequado.",
54 | "fileUploading": "Carregando...",
55 | "form": "Formulário",
56 | "formContents": "Conteúdo do formulário",
57 | "formErrors": "Erros encontrados na submissão do formulário",
58 | "formName": "Nome do formulário",
59 | "forms": "Formulários",
60 | "globalGroup": "Configurações do Formulário",
61 | "group": "Grupo",
62 | "groupAdvanced": "Avançado",
63 | "groupAfterSubmission": "Após a submissão",
64 | "groupContents": "Conteúdos do Grupo",
65 | "groupContentsHelp": "Contém todos os widgets de formulário, exceto grupos",
66 | "groupForm": "Formulário",
67 | "groupLabel": "Rótulo do Grupo",
68 | "notFoundForm": "Nenhum formulário correspondente foi encontrado",
69 | "queryParamKey": "Chave",
70 | "queryParamLimit": "Limitar o comprimento do valor do parâmetro salvo (caracteres)",
71 | "queryParamLimitHelp": "Insira um número inteiro para limitar o comprimento do valor salvo.",
72 | "queryParamList": "Chaves de parâmetros de consulta",
73 | "queryParamListHelp": "Crie um item de array para cada valor de parâmetro de consulta que você deseja capturar.",
74 | "radio": "Entrada de rádio",
75 | "radioChoice": "Opções de entrada de rádio",
76 | "recaptchaConfigError": "O sistema de verificação reCAPTCHA pode estar fora do ar ou configurado incorretamente. Tente novamente ou notifique o proprietário do site.",
77 | "recaptchaEnable": "Ativar reCAPTCHA no formulário (prevenção de spam)",
78 | "recaptchaEnableHelp": "Para usar, um ID do site reCAPTCHA e uma chave secreta devem ser configurados no código do site ou nas configurações globais do site.",
79 | "recaptchaSecret": "Chave secreta do reCAPTCHA",
80 | "recaptchaSecretHelp": "Insira a chave secreta de uma conta reCAPTCHA",
81 | "recaptchaSite": "Chave do site do reCAPTCHA",
82 | "recaptchaSiteHelp": "Insira a chave do site de uma conta reCAPTCHA",
83 | "recaptchaSubmitError": "Houve um problema ao enviar sua verificação reCAPTCHA.",
84 | "recaptchaValidationError": "Houve um problema ao validar sua submissão de verificação reCAPTCHA.",
85 | "requiredError": "Este campo é obrigatório",
86 | "select": "Entrada de seleção",
87 | "selectAllowMultiple": "Permitir que múltiplas opções sejam selecionadas",
88 | "selectBlank": " ",
89 | "selectChoice": "Opções de entrada de seleção",
90 | "selectSize": "Número de opções na lista que devem ser visíveis",
91 | "submitLabel": "Rótulo do botão de envio",
92 | "templateOptional": "(Opcional)",
93 | "text": "Entrada de texto",
94 | "textArea": "Entrada de área de texto",
95 | "textPlaceholder": "Marcador",
96 | "textPlaceholderHelp": "Texto a ser exibido no campo antes que alguém o use (por exemplo, para fornecer direções adicionais).",
97 | "textType": "Tipo de entrada",
98 | "textTypeDate": "Data",
99 | "textTypeEmail": "E-mail",
100 | "textTypeHelp": "Se você estiver solicitando certas informações formatadas (por exemplo, e-mail, URL, número de telefone), selecione o tipo de entrada relevante aqui. Se não, use \"Texto\".",
101 | "textTypePassword": "Senha",
102 | "textTypePhone": "Telefone",
103 | "textTypeText": "Texto",
104 | "textTypeUrl": "URL",
105 | "thankYouBody": "Conteúdo da mensagem de agradecimento",
106 | "thankYouTitle": "Título da mensagem de agradecimento",
107 | "useRecaptcha": "Usar Google reCAPTCHA em formulários",
108 | "useRecaptchaHtmlHelp": "reCAPTCHA ajuda a evitar submissões de spam em formulários. Você precisará de uma chave secreta e chave do site.",
109 | "widgetCaptchaError": "Houve um erro ao conectar ao serviço de validação reCAPTCHA. Por favor, recarregue a página.",
110 | "widgetForm": "Formulário",
111 | "widgetFormSelect": "Formulário a exibir",
112 | "widgetNoScript": "NOTA: O formulário acima requer JavaScript ativado no navegador para envio.",
113 | "widgetSubmit": "Enviar",
114 | "widgetSubmitError": "Ocorreu um erro ao enviar o formulário. Por favor, tente novamente.",
115 | "widgetSubmitting": "Enviando..."
116 | }
117 |
--------------------------------------------------------------------------------
/i18n/it.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseWidget": "Widget di base per il modulo",
3 | "boolean": "Input booleano/opt-in",
4 | "booleanChecked": "Predefinito su selezionato",
5 | "booleanCheckedHelp": "Se \"sì,\" la casella di controllo sarà attivata di default.",
6 | "checkbox": "Input casella di controllo",
7 | "checkboxChoices": "Opzioni input casella di controllo",
8 | "checkboxChoicesLabel": "Etichetta dell'opzione",
9 | "checkboxChoicesLabelHelp": "L'etichetta leggibile visualizzata agli utenti.",
10 | "checkboxChoicesValue": "Valore dell'opzione",
11 | "checkboxChoicesValueHelp": "Il valore salvato (come testo) nel database. Se non inserito, verrà utilizzata l'etichetta.",
12 | "conditional": "Gruppo di campi condizionali",
13 | "conditionalContentsHelp": "Se la condizione sopra indicata è soddisfatta, questi campi saranno attivati.",
14 | "conditionalName": "Campo del modulo da controllare",
15 | "conditionalNameHelp": "Inserisci il valore \"Nome del Campo\" per un campo di modulo select, radio o booleano.",
16 | "conditionalValue": "Valore che attiva questo gruppo",
17 | "conditionalValueHtmlHelp": "Se si utilizza un campo booleano/opt-in, impostare su \"true\".",
18 | "confEmailEnable": "Invia un'email di conferma",
19 | "confEmailEnableHelp": "Abilita questa opzione per inviare un messaggio alla persona che invia questo modulo.",
20 | "confEmailField": "Qual è il campo email di conferma?",
21 | "confEmailFieldHelp": "Inserisci il valore \"nome\" del campo dove le persone entreranno il loro indirizzo email. Per i migliori risultati, assicurati che questo campo accetti solo indirizzi email.",
22 | "defaultThankYou": "Grazie",
23 | "disabledInEditMode": "Passa alla modalità Anteprima o Pubblicata per testare il modulo.",
24 | "divider": "Divisione",
25 | "emailField": "Indirizzo email interno principale",
26 | "emailFieldHelp": "Puoi inserire uno dall'elenco precedente. Questo sarà l'indirizzo utilizzato come indirizzo \"da\" per eventuali messaggi email generati.",
27 | "emails": "Indirizzo/e email per i risultati",
28 | "emailsAddress": "Indirizzo email per i risultati",
29 | "emailsConditions": "Imposta condizioni per questa notifica",
30 | "emailsConditionsField": "Inserisci un campo da utilizzare come tua condizione.",
31 | "emailsConditionsFieldHelp": "Solo i campi selezione (a discesa) e casella di controllo possono essere utilizzati per questa condizione.",
32 | "emailsConditionsHelp": "Ad esempio, se notifichi solo questo indirizzo email se il campo \"paese\" è impostato su \"Austria\". Tutte le condizioni devono essere soddisfatte. Aggiungi nuovamente l'email con un altro set condizionale se necessario.",
33 | "emailsConditionsValue": "Inserisci il valore che un utente finale entrerà per soddisfare questa condizione.",
34 | "emailsConditionsValueHtmlHelp": "Utilizza valori separati da virgola per controllare più valori su questo campo (una relazione OR). I valori che contengono effettivamente virgole devono essere inseriti tra virgolette doppie (ad es., Proud Mary, The Best, \"River Deep, Mountain High\").",
35 | "enableQueryParams": "Abilita la cattura dei parametri di query",
36 | "enableQueryParamsHtmlHelp": "Se abilitato, tutti i parametri di query (le coppie chiave/valore in una stringa di query) saranno raccolti quando il modulo viene inviato. Puoi anche impostare un elenco di chiavi di parametro specifiche che desideri raccogliere.",
37 | "errorEmailConfirm": "Il campo del modulo {{ field }} è configurato per essere utilizzato per l'indirizzo email di conferma, ma consente formati non email.",
38 | "fieldLabel": "Etichetta del campo",
39 | "fieldName": "Nome del campo",
40 | "fieldNameHelp": "Nessuno spazio o punteggiatura diversa dai trattini. Se lasciato vuoto, il modulo popolerà questo con una forma semplificata dell'etichetta. Modificare questo campo dopo che un modulo è in uso potrebbe causare problemi con eventuali integrazioni.",
41 | "fieldRequired": "Questo campo è richiesto?",
42 | "file": "Allegato file",
43 | "fileAllowMultiple": "Consenti più allegati file",
44 | "fileLimitSize": "Limitare la dimensione del file?",
45 | "fileMaxSize": "Dimensione massima dell'allegato file",
46 | "fileMaxSizeError": "Il file è troppo grande '%1$s' (dimensione massima: %2$s).",
47 | "fileMaxSizeHelp": "In Byte",
48 | "fileMissingEarly": "Il file caricato temporaneamente {{ path }} è già stato rimosso; questa dovrebbe essere stata la responsabilità della route di upload.",
49 | "fileSizeUnitB": "B",
50 | "fileSizeUnitGB": "GB",
51 | "fileSizeUnitKB": "KB",
52 | "fileSizeUnitMB": "MB",
53 | "fileUploadError": "Si è verificato un errore durante il caricamento del file. Potrebbe essere troppo grande o di un tipo inappropriato.",
54 | "fileUploading": "Caricamento in corso...",
55 | "form": "Modulo",
56 | "formContents": "Contenuti del modulo",
57 | "formErrors": "Errori trovati nell'invio del modulo",
58 | "formName": "Nome del modulo",
59 | "forms": "Moduli",
60 | "globalGroup": "Impostazioni del modulo",
61 | "group": "Gruppo",
62 | "groupAdvanced": "Avanzato",
63 | "groupAfterSubmission": "Dopo l'invio",
64 | "groupContents": "Contenuti del gruppo",
65 | "groupContentsHelp": "Contiene tutti i widget del modulo tranne i gruppi",
66 | "groupForm": "Modulo",
67 | "groupLabel": "Etichetta del gruppo",
68 | "notFoundForm": "Nessun modulo corrispondente trovato",
69 | "queryParamKey": "Chiave",
70 | "queryParamLimit": "Limita la lunghezza del valore del parametro salvato (caratteri)",
71 | "queryParamLimitHelp": "Inserisci un numero intero per limitare la lunghezza del valore salvato.",
72 | "queryParamList": "Chiavi dei parametri di query",
73 | "queryParamListHelp": "Crea un elemento dell'array per ciascun valore di parametro di query che desideri catturare.",
74 | "radio": "Input radio",
75 | "radioChoice": "Opzioni input radio",
76 | "recaptchaConfigError": "Il sistema di verifica reCAPTCHA potrebbe essere inattivo o configurato in modo errato. Riprova o notificherai il proprietario del sito.",
77 | "recaptchaEnable": "Abilita reCAPTCHA sul modulo (prevenzione spam)",
78 | "recaptchaEnableHelp": "Per utilizzare reCAPTCHA, un ID sito e una chiave segreta devono essere configurati nel codice del sito web o nelle impostazioni globali del sito.",
79 | "recaptchaSecret": "Chiave segreta reCAPTCHA",
80 | "recaptchaSecretHelp": "Inserisci la chiave segreta da un account reCAPTCHA",
81 | "recaptchaSite": "Chiave del sito reCAPTCHA",
82 | "recaptchaSiteHelp": "Inserisci la chiave del sito da un account reCAPTCHA",
83 | "recaptchaSubmitError": "Si è verificato un problema durante l'invio della verifica reCAPTCHA.",
84 | "recaptchaValidationError": "Si è verificato un problema nella validazione dell'invio della verifica reCAPTCHA.",
85 | "requiredError": "Questo campo è richiesto",
86 | "select": "Input selezione",
87 | "selectAllowMultiple": "Consenti la selezione di più opzioni",
88 | "selectBlank": " ",
89 | "selectChoice": "Opzioni input selezione",
90 | "selectSize": "Numero di opzioni nella lista che devono essere visibili",
91 | "submitLabel": "Etichetta del pulsante di invio",
92 | "templateOptional": "(Facoltativo)",
93 | "text": "Input testo",
94 | "textArea": "Input area di testo",
95 | "textPlaceholder": "Segnaposto",
96 | "textPlaceholderHelp": "Testo da visualizzare nel campo prima che qualcuno lo utilizzi (ad esempio, per fornire indicazioni aggiuntive).",
97 | "textType": "Tipo di input",
98 | "textTypeDate": "Data",
99 | "textTypeEmail": "Email",
100 | "textTypeHelp": "Se stai richiedendo informazioni formattate specifiche (ad esempio, email, url, numero di telefono), seleziona il tipo di input pertinente qui. In caso contrario, usa \"Testo\".",
101 | "textTypePassword": "Password",
102 | "textTypePhone": "Telefono",
103 | "textTypeText": "Testo",
104 | "textTypeUrl": "URL",
105 | "thankYouBody": "Contenuto del messaggio di ringraziamento",
106 | "thankYouTitle": "Titolo del messaggio di ringraziamento",
107 | "useRecaptcha": "Utilizza Google reCAPTCHA sui moduli",
108 | "useRecaptchaHtmlHelp": "reCAPTCHA aiuta a evitare invii di spam sui moduli. Avrai bisogno di una chiave segreta e chiave del sito.",
109 | "widgetCaptchaError": "Si è verificato un errore durante la connessione al servizio di verifica reCAPTCHA. Ricarica la pagina.",
110 | "widgetForm": "Modulo",
111 | "widgetFormSelect": "Modulo da visualizzare",
112 | "widgetNoScript": "NOTA: Il modulo sopra richiede JavaScript abilitato nel browser per l'invio.",
113 | "widgetSubmit": "Invia",
114 | "widgetSubmitError": "Si è verificato un errore durante l'invio del modulo. Riprova.",
115 | "widgetSubmitting": "Invio in corso..."
116 | }
117 |
--------------------------------------------------------------------------------
/i18n/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseWidget": "Widget de formulaire de base",
3 | "boolean": "Champ booléen/opt-in",
4 | "booleanChecked": "Par défaut, pré-coché",
5 | "booleanCheckedHelp": "Si \"oui,\" la case à cocher commencera dans l'état coché.",
6 | "checkbox": "Champ de case à cocher",
7 | "checkboxChoices": "Options de champ de case à cocher",
8 | "checkboxChoicesLabel": "Étiquette de l'option",
9 | "checkboxChoicesLabelHelp": "L'étiquette lisible affichée aux utilisateurs.",
10 | "checkboxChoicesValue": "Valeur de l'option",
11 | "checkboxChoicesValueHelp": "La valeur enregistrée (au format texte) dans la base de données. Si non saisie, l'étiquette sera utilisée.",
12 | "conditional": "Groupe de champs conditionnels",
13 | "conditionalContentsHelp": "Si la condition ci-dessus est remplie, ces champs seront activés.",
14 | "conditionalName": "Champ de formulaire à vérifier",
15 | "conditionalNameHelp": "Entrez la valeur \"Nom du champ\" pour un champ de formulaire sélectionner, radio ou booléen.",
16 | "conditionalValue": "Valeur qui active ce groupe",
17 | "conditionalValueHtmlHelp": "Si vous utilisez un champ booléen/opt-in, définissez cela à \"true\".",
18 | "confEmailEnable": "Envoyer un e-mail de confirmation",
19 | "confEmailEnableHelp": "Activez cela pour envoyer un message à la personne qui soumet ce formulaire.",
20 | "confEmailField": "Quel est votre champ d'e-mail de confirmation ?",
21 | "confEmailFieldHelp": "Entrez la valeur \"nom\" du champ où les gens entreront leur adresse e-mail. Pour de meilleurs résultats, assurez-vous que ce champ n'accepte que des adresses e-mails.",
22 | "defaultThankYou": "Merci",
23 | "disabledInEditMode": "Passez en mode Aperçu ou Publié pour tester le formulaire.",
24 | "divider": "Diviseur",
25 | "emailField": "Adresse e-mail interne principale",
26 | "emailFieldHelp": "Vous pouvez entrer une adresse de la liste précédente. C'est l'adresse qui sera utilisée comme adresse \"de\" dans tous les messages e-mail générés.",
27 | "emails": "Adresse(s) e-mail pour les résultats",
28 | "emailsAddress": "Adresse e-mail pour les résultats",
29 | "emailsConditions": "Définir des conditions pour cette notification",
30 | "emailsConditionsField": "Entrez un champ à utiliser comme condition.",
31 | "emailsConditionsFieldHelp": "Seuls les champs (déroulants) et les cases à cocher peuvent être utilisés pour cette condition.",
32 | "emailsConditionsHelp": "Par exemple, si vous ne notifiez cette adresse e-mail que si le champ \"pays\" est défini sur \"Autriche\". Toutes les conditions doivent être remplies. Ajoutez à nouveau l'email avec un autre ensemble conditionnel si nécessaire.",
33 | "emailsConditionsValue": "Entrez la valeur qu'un utilisateur final saisira pour satisfaire cette condition.",
34 | "emailsConditionsValueHtmlHelp": "Utilisez des valeurs séparées par des virgules pour vérifier plusieurs valeurs sur ce champ (une relation OU). Les valeurs qui contiennent réellement des virgules doivent être saisies entre guillemets doubles (par exemple, Proud Mary, The Best, \"River Deep, Mountain High\").",
35 | "enableQueryParams": "Activer la capture des paramètres de requête",
36 | "enableQueryParamsHtmlHelp": "Si activé, tous les paramètres de requête (les paires clé/valeur dans une chaîne de requête) seront collectés lors de la soumission du formulaire. Vous pouvez également définir une liste de clés de paramètres spécifiques que vous souhaitez collecter.",
37 | "errorEmailConfirm": "Le champ de formulaire {{ field }} est configuré pour être utilisé pour l'adresse e-mail de confirmation, mais il permet des formats non valides.",
38 | "fieldLabel": "Étiquette du champ",
39 | "fieldName": "Nom du champ",
40 | "fieldNameHelp": "Pas d'espaces ou de ponctuation autre que des tirets. S'il est laissé vide, le formulaire le remplira avec une version simplifiée de l'étiquette. Changer ce champ après qu'un formulaire est en cours d'utilisation peut causer des problèmes avec des intégrations.",
41 | "fieldRequired": "Ce champ est-il requis ?",
42 | "file": "Pièce jointe",
43 | "fileAllowMultiple": "Autoriser plusieurs pièces jointes",
44 | "fileLimitSize": "Limiter la taille des fichiers ?",
45 | "fileMaxSize": "Taille maximale de la pièce jointe",
46 | "fileMaxSizeError": "Le fichier est trop grand '%1$s' (taille max : %2$s).",
47 | "fileMaxSizeHelp": "En octets",
48 | "fileMissingEarly": "Le fichier temporaire téléchargé {{ path }} a déjà été supprimé, cela aurait dû être la responsabilité de la route de téléchargement",
49 | "fileSizeUnitB": "o",
50 | "fileSizeUnitGB": "Go",
51 | "fileSizeUnitKB": "Ko",
52 | "fileSizeUnitMB": "Mo",
53 | "fileUploadError": "Une erreur s'est produite lors du téléchargement du fichier. Il peut être trop grand ou de type inapproprié.",
54 | "fileUploading": "Téléchargement...",
55 | "form": "Formulaire",
56 | "formContents": "Contenu du formulaire",
57 | "formErrors": "Erreurs trouvées dans la soumission du formulaire",
58 | "formName": "Nom du formulaire",
59 | "forms": "Formulaires",
60 | "globalGroup": "Paramètres du formulaire",
61 | "group": "Groupe",
62 | "groupAdvanced": "Avancé",
63 | "groupAfterSubmission": "Après soumission",
64 | "groupContents": "Contenu du groupe",
65 | "groupContentsHelp": "Contient tous les widgets de formulaire sauf les groupes",
66 | "groupForm": "Formulaire",
67 | "groupLabel": "Étiquette du groupe",
68 | "notFoundForm": "Aucun formulaire correspondant n'a été trouvé",
69 | "queryParamKey": "Clé",
70 | "queryParamLimit": "Limiter la longueur de la valeur du paramètre enregistré (caractères)",
71 | "queryParamLimitHelp": "Entrez un nombre entier pour limiter la longueur de la valeur enregistrée.",
72 | "queryParamList": "Clés de paramètres de requête",
73 | "queryParamListHelp": "Créez un élément de tableau pour chaque valeur de paramètre de requête que vous souhaitez capturer.",
74 | "radio": "Champ radio",
75 | "radioChoice": "Options de champ radio",
76 | "recaptchaConfigError": "Le système de vérification reCAPTCHA peut être hors service ou mal configuré. Veuillez réessayer ou notifier le propriétaire du site.",
77 | "recaptchaEnable": "Activer reCAPTCHA sur le formulaire (prévention du spam)",
78 | "recaptchaEnableHelp": "Pour l'utiliser, un ID de site reCAPTCHA et une clé secrète doivent être configurés dans le code du site web ou les paramètres globaux du site.",
79 | "recaptchaSecret": "Clé secrète reCAPTCHA",
80 | "recaptchaSecretHelp": "Entrez la clé secrète d'un compte reCAPTCHA",
81 | "recaptchaSite": "Clé du site reCAPTCHA",
82 | "recaptchaSiteHelp": "Entrez la clé du site d'un compte reCAPTCHA",
83 | "recaptchaSubmitError": "Un problème est survenu lors de la soumission de votre vérification reCAPTCHA.",
84 | "recaptchaValidationError": "Un problème est survenu lors de la validation de votre soumission de vérification reCAPTCHA.",
85 | "requiredError": "Ce champ est requis",
86 | "select": "Champ de sélection",
87 | "selectAllowMultiple": "Autoriser plusieurs options à être sélectionnées",
88 | "selectBlank": " ",
89 | "selectChoice": "Options de champ de sélection",
90 | "selectSize": "Nombre d'options dans la liste devant être visibles",
91 | "submitLabel": "Étiquette du bouton de soumission",
92 | "templateOptional": "(Facultatif)",
93 | "text": "Champ de texte",
94 | "textArea": "Champ de zone de texte",
95 | "textPlaceholder": "Espace réservé",
96 | "textPlaceholderHelp": "Texte à afficher dans le champ avant que quelqu'un ne l'utilise (par exemple, pour fournir des instructions supplémentaires).",
97 | "textType": "Type d'entrée",
98 | "textTypeDate": "Date",
99 | "textTypeEmail": "E-mail",
100 | "textTypeHelp": "Si vous demandez certaines informations formatées (par exemple, e-mail, url, numéro de téléphone), sélectionnez le type d'entrée pertinent ici. Sinon, utilisez \"Texte\".",
101 | "textTypePassword": "Mot de passe",
102 | "textTypePhone": "Téléphone",
103 | "textTypeText": "Texte",
104 | "textTypeUrl": "URL",
105 | "thankYouBody": "Contenu du message de remerciement",
106 | "thankYouTitle": "Titre du message de remerciement",
107 | "useRecaptcha": "Utilisez Google reCAPTCHA sur les formulaires",
108 | "useRecaptchaHtmlHelp": "reCAPTCHA aide à éviter les soumissions de spam sur les formulaires. Vous aurez besoin d'une clé secrète et clé de site.",
109 | "widgetCaptchaError": "Une erreur est survenue lors de la connexion au service de validation reCAPTCHA. Veuillez recharger la page.",
110 | "widgetForm": "Formulaire",
111 | "widgetFormSelect": "Formulaire à afficher",
112 | "widgetNoScript": "REMARQUE : Le formulaire ci-dessus nécessite JavaScript activé dans le navigateur pour la soumission.",
113 | "widgetSubmit": "Soumettre",
114 | "widgetSubmitError": "Une erreur s'est produite lors de la soumission du formulaire. Veuillez réessayer.",
115 | "widgetSubmitting": "Soumission..."
116 | }
117 |
--------------------------------------------------------------------------------
/i18n/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseWidget": "Formulargrundwidget",
3 | "boolean": "Boolean/opt-in Eingabe",
4 | "booleanChecked": "Standardmäßig auf vorab ausgewählt",
5 | "booleanCheckedHelp": "Wenn \"ja,\" wird das Kontrollkästchen im ausgewählten Zustand starten.",
6 | "checkbox": "Kontrollkästchen Eingabe",
7 | "checkboxChoices": "Optionen für Kontrollkästchen Eingabe",
8 | "checkboxChoicesLabel": "Optionslabel",
9 | "checkboxChoicesLabelHelp": "Das lesbare Label, das den Benutzern angezeigt wird.",
10 | "checkboxChoicesValue": "Optionswert",
11 | "checkboxChoicesValueHelp": "Der Wert, der (als Text) in der Datenbank gespeichert wird. Wenn nicht eingegeben, wird das Label verwendet.",
12 | "conditional": "Bedingte Feldgruppe",
13 | "conditionalContentsHelp": "Wenn die obige Bedingung erfüllt ist, werden diese Felder aktiviert.",
14 | "conditionalName": "Formularfeld zur Überprüfung",
15 | "conditionalNameHelp": "Geben Sie den Wert \"Feldname\" für ein Auswahl-, Radio- oder Boolean Formularfeld ein.",
16 | "conditionalValue": "Wert, der diese Gruppe aktiviert",
17 | "conditionalValueHtmlHelp": "Wenn Sie ein Boolean/Opt-in-Feld verwenden, setzen Sie dies auf \"true.\"",
18 | "confEmailEnable": "Bestätigungs-E-Mail senden",
19 | "confEmailEnableHelp": "Aktivieren Sie dies, um eine Nachricht an die Person zu senden, die dieses Formular absendet.",
20 | "confEmailField": "Welches ist Ihr Bestätigungs-E-Mail-Feld?",
21 | "confEmailFieldHelp": "Geben Sie den \"Namen\" des Feldes ein, in dem die Personen ihre E-Mail-Adresse eingeben können. Für die besten Ergebnisse stellen Sie sicher, dass dieses Feld nur E-Mail-Adressen akzeptiert.",
22 | "defaultThankYou": "Danke",
23 | "disabledInEditMode": "Wechseln Sie in den Vorschau- oder veröffentlichten Modus, um das Formular zu testen.",
24 | "divider": "Divider",
25 | "emailField": "Primäre interne E-Mail-Adresse",
26 | "emailFieldHelp": "Sie können eine aus der vorherigen Liste eingeben. Dies ist die Adresse, die als \"Von\"-Adresse für alle generierten E-Mail-Nachrichten verwendet wird.",
27 | "emails": "E-Mail-Adresse(n) für Ergebnisse",
28 | "emailsAddress": "E-Mail-Adresse für Ergebnisse",
29 | "emailsConditions": "Bedingungen für diese Benachrichtigung festlegen",
30 | "emailsConditionsField": "Geben Sie ein Feld an, das Sie als Ihre Bedingung verwenden möchten.",
31 | "emailsConditionsFieldHelp": "Nur Auswahl (Dropdown) und Kontrollkästchenfelder können für diese Bedingung verwendet werden.",
32 | "emailsConditionsHelp": "Zum Beispiel, wenn Sie diese E-Mail-Adresse nur benachrichtigen, wenn das Feld \"Land\" auf \"Österreich\" gesetzt ist. Alle Bedingungen müssen erfüllt sein. Fügen Sie die E-Mail-Adresse erneut mit einer anderen Bedingung hinzu, falls erforderlich.",
33 | "emailsConditionsValue": "Geben Sie den Wert ein, den ein Endbenutzer eingeben wird, um diese Bedingung zu erfüllen.",
34 | "emailsConditionsValueHtmlHelp": "Verwenden Sie durch Kommas getrennte Werte, um mehrere Werte für dieses Feld zu überprüfen (eine ODER-Beziehung). Werte, die tatsächlich Kommata enthalten, sollten in Anführungszeichen eingegeben werden (z.B. Proud Mary, The Best, \"River Deep, Mountain High\").",
35 | "enableQueryParams": "Aktivieren Sie die Erfassung von Abfrageparametern",
36 | "enableQueryParamsHtmlHelp": "Wenn aktiviert, werden alle Abfrageparameter (die Schlüssel/Wert-Paare in einer Abfragezeichenfolge) erfasst, wenn das Formular abgesendet wird. Sie können auch eine Liste spezifischer Parameter-Schlüssel festlegen, die Sie erfassen möchten.",
37 | "errorEmailConfirm": "Das Formularfeld {{ field }} ist für die Bestätigungs-E-Mail-Adresse konfiguriert, erlaubt jedoch keine Nicht-E-Mail-Formate.",
38 | "fieldLabel": "Feldlabel",
39 | "fieldName": "Feldname",
40 | "fieldNameHelp": "Keine Leerzeichen oder Interpunktion außer Bindestrichen. Wenn dieses Feld leer gelassen wird, wird das Formular damit ein vereinfachtes Formular des Labels ausfüllen. Das Ändern dieses Feldes nach der Verwendung eines Formulars kann Probleme mit Integrationen verursachen.",
41 | "fieldRequired": "Ist dieses Feld erforderlich?",
42 | "file": "Dateianhang",
43 | "fileAllowMultiple": "Mehrere Dateianhänge zulassen",
44 | "fileLimitSize": "Dateigröße begrenzen?",
45 | "fileMaxSize": "Maximale Dateigröße für den Anhang",
46 | "fileMaxSizeError": "Die Datei ist zu groß '%1$s' (max. Größe: %2$s).",
47 | "fileMaxSizeHelp": "In Bytes",
48 | "fileMissingEarly": "Die hochgeladene temporäre Datei {{ path }} wurde bereits entfernt, dies hätte in der Verantwortung der Upload-Routen liegen sollen.",
49 | "fileSizeUnitB": "B",
50 | "fileSizeUnitGB": "GB",
51 | "fileSizeUnitKB": "KB",
52 | "fileSizeUnitMB": "MB",
53 | "fileUploadError": "Beim Hochladen der Datei ist ein Fehler aufgetreten. Sie könnte zu groß oder von einem unangemessenen Typ sein.",
54 | "fileUploading": "Hochladen...",
55 | "form": "Formular",
56 | "formContents": "Formularinhalt",
57 | "formErrors": "Fehler im Formulareingang gefunden",
58 | "formName": "Formularname",
59 | "forms": "Formulare",
60 | "globalGroup": "Formulareinstellungen",
61 | "group": "Gruppe",
62 | "groupAdvanced": "Erweitert",
63 | "groupAfterSubmission": "Nach der Einreichung",
64 | "groupContents": "Gruppeninhalt",
65 | "groupContentsHelp": "Enthält alle Formular-Widgets außer Gruppen",
66 | "groupForm": "Formular",
67 | "groupLabel": "Gruppenlabel",
68 | "notFoundForm": "Es wurde kein passendes Formular gefunden",
69 | "queryParamKey": "Schlüssel",
70 | "queryParamLimit": "Länge des gespeicherten Parameterwertes begrenzen (Zeichen)",
71 | "queryParamLimitHelp": "Geben Sie eine ganze Zahl ein, um die Länge des gespeicherten Wertes zu begrenzen.",
72 | "queryParamList": "Abfrageparameter-Schlüssel",
73 | "queryParamListHelp": "Erstellen Sie für jeden Abfrageparameterwert, den Sie erfassen möchten, ein Array-Element.",
74 | "radio": "Radio-Eingabe",
75 | "radioChoice": "Optionen für Radio-Eingabe",
76 | "recaptchaConfigError": "Das reCAPTCHA-Verifizierungssystem kann ausgefallen oder falsch konfiguriert sein. Bitte versuchen Sie es erneut oder benachrichtigen Sie den Seiteninhaber.",
77 | "recaptchaEnable": "reCAPTCHA im Formular aktivieren (Spam-Prävention)",
78 | "recaptchaEnableHelp": "Um es zu verwenden, müssen eine Website-ID und ein geheimer Schlüssel im Webseiten-Code oder in den globalen Einstellungen der Website konfiguriert werden.",
79 | "recaptchaSecret": "reCAPTCHA geheimer Schlüssel",
80 | "recaptchaSecretHelp": "Geben Sie den geheimen Schlüssel von einem reCAPTCHA-Konto ein",
81 | "recaptchaSite": "reCAPTCHA Site-Schlüssel",
82 | "recaptchaSiteHelp": "Geben Sie den Site-Schlüssel von einem reCAPTCHA-Konto ein",
83 | "recaptchaSubmitError": "Beim Einreichen Ihrer reCAPTCHA-Verifizierung trat ein Problem auf.",
84 | "recaptchaValidationError": "Beim Validieren Ihrer reCAPTCHA-Verifizierungsübermittlung trat ein Problem auf.",
85 | "requiredError": "Dieses Feld ist erforderlich",
86 | "select": "Auswahl-Eingabe",
87 | "selectAllowMultiple": "Erlaube die Auswahl mehrerer Optionen",
88 | "selectBlank": " ",
89 | "selectChoice": "Optionen für Auswahl-Eingabe",
90 | "selectSize": "Anzahl der Optionen in der Liste, die sichtbar sein sollten",
91 | "submitLabel": "Beschriftung der Schaltfläche absenden",
92 | "templateOptional": "(Optional)",
93 | "text": "Text Eingabe",
94 | "textArea": "Textbereich Eingabe",
95 | "textPlaceholder": "Platzhalter",
96 | "textPlaceholderHelp": "Text, der im Feld angezeigt wird, bevor jemand es verwendet (z.B. um zusätzliche Anweisungen bereitzustellen).",
97 | "textType": "Eingabetyp",
98 | "textTypeDate": "Datum",
99 | "textTypeEmail": "E-Mail",
100 | "textTypeHelp": "Wenn Sie bestimmte formatierte Informationen anfordern (z.B. E-Mail, URL, Telefonnummer), wählen Sie hier den entsprechenden Eingabetyp aus. Andernfalls verwenden Sie \"Text.\"",
101 | "textTypePassword": "Passwort",
102 | "textTypePhone": "Telefon",
103 | "textTypeText": "Text",
104 | "textTypeUrl": "URL",
105 | "thankYouBody": "Inhalt der Dankesnachricht",
106 | "thankYouTitle": "Titel der Dankesnachricht",
107 | "useRecaptcha": "Verwende Google reCAPTCHA in Formularen",
108 | "useRecaptchaHtmlHelp": "reCAPTCHA hilft, Spam-Einreichungen in Formularen zu vermeiden. Sie benötigen einen geheimen Schlüssel und Site-Schlüssel.",
109 | "widgetCaptchaError": "Beim Verbindungsaufbau zu dem reCAPTCHA-Validierungsdienst ist ein Fehler aufgetreten. Bitte laden Sie die Seite neu.",
110 | "widgetForm": "Formular",
111 | "widgetFormSelect": "Anzuzeigendes Formular",
112 | "widgetNoScript": "HINWEIS: Das obige Formular erfordert aktiviertes JavaScript im Browser für die Einreichung.",
113 | "widgetSubmit": "Einreichen",
114 | "widgetSubmitError": "Beim Einreichen des Formulars ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.",
115 | "widgetSubmitting": "Einreichen..."
116 | }
117 |
--------------------------------------------------------------------------------
/i18n/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseWidget": "Widget base del formulario",
3 | "boolean": "Entrada booleana/opción de inclusión",
4 | "booleanChecked": "Predefinido como seleccionado",
5 | "booleanCheckedHelp": "Si \"sí\", la casilla comenzará en estado seleccionado.",
6 | "checkbox": "Entrada de casilla de verificación",
7 | "checkboxChoices": "Opciones de entrada de casilla de verificación",
8 | "checkboxChoicesLabel": "Etiqueta de opción",
9 | "checkboxChoicesLabelHelp": "La etiqueta legible mostrada a los usuarios.",
10 | "checkboxChoicesValue": "Valor de opción",
11 | "checkboxChoicesValueHelp": "El valor guardado (como texto) en la base de datos. Si no se ingresa, se utilizará la etiqueta.",
12 | "conditional": "Grupo de campos condicional",
13 | "conditionalContentsHelp": "Si se cumple la condición anterior, estos campos se activarán.",
14 | "conditionalName": "Campo de formulario a verificar",
15 | "conditionalNameHelp": "Ingresa el valor \"Nombre del campo\" para un campo de formulario select, radio o booleano.",
16 | "conditionalValue": "Valor que activa este grupo",
17 | "conditionalValueHtmlHelp": "Si se utiliza un campo booleano/opción de inclusión, configúralo como \"true\".",
18 | "confEmailEnable": "Enviar un correo electrónico de confirmación",
19 | "confEmailEnableHelp": "Habilita esto para enviar un mensaje a la persona que envía este formulario.",
20 | "confEmailField": "¿Cuál es tu campo de correo electrónico de confirmación?",
21 | "confEmailFieldHelp": "Ingresa el valor \"nombre\" del campo donde las personas ingresarán su dirección de correo electrónico. Para obtener mejores resultados, asegúrate de que este campo solo acepte direcciones de correo electrónico.",
22 | "defaultThankYou": "Gracias",
23 | "disabledInEditMode": "Cambia a Vista previa o Modo publicado para probar el formulario.",
24 | "divider": "Divisor",
25 | "emailField": "Dirección de correo electrónico interna principal",
26 | "emailFieldHelp": "Puedes ingresar una de la lista anterior. Esta es la dirección que se utilizará como la dirección \"de\" en los mensajes de correo electrónico generados.",
27 | "emails": "Dirección(es) de correo electrónico para resultados",
28 | "emailsAddress": "Dirección de correo electrónico para resultados",
29 | "emailsConditions": "Establecer condiciones para esta notificación",
30 | "emailsConditionsField": "Ingresa un campo para usar como tu condición.",
31 | "emailsConditionsFieldHelp": "Solo se pueden usar campos (desplegables) y de casilla de verificación para esta condición.",
32 | "emailsConditionsHelp": "Por ejemplo, si solo notifiques esta dirección de correo electrónico si el campo \"país\" está configurado en \"Austria\". Todas las condiciones deben cumplirse. Agrega el correo electrónico nuevamente con otro conjunto condicional si es necesario.",
33 | "emailsConditionsValue": "Ingresa el valor que un usuario final ingresará para cumplir con esta condición.",
34 | "emailsConditionsValueHtmlHelp": "Utiliza valores separados por comas para verificar múltiples valores en este campo (una relación OR). Los valores que realmente contienen comas deben ingresarse entre comillas dobles (por ejemplo, Proud Mary, The Best, \"River Deep, Mountain High\").",
35 | "enableQueryParams": "Habilitar captura de parámetros de consulta",
36 | "enableQueryParamsHtmlHelp": "Si se habilita, todos los parámetros de consulta (las parejas clave/valor en una cadena de consulta) se recopilarán cuando se envíe el formulario. También puedes establecer una lista de claves de parámetros específicas que deseas recopilar.",
37 | "errorEmailConfirm": "El campo del formulario {{ field }} está configurado para ser utilizado como la dirección de correo electrónico de confirmación, pero permite formatos que no son de correo electrónico.",
38 | "fieldLabel": "Etiqueta del campo",
39 | "fieldName": "Nombre del campo",
40 | "fieldNameHelp": "Sin espacios ni puntuación excepto guiones. Si se deja en blanco, el formulario lo rellenará con una forma simplificada de la etiqueta. Cambiar este campo después de que un formulario esté en uso puede causar problemas con las integraciones.",
41 | "fieldRequired": "¿Es este campo requerido?",
42 | "file": "Adjunto de archivo",
43 | "fileAllowMultiple": "Permitir adjuntos de múltiples archivos",
44 | "fileLimitSize": "¿Limitar el tamaño del archivo?",
45 | "fileMaxSize": "Tamaño máximo de adjunto de archivo",
46 | "fileMaxSizeError": "El archivo es demasiado grande '%1$s' (tamaño máximo: %2$s).",
47 | "fileMaxSizeHelp": "En Bytes",
48 | "fileMissingEarly": "El archivo temporal subido {{ path }} ya fue eliminado, esto debería haber sido responsabilidad de la ruta de carga",
49 | "fileSizeUnitB": "B",
50 | "fileSizeUnitGB": "GB",
51 | "fileSizeUnitKB": "KB",
52 | "fileSizeUnitMB": "MB",
53 | "fileUploadError": "Se produjo un error al cargar el archivo. Puede ser demasiado grande o de un tipo inapropiado.",
54 | "fileUploading": "Subiendo...",
55 | "form": "Formulario",
56 | "formContents": "Contenidos del formulario",
57 | "formErrors": "Errores encontrados en la presentación del formulario",
58 | "formName": "Nombre del formulario",
59 | "forms": "Formularios",
60 | "globalGroup": "Configuraciones del formulario",
61 | "group": "Grupo",
62 | "groupAdvanced": "Avanzado",
63 | "groupAfterSubmission": "Después de la presentación",
64 | "groupContents": "Contenidos del grupo",
65 | "groupContentsHelp": "Contiene todos los widgets del formulario excepto grupos",
66 | "groupForm": "Formulario",
67 | "groupLabel": "Etiqueta del grupo",
68 | "notFoundForm": "No se encontró un formulario coincidente",
69 | "queryParamKey": "Clave",
70 | "queryParamLimit": "Limitar la longitud del valor del parámetro guardado (caracteres)",
71 | "queryParamLimitHelp": "Ingresa un número entero para limitar la longitud del valor guardado.",
72 | "queryParamList": "Claves de parámetros de consulta",
73 | "queryParamListHelp": "Crea un elemento de array para cada valor de parámetro de consulta que desees capturar.",
74 | "radio": "Entrada de radio",
75 | "radioChoice": "Opciones de entrada de radio",
76 | "recaptchaConfigError": "El sistema de verificación reCAPTCHA puede estar inactivo o configurado incorrectamente. Intenta nuevamente o notifica al propietario del sitio.",
77 | "recaptchaEnable": "Habilitar reCAPTCHA en el formulario (prevención de spam)",
78 | "recaptchaEnableHelp": "Para usar, se debe configurar un ID de sitio reCAPTCHA y una clave secreta en el código del sitio web o en la configuración global del sitio web.",
79 | "recaptchaSecret": "Clave secreta de reCAPTCHA",
80 | "recaptchaSecretHelp": "Ingresa la clave secreta de una cuenta de reCAPTCHA",
81 | "recaptchaSite": "Clave del sitio de reCAPTCHA",
82 | "recaptchaSiteHelp": "Ingresa la clave del sitio de una cuenta de reCAPTCHA",
83 | "recaptchaSubmitError": "Hubo un problema al enviar tu verificación de reCAPTCHA.",
84 | "recaptchaValidationError": "Hubo un problema al validar tu presentación de verificación de reCAPTCHA.",
85 | "requiredError": "Este campo es requerido",
86 | "select": "Entrada de selección",
87 | "selectAllowMultiple": "Permitir seleccionar múltiples opciones",
88 | "selectBlank": " ",
89 | "selectChoice": "Opciones de entrada de selección",
90 | "selectSize": "Número de opciones en la lista que deberían ser visibles",
91 | "submitLabel": "Etiqueta del botón de envío",
92 | "templateOptional": "(Opcional)",
93 | "text": "Entrada de texto",
94 | "textArea": "Entrada de área de texto",
95 | "textPlaceholder": "Marcador de posición",
96 | "textPlaceholderHelp": "Texto a mostrar en el campo antes de que alguien lo use (por ejemplo, para proporcionar direcciones adicionales).",
97 | "textType": "Tipo de entrada",
98 | "textTypeDate": "Fecha",
99 | "textTypeEmail": "Correo electrónico",
100 | "textTypeHelp": "Si estás solicitando información con un formato específico (por ejemplo, correo electrónico, url, número de teléfono), selecciona el tipo de entrada relevante aquí. Si no, usa \"Texto\".",
101 | "textTypePassword": "Contraseña",
102 | "textTypePhone": "Teléfono",
103 | "textTypeText": "Texto",
104 | "textTypeUrl": "URL",
105 | "thankYouBody": "Contenido del mensaje de agradecimiento",
106 | "thankYouTitle": "Título del mensaje de agradecimiento",
107 | "useRecaptcha": "Usar Google reCAPTCHA en formularios",
108 | "useRecaptchaHtmlHelp": "reCAPTCHA ayuda a evitar envíos de spam en formularios. Necesitarás una clave secreta y clave del sitio.",
109 | "widgetCaptchaError": "Hubo un error al conectar con el servicio de verificación de reCAPTCHA. Por favor recarga la página.",
110 | "widgetForm": "Formulario",
111 | "widgetFormSelect": "Formulario para mostrar",
112 | "widgetNoScript": "NOTA: El formulario anterior requiere JavaScript habilitado en el navegador para su envío.",
113 | "widgetSubmit": "Enviar",
114 | "widgetSubmitError": "Se produjo un error al enviar el formulario. Por favor inténtalo de nuevo.",
115 | "widgetSubmitting": "Enviando..."
116 | }
117 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const os = require('os');
4 | const multer = require('multer');
5 | const fields = require('./lib/fields');
6 | const recaptcha = require('./lib/recaptcha');
7 | const processor = require('./lib/processor');
8 |
9 | module.exports = {
10 | extend: '@apostrophecms/piece-type',
11 | options: {
12 | label: 'aposForm:form',
13 | pluralLabel: 'aposForm:forms',
14 | quickCreate: true,
15 | seoFields: false,
16 | openGraph: false,
17 | i18n: {
18 | ns: 'aposForm',
19 | browser: true
20 | },
21 | shortcut: 'G,O'
22 | },
23 | bundle: {
24 | directory: 'modules',
25 | modules: getBundleModuleNames()
26 | },
27 | fields(self) {
28 | let add = fields.initial(self.options);
29 |
30 | if (self.options.emailSubmissions !== false) {
31 | add = {
32 | ...add,
33 | ...fields.emailFields
34 | };
35 | }
36 |
37 | const group = {
38 | basics: {
39 | label: 'aposForm:groupForm',
40 | fields: [ 'contents' ]
41 | },
42 | afterSubmit: {
43 | label: 'aposForm:groupAfterSubmission',
44 | fields: [
45 | 'thankYouHeading',
46 | 'thankYouBody',
47 | 'sendConfirmationEmail',
48 | 'emailConfirmationField'
49 | ]
50 | .concat(
51 | self.options.emailSubmissions !== false
52 | ? [
53 | 'emails',
54 | 'email'
55 | ]
56 | : []
57 | )
58 | },
59 | advanced: {
60 | label: 'aposForm:groupAdvanced',
61 | fields: [
62 | 'submitLabel',
63 | 'enableRecaptcha',
64 | 'enableQueryParams',
65 | 'queryParamList'
66 | ]
67 | }
68 | };
69 |
70 | return {
71 | add,
72 | group
73 | };
74 | },
75 | init(self) {
76 | self.ensureCollection();
77 |
78 | self.cleanOptions(self.options);
79 | },
80 | methods(self) {
81 | return {
82 | ...recaptcha(self),
83 | ...processor(self),
84 | async ensureCollection() {
85 | self.db = self.apos.db.collection('aposFormSubmissions');
86 | await self.db.ensureIndex({
87 | formId: 1,
88 | createdAt: 1
89 | });
90 | await self.db.ensureIndex({
91 | formId: 1,
92 | createdAt: -1
93 | });
94 | },
95 | processQueryParams(form, input, output, fieldNames) {
96 | if (!input.queryParams ||
97 | (typeof input.queryParams !== 'object')) {
98 | output.queryParams = null;
99 | return;
100 | }
101 |
102 | if (Array.isArray(form.queryParamList) && form.queryParamList.length > 0) {
103 | form.queryParamList.forEach(param => {
104 | // Skip if this is an existing field submitted by the form. This value
105 | // capture will be done by populating the form inputs client-side.
106 | if (fieldNames.includes(param.key)) {
107 | return;
108 | }
109 | const value = input.queryParams[param.key];
110 |
111 | if (value) {
112 | output[param.key] = self.tidyParamValue(param, value);
113 | } else {
114 | output[param.key] = null;
115 | }
116 | });
117 | }
118 | },
119 | tidyParamValue(param, value) {
120 | value = self.apos.launder.string(value);
121 |
122 | if (param.lengthLimit && param.lengthLimit > 0) {
123 | value = value.substring(0, (param.lengthLimit));
124 | }
125 |
126 | return value;
127 | },
128 | async sendEmailSubmissions(req, form, data) {
129 | if (self.options.emailSubmissions === false ||
130 | !form.emails || form.emails.length === 0) {
131 | return;
132 | }
133 |
134 | let emails = [];
135 |
136 | form.emails.forEach(mailRule => {
137 | if (!mailRule.conditions || mailRule.conditions.length === 0) {
138 | emails.push(mailRule.email);
139 | return;
140 | }
141 |
142 | let passed = true;
143 |
144 | mailRule.conditions.forEach(condition => {
145 | if (!condition.value) {
146 | return;
147 | }
148 |
149 | let answer = data[condition.field];
150 |
151 | if (!answer) {
152 | passed = false;
153 | } else {
154 | // Regex for comma-separation from https://stackoverflow.com/questions/11456850/split-a-string-by-commas-but-ignore-commas-within-double-quotes-using-javascript/11457952#comment56094979_11457952
155 | const regex = /(".*?"|[^",]+)(?=\s*,|\s*$)/g;
156 | let acceptable = condition.value.match(regex);
157 |
158 | acceptable = acceptable.map(value => {
159 | // Remove leading/trailing white space and bounding double-quotes.
160 | value = value.trim();
161 |
162 | if (value[0] === '"' && value[value.length - 1] === '"') {
163 | value = value.slice(1, -1);
164 | }
165 |
166 | return value.trim();
167 | });
168 |
169 | // If the value is stored as a string, convert to an array for checking.
170 | if (!Array.isArray(answer)) {
171 | answer = [ answer ];
172 | }
173 |
174 | if (!(answer.some(val => acceptable.includes(val)))) {
175 | passed = false;
176 | }
177 | }
178 | });
179 |
180 | if (passed === true) {
181 | emails.push(mailRule.email);
182 | }
183 | });
184 | // Get array of email addresses without duplicates.
185 | emails = [ ...new Set(emails) ];
186 |
187 | if (self.options.testing) {
188 | return emails;
189 | }
190 |
191 | if (emails.length === 0) {
192 | return null;
193 | }
194 |
195 | for (const key in data) {
196 | // Add some space to array lists.
197 | if (Array.isArray(data[key])) {
198 | data[key] = data[key].join(', ');
199 | }
200 | }
201 |
202 | try {
203 | const emailOptions = {
204 | form,
205 | data,
206 | to: emails.join(',')
207 | };
208 |
209 | await self.sendEmail(req, 'emailSubmission', emailOptions);
210 |
211 | return null;
212 | } catch (err) {
213 | self.apos.util.error('⚠️ @apostrophecms/form submission email notification error: ', err);
214 |
215 | return null;
216 | }
217 | },
218 | // Should be handled async. Options are: form, data, from, to and subject
219 | async sendEmail(req, emailTemplate, options) {
220 | const form = options.form;
221 | const data = options.data;
222 | return self.email(
223 | req,
224 | emailTemplate,
225 | {
226 | form,
227 | input: data
228 | },
229 | {
230 | from: options.from || form.email,
231 | to: options.to,
232 | subject: options.subject || form.title
233 | }
234 | );
235 | },
236 | // Normalize Multer's `req.files` (array) to the historical multiparty shape
237 | // expected by submit handlers (object keyed by field name with name/path/etc.).
238 | normalizeFiles(req, _res, next) {
239 | const files = Array.isArray(req.files) ? req.files : [];
240 | const mapped = {};
241 | const counters = {};
242 |
243 | for (const f of files) {
244 | const base = f.fieldname.replace(/-\d+$/, '');
245 | counters[base] = (counters[base] || 0) + 1;
246 | const key = `${base}-${counters[base]}`;
247 |
248 | mapped[key] = {
249 | path: f.path,
250 | name: f.originalname,
251 | type: f.mimetype,
252 | size: f.size
253 | };
254 | }
255 |
256 | req.files = mapped;
257 | next();
258 | }
259 | };
260 | },
261 | helpers(self) {
262 | return {
263 | prependIfPrefix(str) {
264 | if (self.options.classPrefix) {
265 | return `${self.options.classPrefix}${str}`;
266 | }
267 |
268 | return '';
269 | }
270 | };
271 | },
272 | apiRoutes(self) {
273 | return {
274 | post: {
275 | // Route to accept the submitted form.
276 | submit: [
277 | multer({ dest: os.tmpdir() }).any(),
278 | self.normalizeFiles,
279 | async function (req) {
280 | try {
281 | await self.submitForm(req);
282 | } finally {
283 | // Cleanup temp files (same behavior as before)
284 | for (const file of (Object.values(req.files || {}))) {
285 | try {
286 | fs.unlinkSync(file.path);
287 | } catch (e) {
288 | self.apos.util.warn(req.t('aposForm:fileMissingEarly', {
289 | path: file
290 | }));
291 | }
292 | }
293 | }
294 | }
295 | ]
296 | }
297 | };
298 | },
299 | handlers(self) {
300 | return {
301 | submission: {
302 | async saveSubmission(req, form, data) {
303 | if (self.options.saveSubmissions === false) {
304 | return;
305 | }
306 | const submission = {
307 | createdAt: new Date(),
308 | formId: form._id,
309 | data
310 | };
311 | await self.emit('beforeSaveSubmission', req, {
312 | form,
313 | data,
314 | submission
315 | });
316 | return self.db.insertOne(submission);
317 | },
318 | async emailSubmission(req, form, data) {
319 | await self.sendEmailSubmissions(req, form, data);
320 | },
321 | async emailConfirmation(req, form, data) {
322 | if (form.sendConfirmationEmail !== true || !form.emailConfirmationField) {
323 | return;
324 | }
325 |
326 | // Email validation (Regex reference: https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript)
327 | const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
328 |
329 | if (
330 | data[form.emailConfirmationField] &&
331 | (typeof data[form.emailConfirmationField] !== 'string' ||
332 | !re.test(data[form.emailConfirmationField]))
333 | ) {
334 | await self.apos.notify(req, 'aposForm:errorEmailConfirm', {
335 | type: 'warning',
336 | icon: 'alert-circle-icon',
337 | interpolate: {
338 | field: form.emailConfirmationField
339 | }
340 | });
341 | return null;
342 | }
343 |
344 | try {
345 | const emailOptions = {
346 | form,
347 | data,
348 | to: data[form.emailConfirmationField]
349 | };
350 | await self.sendEmail(req, 'emailConfirmation', emailOptions);
351 |
352 | return null;
353 | } catch (err) {
354 | self.apos.util.error('⚠️ @apostrophecms/form submission email confirmation error: ', err);
355 |
356 | return null;
357 | }
358 | }
359 | }
360 | };
361 | }
362 | };
363 |
364 | function getBundleModuleNames() {
365 | const source = path.join(__dirname, './modules/@apostrophecms');
366 | return fs
367 | .readdirSync(source, { withFileTypes: true })
368 | .filter(dirent => dirent.isDirectory())
369 | .map(dirent => `@apostrophecms/${dirent.name}`);
370 | }
371 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
17 |
18 | **Let content teams build and manage forms without developer intervention.** Editors can create contact forms, surveys, applications, and registrations directly in the CMS, then place them anywhere on your site. Forms automatically handle submissions, email notifications, validation, and spam protection.
19 |
20 |
21 | ## Why Form Builder?
22 |
23 | - **No-Code Form Creation**: Editors build forms through configurable field widgets—no tickets to developers
24 | - **Automatic Data Collection**: Submissions saved to MongoDB with optional email notifications
25 | - **🛡️ Built-in Security**: reCAPTCHA v3 integration and validation prevent spam
26 | - **Design Freedom**: Custom CSS classes and styling hooks for brand consistency
27 | - **Developer-Friendly**: Event hooks, custom validators, and extensible field types
28 | - **Email Ready**: Route submissions to multiple recipients automatically
29 |
30 |
31 | ## Table of Contents
32 | - [Installation](#installation)
33 | - [Usage](#usage)
34 | - [Module Configuration](#module-configuration)
35 | - [How It Works](#how-it-works)
36 | - [Adding Form Widget to Areas](#adding-form-widget-to-areas)
37 | - [Editor Workflow](#editor-workflow)
38 | - [Configuration](#configuration)
39 | - [Main Module Options](#main-module-options)
40 | - [Available Field Types](#available-field-types)
41 | - [Handling Submissions](#handling-submissions)
42 | - [Database Storage](#database-storage)
43 | - [Email Notifications](#email-notifications)
44 | - [Server-Side Events](#server-side-events)
45 | - [Browser Events](#browser-events)
46 | - [Success Event](#success-event)
47 | - [Failure Event](#failure-event)
48 | - [reCAPTCHA Integration](#recaptcha-integration)
49 | - [Configuration Options](#configuration-options)
50 | - [Styling](#styling)
51 | - [Custom CSS Classes](#custom-css-classes)
52 | - [Field-Specific Options](#field-specific-options)
53 | - [Select Field](#select-field)
54 | - [File Upload Field](#file-upload-field)
55 | - [Custom Field Validation](#custom-field-validation)
56 | - [Extending Collectors with the Super Pattern](#extending-collectors-with-the-super-pattern)
57 | - [Example: Minimum Word Count Validation](#example-minimum-word-count-validation)
58 | - [Error Handling](#error-handling)
59 | - [Use Cases](#use-cases)
60 | - [💎 Ready for More?](#-ready-for-more)
61 | - [🚀 **Pro Features for Forms**](#-pro-features-for-forms)
62 |
63 |
64 | ## Installation
65 |
66 | ```bash
67 | npm install @apostrophecms/form
68 | ```
69 |
70 | ## Usage
71 |
72 | ### Module Configuration
73 |
74 | Configure the form modules in your `app.js` file:
75 |
76 | ```javascript
77 | import apostrophe from 'apostrophe';
78 |
79 | apostrophe({
80 | root: import.meta,
81 | shortName: 'my-project',
82 | modules: {
83 | // Main form module (must come first)
84 | '@apostrophecms/form': {},
85 | // Form widget for adding forms to areas
86 | '@apostrophecms/form-widget': {},
87 | // Field widgets (include only the types you need)
88 | '@apostrophecms/form-text-field-widget': {},
89 | '@apostrophecms/form-textarea-field-widget': {},
90 | '@apostrophecms/form-select-field-widget': {},
91 | '@apostrophecms/form-radio-field-widget': {},
92 | '@apostrophecms/form-file-field-widget': {},
93 | '@apostrophecms/form-checkboxes-field-widget': {},
94 | '@apostrophecms/form-boolean-field-widget': {},
95 | '@apostrophecms/form-conditional-widget': {},
96 | '@apostrophecms/form-divider-widget': {},
97 | '@apostrophecms/form-group-widget': {}
98 | }
99 | });
100 | ```
101 |
102 | **Module order matters:** `@apostrophecms/form` must appear before the widget modules. Include only the field types you want editors to use.
103 |
104 | ### How It Works
105 |
106 | The `@apostrophecms/form` module creates a new **piece-type** called "Forms" in your CMS. This means forms are content that editors create once and can reuse across multiple pages—just like blog posts or products. Create a "Contact Form" once, then place it on your contact page, footer, and sidebar using the form widget.
107 |
108 | ### Adding Form Widget to Areas
109 |
110 | To let editors add forms to a page or piece-type, include the form widget in an area:
111 |
112 | ```javascript
113 | // modules/contact-page/index.js
114 | export default {
115 | extend: '@apostrophecms/piece-page-type',
116 | options: {
117 | label: 'Contact Page'
118 | },
119 | fields: {
120 | add: {
121 | contactForm: {
122 | type: 'area',
123 | options: {
124 | max: 1,
125 | widgets: {
126 | '@apostrophecms/form': {}
127 | }
128 | }
129 | }
130 | },
131 | group: {
132 | basics: {
133 | fields: ['contactForm']
134 | }
135 | }
136 | }
137 | };
138 | ```
139 |
140 | ### Editor Workflow
141 |
142 | Once configured, editors can create and manage forms:
143 |
144 | 1. **Create a form**: Click "Forms" in the admin bar and create a new form (e.g., "Contact Form")
145 | 2. **Build the form**: Add field widgets (text fields, email, checkboxes, etc.) and configure options
146 | 3. **Configure submission handling**: Set up email notifications and confirmation messages in the "After-Submission" tab
147 | 4. **Place the form**: Edit any page with a form area, add the form widget, and select your created form
148 |
149 | Editors can now create and manage forms independently.
150 |
151 | ## Configuration
152 |
153 | ### Main Module Options
154 |
155 | Configure `@apostrophecms/form` with these options:
156 |
157 | | Property | Type | Description |
158 | |---|---|---|
159 | | `disableOptionalLabel` | Boolean | Removes "(Optional)" text from optional fields. Default: `false` |
160 | | `formWidgets` | Object | Widget configuration for allowed field types in forms |
161 | | `saveSubmissions` | Boolean | Set to `false` to prevent saving submissions to MongoDB. Default: `true` |
162 | | `emailSubmissions` | Boolean | Set to `false` to hide email notification fields. Default: `true` |
163 | | `recaptchaSecret` | String | Secret key from reCAPTCHA site configuration |
164 | | `recaptchaSite` | String | Site key for reCAPTCHA integration |
165 | | `classPrefix` | String | Namespace for CSS classes on form elements |
166 |
167 | ### Available Field Types
168 |
169 | The `formWidgets` option controls which widgets editors can use when building forms. Configure this in your project-level `/modules/@apostrophecms/form/index.js` file to override the built-in defaults. This is a **global setting** that applies to all forms in your project.
170 |
171 | Default configuration:
172 |
173 | ```javascript
174 | // modules/@apostrophecms/form/index.js
175 | export default {
176 | options: {
177 | formWidgets: {
178 | '@apostrophecms/form-text-field': {},
179 | '@apostrophecms/form-textarea-field': {},
180 | '@apostrophecms/form-boolean-field': {},
181 | '@apostrophecms/form-select-field': {},
182 | '@apostrophecms/form-radio-field': {},
183 | '@apostrophecms/form-checkboxes-field': {},
184 | '@apostrophecms/form-conditional': {},
185 | '@apostrophecms/form-divider': {},
186 | '@apostrophecms/rich-text': {
187 | toolbar: [
188 | 'styles', 'bold', 'italic', 'link',
189 | 'orderedList', 'bulletList'
190 | ]
191 | }
192 | }
193 | }
194 | };
195 | ```
196 |
197 | The rich text widget allows editors to add instructions within forms. Any widget type can be included in this configuration.
198 |
199 | > **Need different field types for different forms?** The `formWidgets` option is global and cannot be set per-area or per-page. If you need separate sets of allowed fields (for example, a simple contact form vs. a detailed application form), extend the `@apostrophecms/form` module to create a second form piece-type with its own `formWidgets` configuration. **However**, without additional controls, all editors can use both form types. Use [`@apostrophecms-pro/advanced-permission`](https://github.com/apostrophecms/advanced-permission) to restrict which user groups can create and manage each form type—ensuring junior editors only access basic forms while senior staff can use advanced forms. [Learn more about Pro features](#-ready-for-more).
200 |
201 | ## Handling Submissions
202 |
203 | ### Database Storage
204 |
205 | Submissions are automatically saved to the `aposFormSubmissions` MongoDB collection. To disable database storage:
206 |
207 | ```javascript
208 | // modules/@apostrophecms/form/index.js
209 | export default {
210 | options: {
211 | saveSubmissions: false
212 | }
213 | };
214 | ```
215 |
216 |
217 | ### Email Notifications
218 |
219 | If `@apostrophecms/email` is configured, forms can automatically email submissions to multiple recipients. In the form editor, navigate to the "After-Submission" tab and enter comma-separated email addresses in the "Email Address(es) for Results" field.
220 |
221 | To hide email notification fields:
222 |
223 | ```javascript
224 | // modules/@apostrophecms/form/index.js
225 | export default {
226 | options: {
227 | emailSubmissions: false
228 | }
229 | };
230 | ```
231 |
232 | > **📧 Email Configuration**: To send form submissions via email, you must first configure the `@apostrophecms/email` module. See the [email configuration guide](https://docs.apostrophecms.org/guide/sending-email.html) for setup instructions. Forms can still save submissions to the database without email configuration.
233 |
234 | ### Server-Side Events
235 |
236 | Form submissions trigger events you can handle in your code for custom processing, integrations, or modifying submission data. For example, you could send submissions to an external CRM, add server-side metadata like query parameters, or trigger custom workflows.
237 |
238 | **`submission` event** - Fires on every form submission:
239 |
240 | ```javascript
241 | // modules/@apostrophecms/form/index.js
242 | export default {
243 | handlers(self) {
244 | return {
245 | 'submission': {
246 | async handleSubmission(req, form, submission) {
247 | // Your custom logic here
248 | console.log('Form submitted:', form.title);
249 | console.log('Data:', submission);
250 | }
251 | }
252 | };
253 | }
254 | };
255 | ```
256 |
257 | **`beforeSaveSubmission` event** - Fires before saving the `info.submission` to the database (if enabled):
258 |
259 | ```javascript
260 | // modules/@apostrophecms/form/index.js
261 | export default {
262 | handlers(self) {
263 | return {
264 | 'beforeSaveSubmission': {
265 | async modifySubmission(req, info) {
266 | // Modify info.submission before it's saved
267 | info.submission.processedAt = new Date();
268 | }
269 | }
270 | };
271 | }
272 | };
273 | ```
274 |
275 | Event handler arguments:
276 |
277 | | Event | Arguments | Description |
278 | |---|---|---|
279 | | `submission` | `req`, `form`, `submission` | Request object, form document, submission data |
280 | | `beforeSaveSubmission` | `req`, `info` | Request object, object with `form`, `data`, and `submission` properties |
281 |
282 | ### Browser Events
283 |
284 | The form module emits browser events on the `body` element after a submission attempt. You can listen for these to add **custom client-side feedback or analytics**.
285 |
286 | #### Success Event
287 |
288 | `@apostrophecms/form:submission-form`
289 | Fires when a submission is successfully processed. The event detail includes a `form` property.
290 |
291 | ```js
292 | document.body.addEventListener('@apostrophecms/form:submission-form', e => {
293 | // e.detail.form contains the form element/config
294 | console.log('Form submitted successfully:', e.detail.form);
295 |
296 | // Example: show a toast notification
297 | apos.notify('✅ Thanks for your submission!', { type: 'success' });
298 |
299 | // Example: send analytics event
300 | gtag('event', 'form_submission', {
301 | formTitle: e.detail.form.title || 'Untitled Form'
302 | });
303 | });
304 | ```
305 | #### Failure Event
306 |
307 | `@apostrophecms/form:submission-failed`
308 | Fires when a submission fails due to validation errors, spam protection, or server issues. The event detail includes a `formError` property.
309 |
310 | ```js
311 | document.body.addEventListener('@apostrophecms/form:submission-failed', e => {
312 | // e.detail.formError contains the error info
313 | console.error('Form submission failed:', e.detail.formError);
314 |
315 | // Example: show a custom error banner
316 | apos.notify('⚠️ Something went wrong. Please try again.', { type: 'danger' });
317 |
318 | // Example: track failed attempts
319 | gtag('event', 'form_submission_failed', {
320 | error: e.detail.formError.message || 'Unknown'
321 | });
322 | });
323 | ```
324 | **Use Cases**
325 | * Replace the default "thank you" UI with a custom success message
326 | * Push events into Google Tag Manager, Segment, or other analytics
327 | * Redirect or scroll the page after a successful submission
328 | * Display tailored error messages on failure
329 |
330 | > **Note:** Forms already support built-in after-submission messages (`thankYouHeading`, `thankYouBody`) and inline error handling. You only need these browser events if you want extra client-side behavior beyond what the module provides out of the box.
331 |
332 | ## reCAPTCHA Integration
333 |
334 | Protect forms from spam with Google reCAPTCHA v3. Set up reCAPTCHA at [google.com/recaptcha](https://www.google.com/recaptcha/) using version 3, then configure your site and secret keys.
335 |
336 | ### Configuration Options
337 |
338 | **Option 1: Hard-code in module configuration**
339 |
340 | ```javascript
341 | // modules/@apostrophecms/form/index.js
342 | export default {
343 | options: {
344 | recaptchaSecret: 'YOUR_SECRET_KEY',
345 | recaptchaSite: 'YOUR_SITE_KEY'
346 | }
347 | };
348 | ```
349 |
350 | **Option 2: Allow editors to configure in UI**
351 |
352 | If you don't hard-code both keys, a global settings UI appears where admins can enter them. Once configured, each form has a checkbox to enable reCAPTCHA independently.
353 |
354 | ## Styling
355 |
356 | ### Custom CSS Classes
357 |
358 | Add your own class prefix to form elements for complete styling control:
359 |
360 | ```javascript
361 | // modules/@apostrophecms/form/index.js
362 | export default {
363 | options: {
364 | classPrefix: 'my-form'
365 | }
366 | };
367 | ```
368 |
369 | This generates BEM-style classes like `my-form__input`, `my-form__label`, and `my-form__error` on form elements.
370 |
371 | For teams who prefer visual design tools, the [Palette extension](https://apostrophecms.com/extensions/palette-extension) allows in-context CSS customization without writing code. [Learn more about Pro features](#-ready-for-more).
372 |
373 | ## Field-Specific Options
374 |
375 | ### Select Field
376 |
377 | The select field widget supports multiple selections:
378 |
379 | ```javascript
380 | // modules/@apostrophecms/form-select-field-widget/index.js
381 | export default {
382 | options: {
383 | allowMultiple: true // Default: false
384 | }
385 | };
386 | ```
387 |
388 | When enabled, two additional fields appear in the widget schema:
389 |
390 | | Property | Type | Description | Default |
391 | |---|---|---|---|
392 | | `allowMultiple` | Boolean | Enable multiple selections. | `false` |
393 | | `size` | Integer | Number of visible options. Set to `0` to use the default compact dropdown; set to `2` or higher to render a listbox showing that many options at once. | `0` |
394 |
395 | ### File Upload Field
396 |
397 | ⚠️ **Security Warning**: File upload fields allow any visitor to upload files to your server, creating potential risks for storage abuse and malicious uploads. This widget is **not included by default**—you must explicitly enable it.
398 |
399 | **Where to implement security measures:**
400 |
401 | 1. **Storage provider level** (AWS S3, Google Cloud Storage, Azure Blob):
402 | - Configure file type restrictions, size limits, and lifecycle policies in your provider's console
403 | - Set up bucket quotas and alerts for unusual upload patterns
404 | - See your provider's documentation for content validation features
405 |
406 | 2. **ApostropheCMS attachment module** (`modules/@apostrophecms/attachment/index.js`):
407 | ```javascript
408 | export default {
409 | options: {
410 | // Restrict file types (extension allowlist)
411 | fileGroups: [
412 | {
413 | name: 'images',
414 | extensions: ['jpg', 'jpeg', 'png', 'gif', 'webp'],
415 | extensionMaps: {},
416 | contentTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
417 | },
418 | {
419 | name: 'office',
420 | extensions: ['pdf', 'doc', 'docx', 'xls', 'xlsx'],
421 | contentTypes: ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']
422 | }
423 | ],
424 | // Set maximum file size (in bytes)
425 | maximumUploadSize: 10485760 // 10MB
426 | }
427 | };
428 | ```
429 | See the [attachment module documentation](https://docs.apostrophecms.org/reference/modules/attachment.html) for complete configuration options.
430 |
431 | 3. **Form submission handler** (for additional validation):
432 | ```javascript
433 | // modules/@apostrophecms/form/index.js
434 | export default {
435 | handlers(self) {
436 | return {
437 | 'beforeSaveSubmission': {
438 | async validateFiles(req, info) {
439 | // Add custom file validation logic here
440 | // Access uploaded files via info.data
441 | }
442 | }
443 | };
444 | }
445 | };
446 | ```
447 |
448 | 4. **Spam protection**: Enable reCAPTCHA v3 (see [reCAPTCHA Integration](#recaptcha-integration) section)
449 |
450 | Files are stored in your configured attachment storage (local uploads or cloud bucket). Form submissions save attachment URLs, not the files themselves.
451 |
452 | **Multiple file uploads**: Like the select field, the file field widget supports an `allowMultiple` option:
453 |
454 | ```javascript
455 | // modules/@apostrophecms/form-file-field-widget/index.js
456 | export default {
457 | options: {
458 | allowMultiple: true // Default: false
459 | }
460 | };
461 | ```
462 |
463 | When enabled, users can select and upload multiple files in a single form submission.
464 |
465 | ## Custom Field Validation
466 |
467 | Need business-specific rules like minimum word counts, format requirements, or cross-field dependencies? Extend the built-in field collectors to add custom validation logic before submission. This runs client-side for immediate feedback without server round-trips.
468 |
469 | Each field returns its value from a collector function located on the `apos.aposForm.collectors` array in the browser. You can extend these collector functions to adjust the value or do additional validation before the form posts to the server. Collector functions can be written as asynchronous functions if needed.
470 |
471 | Collector functions take the widget element as an argument and return a response object on a successful submission. The response object properties are:
472 |
473 | | Property | Description |
474 | |---|---|
475 | | `field` | The field element's `name` attribute (identical to the field widget's `name` property) |
476 | | `value` | The field value |
477 |
478 | ### Extending Collectors with the Super Pattern
479 |
480 | These functions can be extended for project-level validation using the super pattern. This involves:
481 |
482 | 1. Assigning the original function to a variable
483 | 2. Creating a new function that uses the original one, adds functionality, and returns an identically structured response
484 | 3. Assigning the new function to the original function property
485 |
486 | ### Example: Minimum Word Count Validation
487 |
488 | ```javascript
489 | // modules/@apostrophecms/form-textarea-field-widget/ui/src/index.js
490 |
491 | export default () => {
492 | const TEXTAREA_WIDGET = '@apostrophecms/form-textarea-field';
493 |
494 | // 1️⃣ Store the original collector function on `superCollector`.
495 | const superCollector = apos.aposForm.collectors[TEXTAREA_WIDGET].collector;
496 |
497 | // 2️⃣ Create a new collector function that accepts the same widget element
498 | // parameter.
499 | function newCollector(el) {
500 | // Get the response from the original collector.
501 | const response = superCollector(el);
502 |
503 | if (response.value && response.value.split(' ').length < 10) {
504 | // Throwing an object if there are fewer than ten words.
505 | throw {
506 | field: response.field,
507 | message: 'Write at least 10 words'
508 | };
509 | } else {
510 | // Returning the original response if everything is okay.
511 | return response;
512 | }
513 | }
514 |
515 | // 3️⃣ Assign our new collector to the original property.
516 | apos.aposForm.collectors[TEXTAREA_WIDGET].collector = newCollector;
517 | };
518 | ```
519 |
520 | ### Error Handling
521 |
522 | If you want to indicate an error on the field, `throw` an object with the following values (as shown above):
523 |
524 | | Property | Description |
525 | |---|---|
526 | | `field` | The field element's `name` attribute (identical to the field widget's `name` property) |
527 | | `message` | A string to display on the field as an error message |
528 |
529 | ## Use Cases
530 |
531 | **Contact Forms**: Let teams create department-specific contact forms without developer involvement.
532 |
533 | **Lead Generation**: Build conversion-optimized forms with conditional fields and reCAPTCHA protection.
534 |
535 | **Event Registration**: Collect attendee information with file uploads for documents or photos.
536 |
537 | **User Feedback**: Create surveys and feedback forms that route to appropriate team members.
538 |
539 | **Job Applications**: Accept resumes and application materials with validation and email routing.
540 |
541 | ## 💎 Ready for More?
542 |
543 | The open-source form builder provides powerful form creation capabilities, but enterprise teams often need advanced control and workflow features. [**ApostropheCMS Pro**](https://apostrophecms.com/pro) extends form functionality with professional-grade features:
544 |
545 | ### 🚀 **Pro Features for Forms**
546 |
547 | - **🔐 Advanced Permissions** - Control which teams can create, edit, and manage specific form types. Restrict access to sensitive forms like HR applications or customer data collection. Perfect for multi-team organizations that need different form capabilities for different departments.
548 |
549 | - **🌍 Automated Translation** - Automatically translate forms and confirmation messages into multiple languages with AI-powered translation services (DeepL, Google Translate, Azure). Deploy multilingual forms without manual translation work.
550 |
551 | - **📄 Document Management** - Version control for forms with complete audit trails. Track changes to form fields, restore previous versions, and maintain compliance with form modification history.
552 |
553 | - **🎨 Visual Design Tools** - Use the Palette extension for in-context CSS customization of form styling without writing code. Perfect for teams without dedicated frontend developers.
554 |
555 | Create an account on Apostrophe Workspaces and upgrade to [**ApostropheCMS Pro**](https://app.apostrophecms.com/login) or **[contact our team](https://apostrophecms.com/contact-us)** to learn about Pro licensing and access enterprise features that enhance form management, compliance, and team collaboration.
556 |
557 | ---
558 |
559 | Made with ❤️ by the ApostropheCMS team. Found this useful? Give us a star on GitHub! ⭐
561 |