Compare commits
107 commits
v0.9.0-bet
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
55d81f1d9d | ||
|
4d7f7dba72 | ||
|
d9a2cf7549 | ||
|
b58769ef62 | ||
|
ea92e8b12e | ||
|
9f2e708798 | ||
|
0e5cd6043f | ||
|
89481edd11 | ||
|
95a81d17ce | ||
|
d695bb34cc | ||
|
aa5eff915d | ||
|
9fe78d6247 | ||
|
ed57ecca99 | ||
|
cf0c8f3855 | ||
|
194e530d3b | ||
|
20939e8121 | ||
|
6bbde095a8 | ||
|
cd1aa810dd | ||
|
6a21776124 | ||
|
0b0cd5a791 | ||
|
a06f1aed77 | ||
|
02b92b5916 | ||
|
65d25fc3f9 | ||
|
49c747d7d0 | ||
|
f08254d2bf | ||
|
09c56da8c6 | ||
|
26a023813e | ||
|
6c40e05d2d | ||
|
708ec66d9b | ||
|
68b80d0eb6 | ||
|
6ada0aabda | ||
|
a401b1cb48 | ||
|
c7505389d4 | ||
|
60220c7424 | ||
|
f6339c7b5c | ||
|
5868db0124 | ||
|
1c8d2725c6 | ||
|
37824136c0 | ||
|
fe61e898a3 | ||
|
97d297e18c | ||
|
9a4f1a0781 | ||
|
f346f0f9ea | ||
|
33450f86bb | ||
|
c479a90c42 | ||
|
cf5cd95c83 | ||
|
2bbe38f4f5 | ||
|
4ddaba889f | ||
|
ad0a0e0841 | ||
|
c6a4d43efe | ||
|
f9a2eb87f0 | ||
|
777a89877a | ||
|
708d0e0b00 | ||
|
07d8be5465 | ||
|
ca19c5998b | ||
|
12f9ad46b5 | ||
|
620271bec4 | ||
|
bf6d4718e4 | ||
|
1e59d53135 | ||
|
4581e47c80 | ||
|
40aaa2694d | ||
|
c358281193 | ||
|
8a9b3efbb0 | ||
|
a266027f6c | ||
|
b060c751ce | ||
|
f8f074cb95 | ||
|
178ee281b1 | ||
|
bc8b4d08e7 | ||
|
97f8c017ae | ||
|
96f63d010c | ||
|
4485460f53 | ||
|
570a81f966 | ||
|
039feef938 | ||
|
e7e36a080f | ||
|
35b1d01621 | ||
|
ca403d5583 | ||
|
6d61c52126 | ||
|
6dbcfee080 | ||
|
d519a29c7c | ||
|
51d218a484 | ||
|
531d7680e7 | ||
|
faf45d45e1 | ||
|
df34e57e65 | ||
|
c6b85651af | ||
|
207f516673 | ||
|
4d681f053e | ||
|
2579d7c2b3 | ||
|
1ac0e65dd8 | ||
|
e8ad7a9adc | ||
|
f8e555dac5 | ||
|
93a710c9ae | ||
|
8a6ed2ac2e | ||
|
860953e331 | ||
|
267dd52ec3 | ||
|
f268dc69ab | ||
|
d66227256a | ||
|
31ce55a3a1 | ||
|
8779c49660 | ||
|
5777738636 | ||
|
c2d7e101cd | ||
|
82f033b1da | ||
|
77a6110c25 | ||
|
2b8b10c691 | ||
|
da7975f82b | ||
|
b4fea57543 | ||
|
99ff64bd82 | ||
|
97b78aa695 | ||
|
50549f3bfe |
.github
.goreleaser.ymlDockerfileMakefileREADME.mdcmd
campaigns.gohandlers.goi18n.goinit.goinstall.gomain.gopublic.gosettings.gosubscribers.gotemplates.goupgrade.go
config-demo.tomlconfig.toml.samplefrontend
README.mdcypress.jsonpackage.json
go.modgo.sumcypress
downloads
fixtures
integration
plugins
support
src
yarn.locki18n
internal
models
queries.sqlschema.sqlscripts
static
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Report a bug or a problem
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Version:**
|
||||
- listmonk: [eg: v1.0.0]
|
||||
- OS: [e.g. Fedora]
|
||||
|
||||
**Description of the bug and steps to reproduce:**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Screenshots:**
|
||||
If applicable, add screenshots to help explain your problem.
|
14
.github/ISSUE_TEMPLATE/feature-or-change-request.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/feature-or-change-request.md
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
name: Feature or change request
|
||||
about: Suggest new features or changes to existing features
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
10
.github/ISSUE_TEMPLATE/general.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/general.md
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
name: General
|
||||
about: General questions and discussions
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
38
.github/workflows/release.yml
vendored
Normal file
38
.github/workflows/release.yml
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
name: goreleaser
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*" # Will trigger only if tag is pushed matching pattern `v*` (Eg: `v0.1.0`)
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
|
||||
- name: Login to Docker Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Prepare Dependencies
|
||||
run: |
|
||||
make deps
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
version: latest
|
||||
args: --parallelism 1 --rm-dist --skip-validate
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -35,7 +35,7 @@ dockers:
|
|||
-
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
binaries:
|
||||
ids:
|
||||
- listmonk
|
||||
image_templates:
|
||||
- "listmonk/listmonk:latest"
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
FROM alpine:latest AS deploy
|
||||
FROM alpine:latest
|
||||
RUN apk --no-cache add ca-certificates
|
||||
WORKDIR /listmonk
|
||||
COPY listmonk .
|
||||
COPY config.toml.sample config.toml
|
||||
COPY config-demo.toml .
|
||||
CMD ["./listmonk"]
|
||||
EXPOSE 9000
|
||||
|
|
2
Makefile
2
Makefile
|
@ -20,7 +20,7 @@ deps:
|
|||
# Build the backend to ./listmonk.
|
||||
.PHONY: build
|
||||
build:
|
||||
go build -o ${BIN} -ldflags="-s -w -X 'main.buildString=${BUILDSTR}' -X 'main.versionString=${VERSION}'" cmd/*.go
|
||||
CGO_ENABLED=0 go build -o ${BIN} -ldflags="-s -w -X 'main.buildString=${BUILDSTR}' -X 'main.versionString=${VERSION}'" cmd/*.go
|
||||
|
||||
# Run the backend.
|
||||
.PHONY: run
|
||||
|
|
88
README.md
88
README.md
|
@ -32,12 +32,90 @@ More information on [docs](https://listmonk.app/docs).
|
|||
__________________
|
||||
|
||||
### Binary
|
||||
- Download the [latest release](https://github.com/knadh/listmonk/releases) and extract the listmonk binary.
|
||||
- `./listmonk --new-config` to generate config.toml. Then, edit the file.
|
||||
- `./listmonk --install` to setup the Postgres DB (or `--upgrade` to upgrade an existing DB. Upgrades are idempotent and running them multiple times have no side effects).
|
||||
- Run `./listmonk` and visit `http://localhost:9000`.
|
||||
|
||||
__________________
|
||||
#### prerequisites Debian: system user and PostgreSQL
|
||||
- install `sudo`
|
||||
- install PostgreSQL `apt install postgresql`
|
||||
- --create system user with config.toml data for the database:
|
||||
`adduser listmonkuser`--
|
||||
- create the postgreSQL database, the user and grant permissions:
|
||||
`su - postgres` and then acces de PostgreSQL console `psql`
|
||||
```
|
||||
postgres=# CREATE DATABASE listmonkdatabase;
|
||||
postgres=# CREATE USER listmonkuser WITH PASSWORD 'listmonkpassword';
|
||||
postgres=# GRANT ALL PRIVILEGES ON DATABASE listmonkdatabase TO listmonkuser;
|
||||
```
|
||||
|
||||
#### download an prepare config file
|
||||
- `cd /var/www/` and make the directory `mkdir listmonk`
|
||||
- `cd listmonk`
|
||||
- Download the [latest release](https://github.com/knadh/listmonk/releases) and extract the listmonk binary.
|
||||
```
|
||||
wget https://github.com/knadh/listmonk/releases/download/v1.0.0/listmonk_1.0.0_linux_amd64.tar.gz
|
||||
wget https://github.com/knadh/listmonk/releases/download/v1.0.0/listmonk_1.0.0_checksums.txt
|
||||
sha256sum listmonk_1.0.0_linux_amd64.tar.gz
|
||||
cat listmonk_1.0.0_checksums.txt |grep linux
|
||||
```
|
||||
- change ownership of files `chown -R www-data:www-data /var/www/listmonk`
|
||||
- generate config file `sudo -u www-data ./listmonk --new-config`
|
||||
- edit the config file with the data we created previously and more options `vim /var/www/listmonk/confif.toml`
|
||||
|
||||
#### reverse proxy
|
||||
- create the apache config to work behind a reverse proxy `/etc/apache2/sites-available/listmonk.doamain.tdl.conf`
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerName listmonk.domain.tdl
|
||||
|
||||
ServerSignature Off
|
||||
|
||||
|
||||
ErrorLog /var/log/apache2/listmonk.domain.tdl_error.log
|
||||
TransferLog /var/log/apache2/listmonk.domain.tdl_access.log
|
||||
LogLevel warn
|
||||
|
||||
ProxyPreserveHost On
|
||||
ProxyPass "/" "http://127.0.0.1:9000/"
|
||||
ProxyPassReverse "/" "http://127.0.0.1:9000/"
|
||||
</VirtualHost>
|
||||
```
|
||||
- enable the proxy module `a2enmod proxy_http` enable the site `a2ensite listmonk.domain.tdl` and restart apache `systemctl reload apache2`
|
||||
|
||||
#### proceed to install
|
||||
- `sudo -u www-data ./listmonk --install` to setup the Postgres DB (or `--upgrade` to upgrade an existing DB. Upgrades are idempotent and running them multiple times have no side effects).
|
||||
- Run `sudo -u www-data ./listmonk` and visit `http://listmonk.domain.tdl`
|
||||
|
||||
#### after install, fine tunning
|
||||
oncen checked it works, stop the process.
|
||||
- move the files to where you like most, usually `/opt/listmonk/` or inside `/var/www/html/`
|
||||
- change ownership of the files to `www-data` or whatever user you have assigned the webserver `chown -R www-data:www-data listmonk`
|
||||
- run listmonk as a service, create `/etc/systemd/system/listmonk.service` with:
|
||||
```
|
||||
[Unit]
|
||||
Description=Listmonk domain.tdl server
|
||||
After=syslog.target network.target postgressql.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# the user and group executing the service
|
||||
User=www-data
|
||||
Group=www-data
|
||||
# the directory where you have listmonk
|
||||
WorkingDirectory=/var/www/html/listmonk
|
||||
# the file to execute
|
||||
ExecStart=/var/www/html/listmonk/listmonk
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
- enable the service `systemct enable listmonk.service` and restart the daemon `systemctl daemon-restart` or you can start just the service `systemctl start listmonk.service`
|
||||
|
||||
#### fixing small dependencies
|
||||
- uploads fail: `mkdir /var/www/html/listmonk/uploads` and `chown -R www-data:www-data /var/www/html/listmonk`
|
||||
|
||||
|
||||
___________________
|
||||
|
||||
### Heroku
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/jaytaylor/html2text"
|
||||
"github.com/knadh/listmonk/internal/messenger"
|
||||
"github.com/knadh/listmonk/internal/subimporter"
|
||||
"github.com/knadh/listmonk/models"
|
||||
|
@ -23,7 +22,8 @@ import (
|
|||
null "gopkg.in/volatiletech/null.v6"
|
||||
)
|
||||
|
||||
// campaignReq is a wrapper over the Campaign model.
|
||||
// campaignReq is a wrapper over the Campaign model for receiving
|
||||
// campaign creation and updation data from APIs.
|
||||
type campaignReq struct {
|
||||
models.Campaign
|
||||
|
||||
|
@ -42,6 +42,14 @@ type campaignReq struct {
|
|||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// campaignContentReq wraps params coming from API requests for converting
|
||||
// campaign content formats.
|
||||
type campaignContentReq struct {
|
||||
models.Campaign
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
}
|
||||
|
||||
type campaignStats struct {
|
||||
ID int `db:"id" json:"id"`
|
||||
Status string `db:"status" json:"status"`
|
||||
|
@ -155,16 +163,14 @@ func handlePreviewCampaign(c echo.Context) error {
|
|||
var (
|
||||
app = c.Get("app").(*App)
|
||||
id, _ = strconv.Atoi(c.Param("id"))
|
||||
body = c.FormValue("body")
|
||||
|
||||
camp = &models.Campaign{}
|
||||
)
|
||||
|
||||
if id < 1 {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidID"))
|
||||
}
|
||||
|
||||
err := app.queries.GetCampaignForPreview.Get(camp, id)
|
||||
var camp models.Campaign
|
||||
err := app.queries.GetCampaignForPreview.Get(&camp, id)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return echo.NewHTTPError(http.StatusBadRequest,
|
||||
|
@ -177,33 +183,23 @@ func handlePreviewCampaign(c echo.Context) error {
|
|||
"name", "{globals.terms.campaign}", "error", pqErrMsg(err)))
|
||||
}
|
||||
|
||||
var sub models.Subscriber
|
||||
// Get a random subscriber from the campaign.
|
||||
if err := app.queries.GetOneCampaignSubscriber.Get(&sub, camp.ID); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
// There's no subscriber. Mock one.
|
||||
sub = dummySubscriber
|
||||
} else {
|
||||
app.log.Printf("error fetching subscriber: %v", err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||
app.i18n.Ts("globals.messages.errorFetching",
|
||||
"name", "{globals.terms.subscriber}", "error", pqErrMsg(err)))
|
||||
}
|
||||
// There's a body in the request to preview instead of the body in the DB.
|
||||
if c.Request().Method == http.MethodPost {
|
||||
camp.ContentType = c.FormValue("content_type")
|
||||
camp.Body = c.FormValue("body")
|
||||
}
|
||||
|
||||
// Compile the template.
|
||||
if body != "" {
|
||||
camp.Body = body
|
||||
}
|
||||
|
||||
if err := camp.CompileTemplate(app.manager.TemplateFuncs(camp)); err != nil {
|
||||
// Use a dummy campaign ID to prevent views and clicks from {{ TrackView }}
|
||||
// and {{ TrackLink }} being registered on preview.
|
||||
camp.UUID = dummySubscriber.UUID
|
||||
if err := camp.CompileTemplate(app.manager.TemplateFuncs(&camp)); err != nil {
|
||||
app.log.Printf("error compiling template: %v", err)
|
||||
return echo.NewHTTPError(http.StatusBadRequest,
|
||||
app.i18n.Ts("templates.errorCompiling", "error", err.Error()))
|
||||
}
|
||||
|
||||
// Render the message body.
|
||||
m := app.manager.NewCampaignMessage(camp, sub)
|
||||
m := app.manager.NewCampaignMessage(&camp, dummySubscriber)
|
||||
if err := m.Render(); err != nil {
|
||||
app.log.Printf("error rendering message: %v", err)
|
||||
return echo.NewHTTPError(http.StatusBadRequest,
|
||||
|
@ -213,15 +209,28 @@ func handlePreviewCampaign(c echo.Context) error {
|
|||
return c.HTML(http.StatusOK, string(m.Body()))
|
||||
}
|
||||
|
||||
// handleCampainBodyToText converts an HTML campaign body to plaintext.
|
||||
func handleCampainBodyToText(c echo.Context) error {
|
||||
out, err := html2text.FromString(c.FormValue("body"),
|
||||
html2text.Options{PrettyTables: false})
|
||||
if err != nil {
|
||||
// handleCampaignContent handles campaign content (body) format conversions.
|
||||
func handleCampaignContent(c echo.Context) error {
|
||||
var (
|
||||
app = c.Get("app").(*App)
|
||||
id, _ = strconv.Atoi(c.Param("id"))
|
||||
)
|
||||
|
||||
if id < 1 {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidID"))
|
||||
}
|
||||
|
||||
var camp campaignContentReq
|
||||
if err := c.Bind(&camp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.HTML(http.StatusOK, string(out))
|
||||
out, err := camp.ConvertContent(camp.From, camp.To)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, okResp{out})
|
||||
}
|
||||
|
||||
// handleCreateCampaign handles campaign creation.
|
||||
|
|
|
@ -37,9 +37,17 @@ var (
|
|||
)
|
||||
|
||||
// registerHandlers registers HTTP handlers.
|
||||
func registerHTTPHandlers(e *echo.Echo) {
|
||||
func registerHTTPHandlers(e *echo.Echo, app *App) {
|
||||
// Group of private handlers with BasicAuth.
|
||||
g := e.Group("", middleware.BasicAuth(basicAuth))
|
||||
var g *echo.Group
|
||||
|
||||
if len(app.constants.AdminUsername) == 0 ||
|
||||
len(app.constants.AdminPassword) == 0 {
|
||||
g = e.Group("")
|
||||
} else {
|
||||
g = e.Group("", middleware.BasicAuth(basicAuth))
|
||||
}
|
||||
|
||||
g.GET("/", handleIndexPage)
|
||||
g.GET("/api/health", handleHealthCheck)
|
||||
g.GET("/api/config", handleGetServerConfig)
|
||||
|
@ -89,6 +97,7 @@ func registerHTTPHandlers(e *echo.Echo) {
|
|||
g.GET("/api/campaigns/:id", handleGetCampaigns)
|
||||
g.GET("/api/campaigns/:id/preview", handlePreviewCampaign)
|
||||
g.POST("/api/campaigns/:id/preview", handlePreviewCampaign)
|
||||
g.POST("/api/campaigns/:id/content", handleCampaignContent)
|
||||
g.POST("/api/campaigns/:id/text", handlePreviewCampaign)
|
||||
g.POST("/api/campaigns/:id/test", handleTestCampaign)
|
||||
g.POST("/api/campaigns", handleCreateCampaign)
|
||||
|
|
23
cmd/i18n.go
23
cmd/i18n.go
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/knadh/listmonk/internal/i18n"
|
||||
"github.com/knadh/stuffbin"
|
||||
|
@ -29,8 +30,8 @@ func handleGetI18nLang(c echo.Context) error {
|
|||
return echo.NewHTTPError(http.StatusBadRequest, "Invalid language code.")
|
||||
}
|
||||
|
||||
i, err := getI18nLang(lang, app.fs)
|
||||
if err != nil {
|
||||
i, ok, err := getI18nLang(lang, app.fs)
|
||||
if err != nil && !ok {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Unknown language.")
|
||||
}
|
||||
|
||||
|
@ -62,32 +63,38 @@ func getI18nLangList(lang string, app *App) ([]i18nLang, error) {
|
|||
})
|
||||
}
|
||||
|
||||
sort.SliceStable(out, func(i, j int) bool {
|
||||
return out[i].Code < out[j].Code
|
||||
})
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func getI18nLang(lang string, fs stuffbin.FileSystem) (*i18n.I18n, error) {
|
||||
// The bool indicates whether the specified language could be loaded. If it couldn't
|
||||
// be, the app shouldn't halt but throw a warning.
|
||||
func getI18nLang(lang string, fs stuffbin.FileSystem) (*i18n.I18n, bool, error) {
|
||||
const def = "en"
|
||||
|
||||
b, err := fs.Read(fmt.Sprintf("/i18n/%s.json", def))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading default i18n language file: %s: %v", def, err)
|
||||
return nil, false, fmt.Errorf("error reading default i18n language file: %s: %v", def, err)
|
||||
}
|
||||
|
||||
// Initialize with the default language.
|
||||
i, err := i18n.New(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error unmarshalling i18n language: %v", err)
|
||||
return nil, false, fmt.Errorf("error unmarshalling i18n language: %s: %v", lang, err)
|
||||
}
|
||||
|
||||
// Load the selected language on top of it.
|
||||
b, err = fs.Read(fmt.Sprintf("/i18n/%s.json", lang))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading i18n language file: %v", err)
|
||||
return i, true, fmt.Errorf("error reading i18n language file: %s: %v", lang, err)
|
||||
}
|
||||
|
||||
if err := i.Load(b); err != nil {
|
||||
return nil, fmt.Errorf("error loading i18n language file: %v", err)
|
||||
return i, true, fmt.Errorf("error loading i18n language file: %s: %v", lang, err)
|
||||
}
|
||||
|
||||
return i, nil
|
||||
return i, true, nil
|
||||
}
|
||||
|
|
39
cmd/init.go
39
cmd/init.go
|
@ -82,6 +82,7 @@ func initFlags() {
|
|||
f.Bool("version", false, "current version of the build")
|
||||
f.Bool("new-config", false, "generate sample config file")
|
||||
f.String("static-dir", "", "(optional) path to directory with static files")
|
||||
f.String("i18n-dir", "", "(optional) path to directory with i18n language files")
|
||||
f.Bool("yes", false, "assume 'yes' to prompts, eg: during --install")
|
||||
if err := f.Parse(os.Args[1:]); err != nil {
|
||||
lo.Fatalf("error loading flags: %v", err)
|
||||
|
@ -107,7 +108,7 @@ func initConfigFiles(files []string, ko *koanf.Koanf) {
|
|||
|
||||
// initFileSystem initializes the stuffbin FileSystem to provide
|
||||
// access to bunded static assets to the app.
|
||||
func initFS(staticDir string) stuffbin.FileSystem {
|
||||
func initFS(staticDir, i18nDir string) stuffbin.FileSystem {
|
||||
// Get the executable's path.
|
||||
path, err := os.Executable()
|
||||
if err != nil {
|
||||
|
@ -125,10 +126,6 @@ func initFS(staticDir string) stuffbin.FileSystem {
|
|||
"config.toml.sample",
|
||||
"queries.sql",
|
||||
"schema.sql",
|
||||
"static/email-templates",
|
||||
|
||||
// Alias /static/public to /public for the HTTP fileserver.
|
||||
"static/public:/public",
|
||||
|
||||
// The frontend app's static assets are aliased to /frontend
|
||||
// so that they are accessible at /frontend/js/* etc.
|
||||
|
@ -138,13 +135,18 @@ func initFS(staticDir string) stuffbin.FileSystem {
|
|||
"i18n:/i18n",
|
||||
}
|
||||
|
||||
// If no external static dir is provided, try to load from the working dir.
|
||||
if staticDir == "" {
|
||||
files = append(files, "static/email-templates", "static/public:/public")
|
||||
}
|
||||
|
||||
fs, err = stuffbin.NewLocalFS("/", files...)
|
||||
if err != nil {
|
||||
lo.Fatalf("failed to initialize local file for assets: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Optional static directory to override files.
|
||||
// Optional static directory to override static files.
|
||||
if staticDir != "" {
|
||||
lo.Printf("loading static files from: %v", staticDir)
|
||||
fStatic, err := stuffbin.NewLocalFS("/", []string{
|
||||
|
@ -161,6 +163,19 @@ func initFS(staticDir string) stuffbin.FileSystem {
|
|||
lo.Fatalf("error merging static directory: %s: %v", staticDir, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Optional static directory to override i18n language files.
|
||||
if i18nDir != "" {
|
||||
lo.Printf("loading i18n language files from: %v", i18nDir)
|
||||
fi18n, err := stuffbin.NewLocalFS("/", []string{i18nDir + ":/i18n"}...)
|
||||
if err != nil {
|
||||
lo.Fatalf("failed reading i18n directory: %s: %v", i18nDir, err)
|
||||
}
|
||||
|
||||
if err := fs.Merge(fi18n); err != nil {
|
||||
lo.Fatalf("error merging i18n directory: %s: %v", i18nDir, err)
|
||||
}
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
|
@ -207,9 +222,9 @@ func initQueries(sqlFile string, db *sqlx.DB, fs stuffbin.FileSystem, prepareQue
|
|||
}
|
||||
|
||||
// initSettings loads settings from the DB.
|
||||
func initSettings(q *Queries) {
|
||||
func initSettings(q *sqlx.Stmt) {
|
||||
var s types.JSONText
|
||||
if err := q.GetSettings.Get(&s); err != nil {
|
||||
if err := q.Get(&s); err != nil {
|
||||
lo.Fatalf("error reading settings from DB: %s", pqErrMsg(err))
|
||||
}
|
||||
|
||||
|
@ -262,10 +277,14 @@ func initConstants() *constants {
|
|||
// and then the selected language is loaded on top of it so that if there are
|
||||
// missing translations in it, the default English translations show up.
|
||||
func initI18n(lang string, fs stuffbin.FileSystem) *i18n.I18n {
|
||||
i, err := getI18nLang(lang, fs)
|
||||
i, ok, err := getI18nLang(lang, fs)
|
||||
if err != nil {
|
||||
if ok {
|
||||
lo.Println(err)
|
||||
} else {
|
||||
lo.Fatal(err)
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
|
@ -487,7 +506,7 @@ func initHTTPServer(app *App) *echo.Echo {
|
|||
}
|
||||
|
||||
// Register all HTTP handlers.
|
||||
registerHTTPHandlers(srv)
|
||||
registerHTTPHandlers(srv, app)
|
||||
|
||||
// Start the server.
|
||||
go func() {
|
||||
|
|
|
@ -166,7 +166,7 @@ func newConfigFile() error {
|
|||
|
||||
// Initialize the static file system into which all
|
||||
// required static assets (.sql, .js files etc.) are loaded.
|
||||
fs := initFS("")
|
||||
fs := initFS("", "")
|
||||
b, err := fs.Read("config.toml.sample")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading sample config (is binary stuffed?): %v", err)
|
||||
|
|
|
@ -106,7 +106,7 @@ func init() {
|
|||
|
||||
// Connect to the database, load the filesystem to read SQL queries.
|
||||
db = initDB()
|
||||
fs = initFS(ko.String("static-dir"))
|
||||
fs = initFS(ko.String("static-dir"), ko.String("i18n-dir"))
|
||||
|
||||
// Installer mode? This runs before the SQL queries are loaded and prepared
|
||||
// as the installer needs to work on an empty DB.
|
||||
|
@ -135,7 +135,7 @@ func init() {
|
|||
_, queries := initQueries(queryFilePath, db, fs, true)
|
||||
|
||||
// Load settings from DB.
|
||||
initSettings(queries)
|
||||
initSettings(queries.GetSettings)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -180,7 +180,9 @@ func main() {
|
|||
srv := initHTTPServer(app)
|
||||
|
||||
// Star the update checker.
|
||||
if ko.Bool("app.check_updates") {
|
||||
go checkUpdates(versionString, time.Hour*24, app)
|
||||
}
|
||||
|
||||
// Wait for the reload signal with a callback to gracefully shut down resources.
|
||||
// The `wait` channel is passed to awaitReload to wait for the callback to finish
|
||||
|
|
|
@ -118,15 +118,14 @@ func handleViewCampaignMessage(c echo.Context) error {
|
|||
}
|
||||
|
||||
// Get the subscriber.
|
||||
var sub models.Subscriber
|
||||
if err := app.queries.GetSubscriber.Get(&sub, 0, subUUID); err != nil {
|
||||
sub, err := getSubscriber(0, subUUID, "", app)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return c.Render(http.StatusNotFound, tplMessage,
|
||||
makeMsgTpl(app.i18n.T("public.notFoundTitle"), "",
|
||||
app.i18n.T("public.errorFetchingEmail")))
|
||||
}
|
||||
|
||||
app.log.Printf("error fetching campaign subscriber: %v", err)
|
||||
return c.Render(http.StatusInternalServerError, tplMessage,
|
||||
makeMsgTpl(app.i18n.T("public.errorTitle"), "",
|
||||
app.i18n.Ts("public.errorFetchingCampaign")))
|
||||
|
@ -303,6 +302,14 @@ func handleSubscriptionForm(c echo.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// If there's a nonce value, a bot could've filled the form.
|
||||
if c.FormValue("nonce") != "" {
|
||||
return c.Render(http.StatusOK, tplMessage,
|
||||
makeMsgTpl(app.i18n.T("public.errorTitle"), "",
|
||||
app.i18n.T("public.invalidFeature")))
|
||||
|
||||
}
|
||||
|
||||
if len(req.SubListUUIDs) == 0 {
|
||||
return c.Render(http.StatusBadRequest, tplMessage,
|
||||
makeMsgTpl(app.i18n.T("public.errorTitle"), "",
|
||||
|
|
|
@ -20,6 +20,7 @@ type settings struct {
|
|||
AppFromEmail string `json:"app.from_email"`
|
||||
AppNotifyEmails []string `json:"app.notify_emails"`
|
||||
EnablePublicSubPage bool `json:"app.enable_public_subscription_page"`
|
||||
CheckUpdates bool `json:"app.check_updates"`
|
||||
AppLang string `json:"app.lang"`
|
||||
|
||||
AppBatchSize int `json:"app.batch_size"`
|
||||
|
|
|
@ -42,6 +42,13 @@ type subsWrap struct {
|
|||
Page int `json:"page"`
|
||||
}
|
||||
|
||||
type subUpdateReq struct {
|
||||
models.Subscriber
|
||||
RawAttribs json.RawMessage `json:"attribs"`
|
||||
Lists pq.Int64Array `json:"lists"`
|
||||
ListUUIDs pq.StringArray `json:"list_uuids"`
|
||||
}
|
||||
|
||||
// subProfileData represents a subscriber's collated data in JSON
|
||||
// for export.
|
||||
type subProfileData struct {
|
||||
|
@ -62,8 +69,8 @@ type subOptin struct {
|
|||
|
||||
var (
|
||||
dummySubscriber = models.Subscriber{
|
||||
Email: "dummy@listmonk.app",
|
||||
Name: "Dummy Subscriber",
|
||||
Email: "demo@listmonk.app",
|
||||
Name: "Demo Subscriber",
|
||||
UUID: dummyUUID,
|
||||
}
|
||||
|
||||
|
@ -293,7 +300,7 @@ func handleUpdateSubscriber(c echo.Context) error {
|
|||
var (
|
||||
app = c.Get("app").(*App)
|
||||
id, _ = strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
req subimporter.SubReq
|
||||
req subUpdateReq
|
||||
)
|
||||
// Get and validate fields.
|
||||
if err := c.Bind(&req); err != nil {
|
||||
|
@ -310,11 +317,21 @@ func handleUpdateSubscriber(c echo.Context) error {
|
|||
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("subscribers.invalidName"))
|
||||
}
|
||||
|
||||
_, err := app.queries.UpdateSubscriber.Exec(req.ID,
|
||||
// If there's an attribs value, validate it.
|
||||
if len(req.RawAttribs) > 0 {
|
||||
var a models.SubscriberAttribs
|
||||
if err := json.Unmarshal(req.RawAttribs, &a); err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||
app.i18n.Ts("globals.messages.errorUpdating",
|
||||
"name", "{globals.terms.subscriber}", "error", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
_, err := app.queries.UpdateSubscriber.Exec(id,
|
||||
strings.ToLower(strings.TrimSpace(req.Email)),
|
||||
strings.TrimSpace(req.Name),
|
||||
req.Status,
|
||||
req.Attribs,
|
||||
req.RawAttribs,
|
||||
req.Lists)
|
||||
if err != nil {
|
||||
app.log.Printf("error updating subscriber: %v", err)
|
||||
|
@ -338,7 +355,6 @@ func handleSubscriberSendOptin(c echo.Context) error {
|
|||
var (
|
||||
app = c.Get("app").(*App)
|
||||
id, _ = strconv.Atoi(c.Param("id"))
|
||||
out models.Subscribers
|
||||
)
|
||||
|
||||
if id < 1 {
|
||||
|
@ -346,19 +362,15 @@ func handleSubscriberSendOptin(c echo.Context) error {
|
|||
}
|
||||
|
||||
// Fetch the subscriber.
|
||||
err := app.queries.GetSubscriber.Select(&out, id, nil)
|
||||
out, err := getSubscriber(id, "", "", app)
|
||||
if err != nil {
|
||||
app.log.Printf("error fetching subscriber: %v", err)
|
||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||
app.i18n.Ts("globals.messages.errorFetching",
|
||||
"name", "{globals.terms.subscribers}", "error", pqErrMsg(err)))
|
||||
}
|
||||
if len(out) == 0 {
|
||||
return echo.NewHTTPError(http.StatusBadRequest,
|
||||
app.i18n.Ts("globals.messages.notFound", "name", "{globals.terms.subscriber}"))
|
||||
}
|
||||
|
||||
if _, err := sendOptinConfirmation(out[0], nil, app); err != nil {
|
||||
if _, err := sendOptinConfirmation(out, nil, app); err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||
app.i18n.T("subscribers.errorSendingOptin"))
|
||||
}
|
||||
|
@ -631,7 +643,14 @@ func insertSubscriber(req subimporter.SubReq, app *App) (models.Subscriber, bool
|
|||
}
|
||||
req.UUID = uu.String()
|
||||
|
||||
isNew := true
|
||||
var (
|
||||
isNew = true
|
||||
subStatus = models.SubscriptionStatusUnconfirmed
|
||||
)
|
||||
if req.PreconfirmSubs {
|
||||
subStatus = models.SubscriptionStatusConfirmed
|
||||
}
|
||||
|
||||
if err = app.queries.InsertSubscriber.Get(&req.ID,
|
||||
req.UUID,
|
||||
req.Email,
|
||||
|
@ -639,7 +658,8 @@ func insertSubscriber(req subimporter.SubReq, app *App) (models.Subscriber, bool
|
|||
req.Status,
|
||||
req.Attribs,
|
||||
req.Lists,
|
||||
req.ListUUIDs); err != nil {
|
||||
req.ListUUIDs,
|
||||
subStatus); err != nil {
|
||||
if pqErr, ok := err.(*pq.Error); ok && pqErr.Constraint == "subscribers_email_key" {
|
||||
isNew = false
|
||||
} else {
|
||||
|
@ -658,9 +678,13 @@ func insertSubscriber(req subimporter.SubReq, app *App) (models.Subscriber, bool
|
|||
return sub, false, false, err
|
||||
}
|
||||
|
||||
hasOptin := false
|
||||
if !req.PreconfirmSubs {
|
||||
// Send a confirmation e-mail (if there are any double opt-in lists).
|
||||
num, _ := sendOptinConfirmation(sub, []int64(req.Lists), app)
|
||||
return sub, isNew, num > 0, nil
|
||||
hasOptin = num > 0
|
||||
}
|
||||
return sub, isNew, hasOptin, nil
|
||||
}
|
||||
|
||||
// getSubscriber gets a single subscriber by ID, uuid, or e-mail in that order.
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
@ -177,8 +176,7 @@ func handleUpdateTemplate(c echo.Context) error {
|
|||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
// TODO: PASSWORD HASHING.
|
||||
res, err := app.queries.UpdateTemplate.Exec(o.ID, o.Name, o.Body)
|
||||
res, err := app.queries.UpdateTemplate.Exec(id, o.Name, o.Body)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||
app.i18n.Ts("globals.messages.errorUpdating",
|
||||
|
@ -223,23 +221,15 @@ func handleDeleteTemplate(c echo.Context) error {
|
|||
|
||||
if id < 1 {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidID"))
|
||||
} else if id == 1 {
|
||||
return echo.NewHTTPError(http.StatusBadRequest,
|
||||
app.i18n.T("templates.cantDeleteDefault"))
|
||||
}
|
||||
|
||||
var delID int
|
||||
err := app.queries.DeleteTemplate.Get(&delID, id)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return c.JSON(http.StatusOK, okResp{true})
|
||||
}
|
||||
|
||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||
app.i18n.Ts("globals.messages.errorCreating",
|
||||
app.i18n.Ts("globals.messages.errorDeleting",
|
||||
"name", "{globals.terms.template}", "error", pqErrMsg(err)))
|
||||
}
|
||||
|
||||
if delID == 0 {
|
||||
return echo.NewHTTPError(http.StatusBadRequest,
|
||||
app.i18n.T("templates.cantDeleteDefault"))
|
||||
|
|
|
@ -29,6 +29,7 @@ var migList = []migFunc{
|
|||
{"v0.7.0", migrations.V0_7_0},
|
||||
{"v0.8.0", migrations.V0_8_0},
|
||||
{"v0.9.0", migrations.V0_9_0},
|
||||
{"v1.0.0", migrations.V1_0_0},
|
||||
}
|
||||
|
||||
// upgrade upgrades the database to the current version by running SQL migration files
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
[app]
|
||||
# Interface and port where the app will run its webserver.
|
||||
address = "0.0.0.0:9000"
|
||||
# Interface and port where the app will run its webserver.
|
||||
address = "0.0.0.0:9000"
|
||||
|
||||
# Database.
|
||||
[db]
|
||||
host = "demo-db"
|
||||
port = 5432
|
||||
user = "listmonk"
|
||||
password = "listmonk"
|
||||
database = "listmonk"
|
||||
ssl_mode = "disable"
|
||||
max_open = 25
|
||||
max_idle = 25
|
||||
max_lifetime = "300s"
|
||||
host = "demo-db"
|
||||
port = 5432
|
||||
user = "listmonk"
|
||||
password = "listmonk"
|
||||
database = "listmonk"
|
||||
ssl_mode = "disable"
|
||||
max_open = 25
|
||||
max_idle = 25
|
||||
max_lifetime = "300s"
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
[app]
|
||||
# Interface and port where the app will run its webserver.
|
||||
address = "0.0.0.0:9000"
|
||||
# Interface and port where the app will run its webserver. The default value
|
||||
# of localhost will only listen to connections from the current machine. To
|
||||
# listen on all interfaces use '0.0.0.0'. To listen on the default web address
|
||||
# port, use port 80 (this will require running with elevated permissions).
|
||||
address = "localhost:9000"
|
||||
|
||||
# BasicAuth authentication for the admin dashboard. This will eventually
|
||||
# be replaced with a better multi-user, role-based authentication system.
|
||||
# IMPORTANT: Leave both values empty to disable authentication on admin
|
||||
# only where an external authentication is already setup.
|
||||
admin_username = "listmonk"
|
||||
admin_password = "listmonk"
|
||||
# BasicAuth authentication for the admin dashboard. This will eventually
|
||||
# be replaced with a better multi-user, role-based authentication system.
|
||||
# IMPORTANT: Leave both values empty to disable authentication on admin
|
||||
# only where an external authentication is already setup.
|
||||
admin_username = "listmonk"
|
||||
admin_password = "listmonk"
|
||||
|
||||
# Database.
|
||||
[db]
|
||||
host = "db"
|
||||
port = 5432
|
||||
user = "listmonk"
|
||||
password = "listmonk"
|
||||
database = "listmonk"
|
||||
ssl_mode = "disable"
|
||||
max_open = 25
|
||||
max_idle = 25
|
||||
max_lifetime = "300s"
|
||||
host = "localhost"
|
||||
port = 5432
|
||||
user = "listmonk"
|
||||
password = "listmonk"
|
||||
database = "listmonk"
|
||||
ssl_mode = "disable"
|
||||
max_open = 25
|
||||
max_idle = 25
|
||||
max_lifetime = "300s"
|
||||
|
|
1
frontend/README.md
vendored
1
frontend/README.md
vendored
|
@ -12,6 +12,7 @@ In `main.js`, Buefy and vue-i18n are attached globally. In addition:
|
|||
|
||||
Some constants are defined in `constants.js`.
|
||||
|
||||
|
||||
## APIs and states
|
||||
The project uses a global `vuex` state to centrally store the responses to pretty much all APIs (eg: fetch lists, campaigns etc.) except for a few exceptions. These are called `models` and have been defined in `constants.js`. The definitions are in `store/index.js`.
|
||||
|
||||
|
|
8
frontend/cypress.json
vendored
Normal file
8
frontend/cypress.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"baseUrl": "http://localhost:8080",
|
||||
"env": {
|
||||
"server_init_command": "pkill -9 listmonk | cd ../ && ./listmonk --install --yes && ./listmonk > /dev/null 2>/dev/null &",
|
||||
"username": "listmonk",
|
||||
"password": "listmonk"
|
||||
}
|
||||
}
|
28
frontend/cypress/downloads/data.json
Normal file
28
frontend/cypress/downloads/data.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"profile": [
|
||||
{
|
||||
"id": 2,
|
||||
"uuid": "0954ba2e-50e4-4847-86f4-c2b8b72dace8",
|
||||
"email": "anon@example.com",
|
||||
"name": "Anon Doe",
|
||||
"attribs": {
|
||||
"city": "Bengaluru",
|
||||
"good": true,
|
||||
"type": "unknown"
|
||||
},
|
||||
"status": "enabled",
|
||||
"created_at": "2021-02-20T15:52:16.251648+05:30",
|
||||
"updated_at": "2021-02-20T15:52:16.251648+05:30"
|
||||
}
|
||||
],
|
||||
"subscriptions": [
|
||||
{
|
||||
"subscription_status": "unconfirmed",
|
||||
"name": "Opt-in list",
|
||||
"type": "public",
|
||||
"created_at": "2021-02-20T15:52:16.251648+05:30"
|
||||
}
|
||||
],
|
||||
"campaign_views": [],
|
||||
"link_clicks": []
|
||||
}
|
101
frontend/cypress/fixtures/subs.csv
Normal file
101
frontend/cypress/fixtures/subs.csv
Normal file
|
@ -0,0 +1,101 @@
|
|||
email,name,attributes
|
||||
user0@mail.com,First0 Last0,"{""age"": 29, ""city"": ""Bangalore"", ""clientId"": ""DAXX79""}"
|
||||
user1@mail.com,First1 Last1,"{""age"": 43, ""city"": ""Bangalore"", ""clientId"": ""DAXX71""}"
|
||||
user2@mail.com,First2 Last2,"{""age"": 47, ""city"": ""Bangalore"", ""clientId"": ""DAXX70""}"
|
||||
user3@mail.com,First3 Last3,"{""age"": 67, ""city"": ""Bangalore"", ""clientId"": ""DAXX32""}"
|
||||
user4@mail.com,First4 Last4,"{""age"": 63, ""city"": ""Bangalore"", ""clientId"": ""DAXX30""}"
|
||||
user5@mail.com,First5 Last5,"{""age"": 69, ""city"": ""Bangalore"", ""clientId"": ""DAXX64""}"
|
||||
user6@mail.com,First6 Last6,"{""age"": 68, ""city"": ""Bangalore"", ""clientId"": ""DAXX22""}"
|
||||
user7@mail.com,First7 Last7,"{""age"": 56, ""city"": ""Bangalore"", ""clientId"": ""DAXX54""}"
|
||||
user8@mail.com,First8 Last8,"{""age"": 58, ""city"": ""Bangalore"", ""clientId"": ""DAXX65""}"
|
||||
user9@mail.com,First9 Last9,"{""age"": 51, ""city"": ""Bangalore"", ""clientId"": ""DAXX66""}"
|
||||
user10@mail.com,First10 Last10,"{""age"": 53, ""city"": ""Bangalore"", ""clientId"": ""DAXX31""}"
|
||||
user11@mail.com,First11 Last11,"{""age"": 46, ""city"": ""Bangalore"", ""clientId"": ""DAXX59""}"
|
||||
user12@mail.com,First12 Last12,"{""age"": 41, ""city"": ""Bangalore"", ""clientId"": ""DAXX80""}"
|
||||
user13@mail.com,First13 Last13,"{""age"": 27, ""city"": ""Bangalore"", ""clientId"": ""DAXX96""}"
|
||||
user14@mail.com,First14 Last14,"{""age"": 51, ""city"": ""Bangalore"", ""clientId"": ""DAXX22""}"
|
||||
user15@mail.com,First15 Last15,"{""age"": 31, ""city"": ""Bangalore"", ""clientId"": ""DAXX97""}"
|
||||
user16@mail.com,First16 Last16,"{""age"": 59, ""city"": ""Bangalore"", ""clientId"": ""DAXX41""}"
|
||||
user17@mail.com,First17 Last17,"{""age"": 29, ""city"": ""Bangalore"", ""clientId"": ""DAXX93""}"
|
||||
user18@mail.com,First18 Last18,"{""age"": 39, ""city"": ""Bangalore"", ""clientId"": ""DAXX35""}"
|
||||
user19@mail.com,First19 Last19,"{""age"": 67, ""city"": ""Bangalore"", ""clientId"": ""DAXX21""}"
|
||||
user20@mail.com,First20 Last20,"{""age"": 66, ""city"": ""Bangalore"", ""clientId"": ""DAXX56""}"
|
||||
user21@mail.com,First21 Last21,"{""age"": 39, ""city"": ""Bangalore"", ""clientId"": ""DAXX26""}"
|
||||
user22@mail.com,First22 Last22,"{""age"": 44, ""city"": ""Bangalore"", ""clientId"": ""DAXX98""}"
|
||||
user23@mail.com,First23 Last23,"{""age"": 66, ""city"": ""Bangalore"", ""clientId"": ""DAXX64""}"
|
||||
user24@mail.com,First24 Last24,"{""age"": 48, ""city"": ""Bangalore"", ""clientId"": ""DAXX41""}"
|
||||
user25@mail.com,First25 Last25,"{""age"": 38, ""city"": ""Bangalore"", ""clientId"": ""DAXX80""}"
|
||||
user26@mail.com,First26 Last26,"{""age"": 27, ""city"": ""Bangalore"", ""clientId"": ""DAXX26""}"
|
||||
user27@mail.com,First27 Last27,"{""age"": 59, ""city"": ""Bangalore"", ""clientId"": ""DAXX55""}"
|
||||
user28@mail.com,First28 Last28,"{""age"": 49, ""city"": ""Bangalore"", ""clientId"": ""DAXX45""}"
|
||||
user29@mail.com,First29 Last29,"{""age"": 45, ""city"": ""Bangalore"", ""clientId"": ""DAXX74""}"
|
||||
user30@mail.com,First30 Last30,"{""age"": 47, ""city"": ""Bangalore"", ""clientId"": ""DAXX27""}"
|
||||
user31@mail.com,First31 Last31,"{""age"": 21, ""city"": ""Bangalore"", ""clientId"": ""DAXX37""}"
|
||||
user32@mail.com,First32 Last32,"{""age"": 21, ""city"": ""Bangalore"", ""clientId"": ""DAXX50""}"
|
||||
user33@mail.com,First33 Last33,"{""age"": 70, ""city"": ""Bangalore"", ""clientId"": ""DAXX29""}"
|
||||
user34@mail.com,First34 Last34,"{""age"": 59, ""city"": ""Bangalore"", ""clientId"": ""DAXX95""}"
|
||||
user35@mail.com,First35 Last35,"{""age"": 36, ""city"": ""Bangalore"", ""clientId"": ""DAXX79""}"
|
||||
user36@mail.com,First36 Last36,"{""age"": 47, ""city"": ""Bangalore"", ""clientId"": ""DAXX30""}"
|
||||
user37@mail.com,First37 Last37,"{""age"": 36, ""city"": ""Bangalore"", ""clientId"": ""DAXX92""}"
|
||||
user38@mail.com,First38 Last38,"{""age"": 29, ""city"": ""Bangalore"", ""clientId"": ""DAXX48""}"
|
||||
user39@mail.com,First39 Last39,"{""age"": 23, ""city"": ""Bangalore"", ""clientId"": ""DAXX12""}"
|
||||
user40@mail.com,First40 Last40,"{""age"": 39, ""city"": ""Bangalore"", ""clientId"": ""DAXX40""}"
|
||||
user41@mail.com,First41 Last41,"{""age"": 41, ""city"": ""Bangalore"", ""clientId"": ""DAXX51""}"
|
||||
user42@mail.com,First42 Last42,"{""age"": 22, ""city"": ""Bangalore"", ""clientId"": ""DAXX49""}"
|
||||
user43@mail.com,First43 Last43,"{""age"": 68, ""city"": ""Bangalore"", ""clientId"": ""DAXX58""}"
|
||||
user44@mail.com,First44 Last44,"{""age"": 45, ""city"": ""Bangalore"", ""clientId"": ""DAXX15""}"
|
||||
user45@mail.com,First45 Last45,"{""age"": 44, ""city"": ""Bangalore"", ""clientId"": ""DAXX75""}"
|
||||
user46@mail.com,First46 Last46,"{""age"": 42, ""city"": ""Bangalore"", ""clientId"": ""DAXX99""}"
|
||||
user47@mail.com,First47 Last47,"{""age"": 61, ""city"": ""Bangalore"", ""clientId"": ""DAXX39""}"
|
||||
user48@mail.com,First48 Last48,"{""age"": 57, ""city"": ""Bangalore"", ""clientId"": ""DAXX13""}"
|
||||
user49@mail.com,First49 Last49,"{""age"": 28, ""city"": ""Bangalore"", ""clientId"": ""DAXX97""}"
|
||||
user50@mail.com,First50 Last50,"{""age"": 61, ""city"": ""Bangalore"", ""clientId"": ""DAXX75""}"
|
||||
user51@mail.com,First51 Last51,"{""age"": 27, ""city"": ""Bangalore"", ""clientId"": ""DAXX55""}"
|
||||
user52@mail.com,First52 Last52,"{""age"": 62, ""city"": ""Bangalore"", ""clientId"": ""DAXX35""}"
|
||||
user53@mail.com,First53 Last53,"{""age"": 24, ""city"": ""Bangalore"", ""clientId"": ""DAXX67""}"
|
||||
user54@mail.com,First54 Last54,"{""age"": 25, ""city"": ""Bangalore"", ""clientId"": ""DAXX36""}"
|
||||
user55@mail.com,First55 Last55,"{""age"": 39, ""city"": ""Bangalore"", ""clientId"": ""DAXX74""}"
|
||||
user56@mail.com,First56 Last56,"{""age"": 53, ""city"": ""Bangalore"", ""clientId"": ""DAXX28""}"
|
||||
user57@mail.com,First57 Last57,"{""age"": 32, ""city"": ""Bangalore"", ""clientId"": ""DAXX36""}"
|
||||
user58@mail.com,First58 Last58,"{""age"": 64, ""city"": ""Bangalore"", ""clientId"": ""DAXX44""}"
|
||||
user59@mail.com,First59 Last59,"{""age"": 47, ""city"": ""Bangalore"", ""clientId"": ""DAXX65""}"
|
||||
user60@mail.com,First60 Last60,"{""age"": 62, ""city"": ""Bangalore"", ""clientId"": ""DAXX11""}"
|
||||
user61@mail.com,First61 Last61,"{""age"": 24, ""city"": ""Bangalore"", ""clientId"": ""DAXX55""}"
|
||||
user62@mail.com,First62 Last62,"{""age"": 61, ""city"": ""Bangalore"", ""clientId"": ""DAXX49""}"
|
||||
user63@mail.com,First63 Last63,"{""age"": 52, ""city"": ""Bangalore"", ""clientId"": ""DAXX83""}"
|
||||
user64@mail.com,First64 Last64,"{""age"": 38, ""city"": ""Bangalore"", ""clientId"": ""DAXX16""}"
|
||||
user65@mail.com,First65 Last65,"{""age"": 48, ""city"": ""Bangalore"", ""clientId"": ""DAXX54""}"
|
||||
user66@mail.com,First66 Last66,"{""age"": 35, ""city"": ""Bangalore"", ""clientId"": ""DAXX74""}"
|
||||
user67@mail.com,First67 Last67,"{""age"": 70, ""city"": ""Bangalore"", ""clientId"": ""DAXX22""}"
|
||||
user68@mail.com,First68 Last68,"{""age"": 21, ""city"": ""Bangalore"", ""clientId"": ""DAXX98""}"
|
||||
user69@mail.com,First69 Last69,"{""age"": 46, ""city"": ""Bangalore"", ""clientId"": ""DAXX24""}"
|
||||
user70@mail.com,First70 Last70,"{""age"": 58, ""city"": ""Bangalore"", ""clientId"": ""DAXX75""}"
|
||||
user71@mail.com,First71 Last71,"{""age"": 50, ""city"": ""Bangalore"", ""clientId"": ""DAXX57""}"
|
||||
user72@mail.com,First72 Last72,"{""age"": 63, ""city"": ""Bangalore"", ""clientId"": ""DAXX30""}"
|
||||
user73@mail.com,First73 Last73,"{""age"": 54, ""city"": ""Bangalore"", ""clientId"": ""DAXX77""}"
|
||||
user74@mail.com,First74 Last74,"{""age"": 67, ""city"": ""Bangalore"", ""clientId"": ""DAXX91""}"
|
||||
user75@mail.com,First75 Last75,"{""age"": 61, ""city"": ""Bangalore"", ""clientId"": ""DAXX30""}"
|
||||
user76@mail.com,First76 Last76,"{""age"": 50, ""city"": ""Bangalore"", ""clientId"": ""DAXX28""}"
|
||||
user77@mail.com,First77 Last77,"{""age"": 62, ""city"": ""Bangalore"", ""clientId"": ""DAXX41""}"
|
||||
user78@mail.com,First78 Last78,"{""age"": 66, ""city"": ""Bangalore"", ""clientId"": ""DAXX18""}"
|
||||
user79@mail.com,First79 Last79,"{""age"": 40, ""city"": ""Bangalore"", ""clientId"": ""DAXX89""}"
|
||||
user80@mail.com,First80 Last80,"{""age"": 21, ""city"": ""Bangalore"", ""clientId"": ""DAXX72""}"
|
||||
user81@mail.com,First81 Last81,"{""age"": 43, ""city"": ""Bangalore"", ""clientId"": ""DAXX31""}"
|
||||
user82@mail.com,First82 Last82,"{""age"": 33, ""city"": ""Bangalore"", ""clientId"": ""DAXX89""}"
|
||||
user83@mail.com,First83 Last83,"{""age"": 38, ""city"": ""Bangalore"", ""clientId"": ""DAXX88""}"
|
||||
user84@mail.com,First84 Last84,"{""age"": 24, ""city"": ""Bangalore"", ""clientId"": ""DAXX77""}"
|
||||
user85@mail.com,First85 Last85,"{""age"": 27, ""city"": ""Bangalore"", ""clientId"": ""DAXX40""}"
|
||||
user86@mail.com,First86 Last86,"{""age"": 67, ""city"": ""Bangalore"", ""clientId"": ""DAXX46""}"
|
||||
user87@mail.com,First87 Last87,"{""age"": 20, ""city"": ""Bangalore"", ""clientId"": ""DAXX53""}"
|
||||
user88@mail.com,First88 Last88,"{""age"": 45, ""city"": ""Bangalore"", ""clientId"": ""DAXX79""}"
|
||||
user89@mail.com,First89 Last89,"{""age"": 31, ""city"": ""Bangalore"", ""clientId"": ""DAXX11""}"
|
||||
user90@mail.com,First90 Last90,"{""age"": 51, ""city"": ""Bangalore"", ""clientId"": ""DAXX71""}"
|
||||
user91@mail.com,First91 Last91,"{""age"": 49, ""city"": ""Bangalore"", ""clientId"": ""DAXX20""}"
|
||||
user92@mail.com,First92 Last92,"{""age"": 26, ""city"": ""Bangalore"", ""clientId"": ""DAXX20""}"
|
||||
user93@mail.com,First93 Last93,"{""age"": 67, ""city"": ""Bangalore"", ""clientId"": ""DAXX64""}"
|
||||
user94@mail.com,First94 Last94,"{""age"": 60, ""city"": ""Bangalore"", ""clientId"": ""DAXX53""}"
|
||||
user95@mail.com,First95 Last95,"{""age"": 64, ""city"": ""Bangalore"", ""clientId"": ""DAXX91""}"
|
||||
user96@mail.com,First96 Last96,"{""age"": 27, ""city"": ""Bangalore"", ""clientId"": ""DAXX53""}"
|
||||
user97@mail.com,First97 Last97,"{""age"": 29, ""city"": ""Bangalore"", ""clientId"": ""DAXX46""}"
|
||||
user98@mail.com,First98 Last98,"{""age"": 26, ""city"": ""Bangalore"", ""clientId"": ""DAXX49""}"
|
||||
user99@mail.com,First99 Last99,"{""age"": 49, ""city"": ""Bangalore"", ""clientId"": ""DAXX26""}"
|
|
211
frontend/cypress/integration/campaigns.js
Normal file
211
frontend/cypress/integration/campaigns.js
Normal file
|
@ -0,0 +1,211 @@
|
|||
describe('Subscribers', () => {
|
||||
it('Opens campaigns page', () => {
|
||||
cy.resetDB();
|
||||
cy.loginAndVisit('/campaigns');
|
||||
});
|
||||
|
||||
|
||||
it('Counts campaigns', () => {
|
||||
cy.get('tbody td[data-label=Status]').should('have.length', 1);
|
||||
});
|
||||
|
||||
it('Edits campaign', () => {
|
||||
cy.get('td[data-label=Status] a').click();
|
||||
|
||||
// Fill fields.
|
||||
cy.get('input[name=name]').clear().type('new-name');
|
||||
cy.get('input[name=subject]').clear().type('new-subject');
|
||||
cy.get('input[name=from_email]').clear().type('new <from@email>');
|
||||
|
||||
// Change the list.
|
||||
cy.get('.list-selector a.delete').click();
|
||||
cy.get('.list-selector input').click();
|
||||
cy.get('.list-selector .autocomplete a').eq(1).click();
|
||||
|
||||
// Clear and redo tags.
|
||||
cy.get('input[name=tags]').type('{backspace}new-tag{enter}');
|
||||
|
||||
// Enable schedule.
|
||||
cy.get('[data-cy=btn-send-later] .check').click();
|
||||
cy.get('.datepicker input').click();
|
||||
cy.get('.datepicker-header .control:nth-child(2) select').select((new Date().getFullYear() + 1).toString());
|
||||
cy.get('.datepicker-body a.is-selectable:first').click();
|
||||
cy.get('body').click(1, 1);
|
||||
|
||||
// Switch to content tab.
|
||||
cy.get('.b-tabs nav a').eq(1).click();
|
||||
|
||||
// Switch format to plain text.
|
||||
cy.get('label[data-cy=check-plain]').click();
|
||||
cy.get('.modal button.is-primary').click();
|
||||
|
||||
// Enter body value.
|
||||
cy.get('textarea[name=content]').clear().type('new-content');
|
||||
cy.get('button[data-cy=btn-save]').click();
|
||||
|
||||
// Schedule.
|
||||
cy.get('button[data-cy=btn-schedule]').click();
|
||||
cy.get('.modal button.is-primary').click();
|
||||
|
||||
cy.wait(250);
|
||||
|
||||
// Verify the changes.
|
||||
cy.request('/api/campaigns/1').should((response) => {
|
||||
const { data } = response.body;
|
||||
expect(data.status).to.equal('scheduled');
|
||||
expect(data.name).to.equal('new-name');
|
||||
expect(data.subject).to.equal('new-subject');
|
||||
expect(data.content_type).to.equal('plain');
|
||||
expect(data.altbody).to.equal(null);
|
||||
expect(data.send_at).to.not.equal(null);
|
||||
expect(data.body).to.equal('new-content');
|
||||
|
||||
expect(data.lists.length).to.equal(1);
|
||||
expect(data.lists[0].id).to.equal(2);
|
||||
expect(data.tags.length).to.equal(1);
|
||||
expect(data.tags[0]).to.equal('new-tag');
|
||||
});
|
||||
|
||||
cy.get('tbody td[data-label=Status] .tag.scheduled');
|
||||
});
|
||||
|
||||
it('Clones campaign', () => {
|
||||
for (let n = 0; n < 3; n++) {
|
||||
// Clone the campaign.
|
||||
cy.get('[data-cy=btn-clone]').first().click();
|
||||
cy.get('.modal input').clear().type(`clone${n}`).click();
|
||||
cy.get('.modal button.is-primary').click();
|
||||
cy.wait(250);
|
||||
cy.clickMenu('all-campaigns');
|
||||
cy.wait(100);
|
||||
|
||||
// Verify the newly created row.
|
||||
cy.get('tbody td[data-label="Name"]').first().contains(`clone${n}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('Searches campaigns', () => {
|
||||
cy.get('input[name=query]').clear().type('clone2{enter}');
|
||||
cy.get('tbody tr').its('length').should('eq', 1);
|
||||
cy.get('tbody td[data-label="Name"]').first().contains('clone2');
|
||||
cy.get('input[name=query]').clear().type('{enter}');
|
||||
});
|
||||
|
||||
|
||||
it('Deletes campaign', () => {
|
||||
// Delete all visible lists.
|
||||
cy.get('tbody tr').each(() => {
|
||||
cy.get('tbody a[data-cy=btn-delete]').first().click();
|
||||
cy.get('.modal button.is-primary').click();
|
||||
});
|
||||
|
||||
// Confirm deletion.
|
||||
cy.get('table tr.is-empty');
|
||||
});
|
||||
|
||||
|
||||
it('Adds new campaigns', () => {
|
||||
const lists = [[1], [1, 2]];
|
||||
const cTypes = ['richtext', 'html', 'plain'];
|
||||
|
||||
let n = 0;
|
||||
cTypes.forEach((c) => {
|
||||
lists.forEach((l) => {
|
||||
// Click the 'new button'
|
||||
cy.get('[data-cy=btn-new]').click();
|
||||
cy.wait(100);
|
||||
|
||||
// Fill fields.
|
||||
cy.get('input[name=name]').clear().type(`name${n}`);
|
||||
cy.get('input[name=subject]').clear().type(`subject${n}`);
|
||||
|
||||
l.forEach(() => {
|
||||
cy.get('.list-selector input').click();
|
||||
cy.get('.list-selector .autocomplete a').first().click();
|
||||
});
|
||||
|
||||
// Add tags.
|
||||
for (let i = 0; i < 3; i++) {
|
||||
cy.get('input[name=tags]').type(`tag${i}{enter}`);
|
||||
}
|
||||
|
||||
// Hit 'Continue'.
|
||||
cy.get('button[data-cy=btn-continue]').click();
|
||||
cy.wait(250);
|
||||
|
||||
// Insert content.
|
||||
cy.get('.ql-editor').type(`hello${n} \{\{ .Subscriber.Name \}\}`, { parseSpecialCharSequences: false });
|
||||
cy.get('.ql-editor').type('{enter}');
|
||||
cy.get('.ql-editor').type('\{\{ .Subscriber.Attribs.city \}\}', { parseSpecialCharSequences: false });
|
||||
|
||||
// Select content type.
|
||||
cy.get(`label[data-cy=check-${c}]`).click();
|
||||
|
||||
// If it's not richtext, there's a "you'll lose formatting" prompt.
|
||||
if (c !== 'richtext') {
|
||||
cy.get('.modal button.is-primary').click();
|
||||
}
|
||||
|
||||
// Save.
|
||||
cy.get('button[data-cy=btn-save]').click();
|
||||
|
||||
cy.clickMenu('all-campaigns');
|
||||
cy.wait(250);
|
||||
|
||||
// Verify the newly created campaign in the table.
|
||||
cy.get('tbody td[data-label="Name"]').first().contains(`name${n}`);
|
||||
cy.get('tbody td[data-label="Name"]').first().contains(`subject${n}`);
|
||||
cy.get('tbody td[data-label="Lists"]').first().then(($el) => {
|
||||
cy.wrap($el).find('li').should('have.length', l.length);
|
||||
});
|
||||
|
||||
n++;
|
||||
});
|
||||
});
|
||||
|
||||
// Fetch the campaigns API and verfiy the values that couldn't be verified on the table UI.
|
||||
cy.request('/api/campaigns?order=asc&order_by=created_at').should((response) => {
|
||||
const { data } = response.body;
|
||||
expect(data.total).to.equal(lists.length * cTypes.length);
|
||||
|
||||
let n = 0;
|
||||
cTypes.forEach((c) => {
|
||||
lists.forEach((l) => {
|
||||
expect(data.results[n].content_type).to.equal(c);
|
||||
expect(data.results[n].lists.map((ls) => ls.id)).to.deep.equal(l);
|
||||
n++;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Starts and cancels campaigns', () => {
|
||||
for (let n = 1; n <= 2; n++) {
|
||||
cy.get(`tbody tr:nth-child(${n}) [data-cy=btn-start]`).click();
|
||||
cy.get('.modal button.is-primary').click();
|
||||
cy.wait(250);
|
||||
cy.get(`tbody tr:nth-child(${n}) td[data-label=Status] .tag.running`);
|
||||
|
||||
if (n > 1) {
|
||||
cy.get(`tbody tr:nth-child(${n}) [data-cy=btn-cancel]`).click();
|
||||
cy.get('.modal button.is-primary').click();
|
||||
cy.wait(250);
|
||||
cy.get(`tbody tr:nth-child(${n}) td[data-label=Status] .tag.cancelled`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('Sorts campaigns', () => {
|
||||
const asc = [5, 6, 7, 8, 9, 10];
|
||||
const desc = [10, 9, 8, 7, 6, 5];
|
||||
const cases = ['cy-name', 'cy-timestamp'];
|
||||
|
||||
cases.forEach((c) => {
|
||||
cy.sortTable(`thead th.${c}`, asc);
|
||||
cy.wait(250);
|
||||
cy.sortTable(`thead th.${c}`, desc);
|
||||
cy.wait(250);
|
||||
});
|
||||
});
|
||||
});
|
28
frontend/cypress/integration/dashboard.js
Normal file
28
frontend/cypress/integration/dashboard.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
describe('Dashboard', () => {
|
||||
it('Opens dashboard', () => {
|
||||
cy.loginAndVisit('/');
|
||||
|
||||
// List counts.
|
||||
cy.get('[data-cy=lists]')
|
||||
.should('contain', '2 Lists')
|
||||
.and('contain', '1 Public')
|
||||
.and('contain', '1 Private')
|
||||
.and('contain', '1 Single opt-in')
|
||||
.and('contain', '1 Double opt-in');
|
||||
|
||||
// Campaign counts.
|
||||
cy.get('[data-cy=campaigns]')
|
||||
.should('contain', '1 Campaign')
|
||||
.and('contain', '1 draft');
|
||||
|
||||
// Subscriber counts.
|
||||
cy.get('[data-cy=subscribers]')
|
||||
.should('contain', '2 Subscribers')
|
||||
.and('contain', '0 Blocklisted')
|
||||
.and('contain', '0 Orphans');
|
||||
|
||||
// Message count.
|
||||
cy.get('[data-cy=messages]')
|
||||
.should('contain', '0 Messages sent');
|
||||
});
|
||||
});
|
68
frontend/cypress/integration/forms.js
Normal file
68
frontend/cypress/integration/forms.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
describe('Forms', () => {
|
||||
it('Opens forms page', () => {
|
||||
cy.resetDB();
|
||||
cy.loginAndVisit('/lists/forms');
|
||||
});
|
||||
|
||||
it('Checks form URL', () => {
|
||||
cy.get('a[data-cy=url]').contains('http://localhost:9000');
|
||||
});
|
||||
|
||||
it('Checks public lists', () => {
|
||||
cy.get('ul[data-cy=lists] li')
|
||||
.should('contain', 'Opt-in list')
|
||||
.its('length')
|
||||
.should('eq', 1);
|
||||
|
||||
cy.get('[data-cy=form] pre').should('not.exist');
|
||||
});
|
||||
|
||||
it('Selects public list', () => {
|
||||
// Click the list checkbox.
|
||||
cy.get('ul[data-cy=lists] .checkbox').click();
|
||||
|
||||
// Make sure the <pre> form HTML has appeared.
|
||||
cy.get('[data-cy=form] pre').then(($pre) => {
|
||||
// Check that the ID of the list in the checkbox appears in the HTML.
|
||||
cy.get('ul[data-cy=lists] input').then(($inp) => {
|
||||
cy.wrap($pre).contains($inp.val());
|
||||
});
|
||||
});
|
||||
|
||||
// Click the list checkbox.
|
||||
cy.get('ul[data-cy=lists] .checkbox').click();
|
||||
cy.get('[data-cy=form] pre').should('not.exist');
|
||||
});
|
||||
|
||||
it('Subscribes from public form page', () => {
|
||||
// Create a public test list.
|
||||
cy.request('POST', '/api/lists', { name: 'test-list', type: 'public', optin: 'single' });
|
||||
|
||||
// Open the public page and subscribe to alternating lists multiple times.
|
||||
// There should be no errors and two new subscribers should be subscribed to two lists.
|
||||
for (let i = 0; i < 2; i++) {
|
||||
for (let j = 0; j < 2; j++) {
|
||||
cy.loginAndVisit('/subscription/form');
|
||||
cy.get('input[name=email]').clear().type(`test${i}@test.com`);
|
||||
cy.get('input[name=name]').clear().type(`test${i}`);
|
||||
cy.get('input[type=checkbox]').eq(j).click();
|
||||
cy.get('button').click();
|
||||
cy.wait(250);
|
||||
cy.get('.wrap').contains(/has been sent|successfully/);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify form subscriptions.
|
||||
cy.request('/api/subscribers').should((response) => {
|
||||
const { data } = response.body;
|
||||
|
||||
// Two new + two dummy subscribers that are there by default.
|
||||
expect(data.total).to.equal(4);
|
||||
|
||||
// The two new subscribers should each have two list subscriptions.
|
||||
for (let i = 0; i < 2; i++) {
|
||||
expect(data.results.find((s) => s.email === `test${i}@test.com`).lists.length).to.equal(2);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
50
frontend/cypress/integration/import.js
Normal file
50
frontend/cypress/integration/import.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
|
||||
describe('Import', () => {
|
||||
it('Opens import page', () => {
|
||||
cy.resetDB();
|
||||
cy.loginAndVisit('/subscribers/import');
|
||||
});
|
||||
|
||||
it('Imports subscribers', () => {
|
||||
const cases = [
|
||||
{ mode: 'check-subscribe', status: 'enabled', count: 102 },
|
||||
{ mode: 'check-blocklist', status: 'blocklisted', count: 102 },
|
||||
];
|
||||
|
||||
cases.forEach((c) => {
|
||||
cy.get(`[data-cy=${c.mode}] .check`).click();
|
||||
|
||||
if (c.status === 'enabled') {
|
||||
cy.get('.list-selector input').click();
|
||||
cy.get('.list-selector .autocomplete a').first().click();
|
||||
}
|
||||
|
||||
cy.fixture('subs.csv').then((data) => {
|
||||
cy.get('input[type="file"]').attachFile({
|
||||
fileContent: data.toString(),
|
||||
fileName: 'subs.csv',
|
||||
mimeType: 'text/csv',
|
||||
});
|
||||
});
|
||||
|
||||
cy.get('button.is-primary').click();
|
||||
cy.get('section.wrap .has-text-success');
|
||||
cy.get('button.is-primary').click();
|
||||
cy.wait(100);
|
||||
|
||||
// Verify that 100 (+2 default) subs are imported.
|
||||
cy.loginAndVisit('/subscribers');
|
||||
cy.wait(100);
|
||||
cy.get('[data-cy=count]').then(($el) => {
|
||||
cy.expect(parseInt($el.text().trim())).to.equal(c.count);
|
||||
});
|
||||
|
||||
cy.get('tbody td[data-label=Status]').each(($el) => {
|
||||
cy.wrap($el).find(`.tag.${c.status}`);
|
||||
});
|
||||
|
||||
cy.loginAndVisit('/subscribers/import');
|
||||
cy.wait(100);
|
||||
});
|
||||
});
|
||||
});
|
130
frontend/cypress/integration/lists.js
Normal file
130
frontend/cypress/integration/lists.js
Normal file
|
@ -0,0 +1,130 @@
|
|||
describe('Lists', () => {
|
||||
it('Opens lists page', () => {
|
||||
cy.resetDB();
|
||||
cy.loginAndVisit('/lists');
|
||||
});
|
||||
|
||||
|
||||
it('Counts subscribers in default lists', () => {
|
||||
cy.get('tbody td[data-label=Subscribers]').contains('1');
|
||||
});
|
||||
|
||||
|
||||
it('Creates campaign for list', () => {
|
||||
cy.get('tbody a[data-cy=btn-campaign]').first().click();
|
||||
cy.location('pathname').should('contain', '/campaigns/new');
|
||||
cy.get('.list-tags .tag').contains('Default list');
|
||||
|
||||
cy.clickMenu('lists', 'all-lists');
|
||||
});
|
||||
|
||||
|
||||
it('Creates opt-in campaign for list', () => {
|
||||
cy.get('tbody a[data-cy=btn-send-optin-campaign]').click();
|
||||
cy.get('.modal button.is-primary').click();
|
||||
cy.location('pathname').should('contain', '/campaigns/2');
|
||||
|
||||
cy.clickMenu('lists', 'all-lists');
|
||||
});
|
||||
|
||||
|
||||
it('Checks individual subscribers in lists', () => {
|
||||
const subs = [{ listID: 1, email: 'john@example.com' },
|
||||
{ listID: 2, email: 'anon@example.com' }];
|
||||
|
||||
// Click on each list on the lists page, go the the subscribers page
|
||||
// for that list, and check the subscriber details.
|
||||
subs.forEach((s, n) => {
|
||||
cy.get('tbody td[data-label=Subscribers] a').eq(n).click();
|
||||
cy.location('pathname').should('contain', `/subscribers/lists/${s.listID}`);
|
||||
cy.get('tbody tr').its('length').should('eq', 1);
|
||||
cy.get('tbody td[data-label="E-mail"]').contains(s.email);
|
||||
cy.clickMenu('lists', 'all-lists');
|
||||
});
|
||||
});
|
||||
|
||||
it('Edits lists', () => {
|
||||
// Open the edit popup and edit the default lists.
|
||||
cy.get('[data-cy=btn-edit]').each(($el, n) => {
|
||||
cy.wrap($el).click();
|
||||
cy.get('input[name=name]').clear().type(`list-${n}`);
|
||||
cy.get('select[name=type]').select('public');
|
||||
cy.get('select[name=optin]').select('double');
|
||||
cy.get('input[name=tags]').clear().type(`tag${n}`);
|
||||
cy.get('button[type=submit]').click();
|
||||
});
|
||||
cy.wait(250);
|
||||
|
||||
// Confirm the edits.
|
||||
cy.get('tbody tr').each(($el, n) => {
|
||||
cy.wrap($el).find('td[data-label=Name]').contains(`list-${n}`);
|
||||
cy.wrap($el).find('.tags')
|
||||
.should('contain', 'test')
|
||||
.and('contain', `tag${n}`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('Deletes lists', () => {
|
||||
// Delete all visible lists.
|
||||
cy.get('tbody tr').each(() => {
|
||||
cy.get('tbody a[data-cy=btn-delete]').first().click();
|
||||
cy.get('.modal button.is-primary').click();
|
||||
});
|
||||
|
||||
// Confirm deletion.
|
||||
cy.get('table tr.is-empty');
|
||||
});
|
||||
|
||||
|
||||
// Add new lists.
|
||||
it('Adds new lists', () => {
|
||||
// Open the list form and create lists of multiple type/optin combinations.
|
||||
const types = ['private', 'public'];
|
||||
const optin = ['single', 'double'];
|
||||
|
||||
let n = 0;
|
||||
types.forEach((t) => {
|
||||
optin.forEach((o) => {
|
||||
const name = `list-${t}-${o}-${n}`;
|
||||
|
||||
cy.get('[data-cy=btn-new]').click();
|
||||
cy.get('input[name=name]').type(name);
|
||||
cy.get('select[name=type]').select(t);
|
||||
cy.get('select[name=optin]').select(o);
|
||||
cy.get('input[name=tags]').type(`tag${n}{enter}${t}{enter}${o}{enter}`);
|
||||
cy.get('button[type=submit]').click();
|
||||
|
||||
// Confirm the addition by inspecting the newly created list row.
|
||||
const tr = `tbody tr:nth-child(${n + 1})`;
|
||||
cy.get(`${tr} td[data-label=Name]`).contains(name);
|
||||
cy.get(`${tr} td[data-label=Type] [data-cy=type-${t}]`);
|
||||
cy.get(`${tr} td[data-label=Type] [data-cy=optin-${o}]`);
|
||||
cy.get(`${tr} .tags`)
|
||||
.should('contain', `tag${n}`)
|
||||
.and('contain', t)
|
||||
.and('contain', o);
|
||||
|
||||
n++;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Sort lists by clicking on various headers. At this point, there should be four
|
||||
// lists with IDs = [3, 4, 5, 6]. Sort the items be columns and match them with
|
||||
// the expected order of IDs.
|
||||
it('Sorts lists', () => {
|
||||
cy.sortTable('thead th.cy-name', [4, 3, 6, 5]);
|
||||
cy.sortTable('thead th.cy-name', [5, 6, 3, 4]);
|
||||
|
||||
cy.sortTable('thead th.cy-type', [5, 6, 4, 3]);
|
||||
cy.sortTable('thead th.cy-type', [4, 3, 5, 6]);
|
||||
|
||||
cy.sortTable('thead th.cy-created_at', [3, 4, 5, 6]);
|
||||
cy.sortTable('thead th.cy-created_at', [6, 5, 4, 3]);
|
||||
|
||||
cy.sortTable('thead th.cy-updated_at', [3, 4, 5, 6]);
|
||||
cy.sortTable('thead th.cy-updated_at', [6, 5, 4, 3]);
|
||||
});
|
||||
});
|
40
frontend/cypress/integration/settings.js
Normal file
40
frontend/cypress/integration/settings.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
describe('Templates', () => {
|
||||
it('Opens settings page', () => {
|
||||
cy.resetDB();
|
||||
cy.loginAndVisit('/settings');
|
||||
});
|
||||
|
||||
it('Changes some settings', () => {
|
||||
const rootURL = 'http://127.0.0.1:9000';
|
||||
const faveURL = 'http://127.0.0.1:9000/public/static/logo.png';
|
||||
|
||||
cy.get('input[name="app.root_url"]').clear().type(rootURL);
|
||||
cy.get('input[name="app.favicon_url"]').type(faveURL);
|
||||
cy.get('.b-tabs nav a').eq(1).click();
|
||||
cy.get('.tab-item:visible').find('.field').first()
|
||||
.find('button')
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// Enable / disable SMTP and delete one.
|
||||
cy.get('.b-tabs nav a').eq(4).click();
|
||||
cy.get('.tab-item:visible [data-cy=btn-enable-smtp]').eq(1).click();
|
||||
cy.get('.tab-item:visible [data-cy=btn-delete-smtp]').first().click();
|
||||
cy.get('.modal button.is-primary').click();
|
||||
|
||||
cy.get('[data-cy=btn-save]').click();
|
||||
|
||||
cy.wait(250);
|
||||
|
||||
// Verify the changes.
|
||||
cy.request('/api/settings').should((response) => {
|
||||
const { data } = response.body;
|
||||
expect(data['app.root_url']).to.equal(rootURL);
|
||||
expect(data['app.favicon_url']).to.equal(faveURL);
|
||||
expect(data['app.concurrency']).to.equal(9);
|
||||
|
||||
expect(data.smtp.length).to.equal(1);
|
||||
expect(data.smtp[0].enabled).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
219
frontend/cypress/integration/subscribers.js
Normal file
219
frontend/cypress/integration/subscribers.js
Normal file
|
@ -0,0 +1,219 @@
|
|||
describe('Subscribers', () => {
|
||||
it('Opens subscribers page', () => {
|
||||
cy.resetDB();
|
||||
cy.loginAndVisit('/subscribers');
|
||||
});
|
||||
|
||||
|
||||
it('Counts subscribers', () => {
|
||||
cy.get('tbody td[data-label=Status]').its('length').should('eq', 2);
|
||||
});
|
||||
|
||||
|
||||
it('Searches subscribers', () => {
|
||||
const cases = [
|
||||
{ value: 'john{enter}', count: 1, contains: 'john@example.com' },
|
||||
{ value: 'anon{enter}', count: 1, contains: 'anon@example.com' },
|
||||
{ value: '{enter}', count: 2, contains: null },
|
||||
];
|
||||
|
||||
cases.forEach((c) => {
|
||||
cy.get('[data-cy=search]').clear().type(c.value);
|
||||
cy.get('tbody td[data-label=Status]').its('length').should('eq', c.count);
|
||||
if (c.contains) {
|
||||
cy.get('tbody td[data-label=E-mail]').contains(c.contains);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('Advanced searches subscribers', () => {
|
||||
cy.get('[data-cy=btn-advanced-search]').click();
|
||||
|
||||
const cases = [
|
||||
{ value: 'subscribers.attribs->>\'city\'=\'Bengaluru\'', count: 2 },
|
||||
{ value: 'subscribers.attribs->>\'city\'=\'Bengaluru\' AND id=1', count: 1 },
|
||||
{ value: '(subscribers.attribs->>\'good\')::BOOLEAN = true AND name like \'Anon%\'', count: 1 },
|
||||
];
|
||||
|
||||
cases.forEach((c) => {
|
||||
cy.get('[data-cy=query]').clear().type(c.value);
|
||||
cy.get('[data-cy=btn-query]').click();
|
||||
cy.get('tbody td[data-label=Status]').its('length').should('eq', c.count);
|
||||
});
|
||||
|
||||
cy.get('[data-cy=btn-query-reset]').click();
|
||||
cy.get('tbody td[data-label=Status]').its('length').should('eq', 2);
|
||||
});
|
||||
|
||||
|
||||
it('Does bulk subscriber list add and remove', () => {
|
||||
const cases = [
|
||||
// radio: action to perform, rows: table rows to select and perform on: [expected statuses of those rows after thea action]
|
||||
{ radio: 'check-list-add', lists: [0, 1], rows: { 0: ['unconfirmed', 'unconfirmed'] } },
|
||||
{ radio: 'check-list-unsubscribe', lists: [0, 1], rows: { 0: ['unsubscribed', 'unsubscribed'], 1: ['unsubscribed'] } },
|
||||
{ radio: 'check-list-remove', lists: [0, 1], rows: { 1: [] } },
|
||||
{ radio: 'check-list-add', lists: [0, 1], rows: { 0: ['unsubscribed', 'unsubscribed'], 1: ['unconfirmed', 'unconfirmed'] } },
|
||||
{ radio: 'check-list-remove', lists: [0], rows: { 0: ['unsubscribed'] } },
|
||||
{ radio: 'check-list-add', lists: [0], rows: { 0: ['unconfirmed', 'unsubscribed'] } },
|
||||
];
|
||||
|
||||
|
||||
cases.forEach((c, n) => {
|
||||
// Select one of the 2 subscribers in the table.
|
||||
Object.keys(c.rows).forEach((r) => {
|
||||
cy.get('tbody td.checkbox-cell .checkbox').eq(r).click();
|
||||
});
|
||||
|
||||
// Open the 'manage lists' modal.
|
||||
cy.get('[data-cy=btn-manage-lists]').click();
|
||||
|
||||
// Check both lists in the modal.
|
||||
c.lists.forEach((l) => {
|
||||
cy.get('.list-selector input').click();
|
||||
cy.get('.list-selector .autocomplete a').first().click();
|
||||
});
|
||||
|
||||
// Select the radio option in the modal.
|
||||
cy.get(`[data-cy=${c.radio}] .check`).click();
|
||||
|
||||
// Save.
|
||||
cy.get('.modal button.is-primary').click();
|
||||
|
||||
// Check the status of the lists on the subscriber.
|
||||
Object.keys(c.rows).forEach((r) => {
|
||||
cy.get('tbody td[data-label=E-mail]').eq(r).find('.tags').then(($el) => {
|
||||
cy.wrap($el).find('.tag').should('have.length', c.rows[r].length);
|
||||
c.rows[r].forEach((status, n) => {
|
||||
// eg: .tag(n).unconfirmed
|
||||
cy.wrap($el).find('.tag').eq(n).should('have.class', status);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Resets subscribers page', () => {
|
||||
cy.resetDB();
|
||||
cy.loginAndVisit('/subscribers');
|
||||
});
|
||||
|
||||
|
||||
it('Edits subscribers', () => {
|
||||
const status = ['enabled', 'blocklisted'];
|
||||
const json = '{"string": "hello", "ints": [1,2,3], "null": null, "sub": {"bool": true}}';
|
||||
|
||||
// Collect values being edited on each sub to confirm the changes in the next step
|
||||
// index by their ID shown in the modal.
|
||||
const rows = {};
|
||||
|
||||
// Open the edit popup and edit the default lists.
|
||||
cy.get('[data-cy=btn-edit]').each(($el, n) => {
|
||||
const email = `email-${n}@email.com`;
|
||||
const name = `name-${n}`;
|
||||
|
||||
// Open the edit modal.
|
||||
cy.wrap($el).click();
|
||||
|
||||
// Get the ID from the header and proceed to fill the form.
|
||||
let id = 0;
|
||||
cy.get('[data-cy=id]').then(($el) => {
|
||||
id = $el.text();
|
||||
|
||||
cy.get('input[name=email]').clear().type(email);
|
||||
cy.get('input[name=name]').clear().type(name);
|
||||
cy.get('select[name=status]').select(status[n]);
|
||||
cy.get('.list-selector input').click();
|
||||
cy.get('.list-selector .autocomplete a').first().click();
|
||||
cy.get('textarea[name=attribs]').clear().type(json, { parseSpecialCharSequences: false, delay: 0 });
|
||||
cy.get('.modal-card-foot button[type=submit]').click();
|
||||
|
||||
rows[id] = { email, name, status: status[n] };
|
||||
});
|
||||
});
|
||||
|
||||
// Confirm the edits on the table.
|
||||
cy.wait(250);
|
||||
cy.get('tbody tr').each(($el) => {
|
||||
cy.wrap($el).find('td[data-id]').invoke('attr', 'data-id').then((id) => {
|
||||
cy.wrap($el).find('td[data-label=E-mail]').contains(rows[id].email);
|
||||
cy.wrap($el).find('td[data-label=Name]').contains(rows[id].name);
|
||||
cy.wrap($el).find('td[data-label=Status]').contains(rows[id].status, { matchCase: false });
|
||||
|
||||
// Both lists on the enabled sub should be 'unconfirmed' and the blocklisted one, 'unsubscribed.'
|
||||
cy.wrap($el).find(`.tags .${rows[id].status === 'enabled' ? 'unconfirmed' : 'unsubscribed'}`)
|
||||
.its('length').should('eq', 2);
|
||||
cy.wrap($el).find('td[data-label=Lists]').then((l) => {
|
||||
cy.expect(parseInt(l.text().trim())).to.equal(rows[id].status === 'blocklisted' ? 0 : 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Deletes subscribers', () => {
|
||||
// Delete all visible lists.
|
||||
cy.get('tbody tr').each(() => {
|
||||
cy.get('tbody a[data-cy=btn-delete]').first().click();
|
||||
cy.get('.modal button.is-primary').click();
|
||||
});
|
||||
|
||||
// Confirm deletion.
|
||||
cy.get('table tr.is-empty');
|
||||
});
|
||||
|
||||
|
||||
it('Creates new subscribers', () => {
|
||||
const statuses = ['enabled', 'blocklisted'];
|
||||
const lists = [[1], [2], [1, 2]];
|
||||
const json = '{"string": "hello", "ints": [1,2,3], "null": null, "sub": {"bool": true}}';
|
||||
|
||||
|
||||
// Cycle through each status and each list ID combination and create subscribers.
|
||||
const n = 0;
|
||||
for (let n = 0; n < 6; n++) {
|
||||
const email = `email-${n}@email.com`;
|
||||
const name = `name-${n}`;
|
||||
const status = statuses[(n + 1) % statuses.length];
|
||||
const list = lists[(n + 1) % lists.length];
|
||||
|
||||
cy.get('[data-cy=btn-new]').click();
|
||||
cy.get('input[name=email]').type(email);
|
||||
cy.get('input[name=name]').type(name);
|
||||
cy.get('select[name=status]').select(status);
|
||||
|
||||
list.forEach((l) => {
|
||||
cy.get('.list-selector input').click();
|
||||
cy.get('.list-selector .autocomplete a').first().click();
|
||||
});
|
||||
cy.get('textarea[name=attribs]').clear().type(json, { parseSpecialCharSequences: false, delay: 0 });
|
||||
cy.get('.modal-card-foot button[type=submit]').click();
|
||||
|
||||
// Confirm the addition by inspecting the newly created list row,
|
||||
// which is always the first row in the table.
|
||||
cy.wait(250);
|
||||
const tr = cy.get('tbody tr:nth-child(1)').then(($el) => {
|
||||
cy.wrap($el).find('td[data-label=E-mail]').contains(email);
|
||||
cy.wrap($el).find('td[data-label=Name]').contains(name);
|
||||
cy.wrap($el).find('td[data-label=Status]').contains(status, { matchCase: false });
|
||||
cy.wrap($el).find(`.tags .${status === 'enabled' ? 'unconfirmed' : 'unsubscribed'}`)
|
||||
.its('length').should('eq', list.length);
|
||||
cy.wrap($el).find('td[data-label=Lists]').then((l) => {
|
||||
cy.expect(parseInt(l.text().trim())).to.equal(status === 'blocklisted' ? 0 : list.length);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('Sorts subscribers', () => {
|
||||
const asc = [3, 4, 5, 6, 7, 8];
|
||||
const desc = [8, 7, 6, 5, 4, 3];
|
||||
const cases = ['cy-status', 'cy-email', 'cy-name', 'cy-created_at', 'cy-updated_at'];
|
||||
|
||||
cases.forEach((c) => {
|
||||
cy.sortTable(`thead th.${c}`, asc);
|
||||
cy.wait(100);
|
||||
cy.sortTable(`thead th.${c}`, desc);
|
||||
cy.wait(100);
|
||||
});
|
||||
});
|
||||
});
|
78
frontend/cypress/integration/templates.js
Normal file
78
frontend/cypress/integration/templates.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
describe('Templates', () => {
|
||||
it('Opens templates page', () => {
|
||||
cy.resetDB();
|
||||
cy.loginAndVisit('/campaigns/templates');
|
||||
});
|
||||
|
||||
|
||||
it('Counts default templates', () => {
|
||||
cy.get('tbody td[data-label=Name]').should('have.length', 1);
|
||||
});
|
||||
|
||||
it('Clones template', () => {
|
||||
// Clone the campaign.
|
||||
cy.get('[data-cy=btn-clone]').first().click();
|
||||
cy.get('.modal input').clear().type('cloned').click();
|
||||
cy.get('.modal button.is-primary').click();
|
||||
cy.wait(250);
|
||||
|
||||
// Verify the newly created row.
|
||||
cy.get('tbody td[data-label="Name"]').eq(1).contains('cloned');
|
||||
});
|
||||
|
||||
it('Edits template', () => {
|
||||
cy.get('tbody td.actions [data-cy=btn-edit]').first().click();
|
||||
cy.wait(250);
|
||||
cy.get('input[name=name]').clear().type('edited');
|
||||
cy.get('textarea[name=body]').clear().type('<span>test</span> {{ template "content" . }}',
|
||||
{ parseSpecialCharSequences: false, delay: 0 });
|
||||
cy.get('.modal-card-foot button.is-primary').click();
|
||||
cy.wait(250);
|
||||
cy.get('tbody td[data-label="Name"] a').contains('edited');
|
||||
});
|
||||
|
||||
|
||||
it('Previews templates', () => {
|
||||
// Edited one sould have a bare body.
|
||||
cy.get('tbody [data-cy=btn-preview').eq(0).click();
|
||||
cy.wait(500);
|
||||
cy.get('.modal-card-body iframe').iframe(() => {
|
||||
cy.get('span').first().contains('test');
|
||||
cy.get('p').first().contains('Hi there');
|
||||
});
|
||||
cy.get('.modal-card-foot button').click();
|
||||
|
||||
// Cloned one should have the full template.
|
||||
cy.get('tbody [data-cy=btn-preview').eq(1).click();
|
||||
cy.wait(500);
|
||||
cy.get('.modal-card-body iframe').iframe(() => {
|
||||
cy.get('.wrap p').first().contains('Hi there');
|
||||
cy.get('.footer a').first().contains('Unsubscribe');
|
||||
});
|
||||
cy.get('.modal-card-foot button').click();
|
||||
});
|
||||
|
||||
it('Sets default', () => {
|
||||
cy.get('tbody td.actions').eq(1).find('[data-cy=btn-set-default]').click();
|
||||
cy.get('.modal button.is-primary').click();
|
||||
|
||||
// The original default shouldn't have default and the new one should have.
|
||||
cy.get('tbody td.actions').eq(0).then((el) => {
|
||||
cy.wrap(el).find('[data-cy=btn-delete]').should('exist');
|
||||
cy.wrap(el).find('[data-cy=btn-set-default]').should('exist');
|
||||
});
|
||||
cy.get('tbody td.actions').eq(1).then((el) => {
|
||||
cy.wrap(el).find('[data-cy=btn-delete]').should('not.exist');
|
||||
cy.wrap(el).find('[data-cy=btn-set-default]').should('not.exist');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('Deletes template', () => {
|
||||
cy.wait(250);
|
||||
cy.get('tbody td.actions [data-cy=btn-delete]').first().click();
|
||||
cy.get('.modal button.is-primary').click();
|
||||
cy.wait(250);
|
||||
cy.get('tbody td.actions').should('have.length', 1);
|
||||
});
|
||||
});
|
21
frontend/cypress/plugins/index.js
Normal file
21
frontend/cypress/plugins/index.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
42
frontend/cypress/support/commands.js
Normal file
42
frontend/cypress/support/commands.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import 'cypress-file-upload';
|
||||
|
||||
Cypress.Commands.add('resetDB', () => {
|
||||
// Although cypress clearly states that a webserver should not be run
|
||||
// from within it, listmonk is killed, the DB reset, and run again
|
||||
// in the background. If the DB is reset without restartin listmonk,
|
||||
// the live Postgres connections in the app throw errors because the
|
||||
// schema changes midway.
|
||||
cy.exec(Cypress.env('server_init_command'));
|
||||
});
|
||||
|
||||
// Takes a th class selector of a Buefy table, clicks it sorting the table,
|
||||
// then compares the values of [td.data-id] attri of all the rows in the
|
||||
// table against the given IDs, asserting the expected order of sort.
|
||||
Cypress.Commands.add('sortTable', (theadSelector, ordIDs) => {
|
||||
cy.get(theadSelector).click();
|
||||
cy.get('tbody td[data-id]').each(($el, index) => {
|
||||
expect(ordIDs[index]).to.equal(parseInt($el.attr('data-id')));
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('loginAndVisit', (url) => {
|
||||
cy.visit(url, {
|
||||
auth: {
|
||||
username: Cypress.env('username'),
|
||||
password: Cypress.env('password'),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('clickMenu', (...selectors) => {
|
||||
selectors.forEach((s) => {
|
||||
cy.get(`.menu a[data-cy="${s}"]`).click();
|
||||
});
|
||||
});
|
||||
|
||||
// https://www.nicknish.co/blog/cypress-targeting-elements-inside-iframes
|
||||
Cypress.Commands.add('iframe', { prevSubject: 'element' }, ($iframe, callback = () => {}) => cy
|
||||
.wrap($iframe)
|
||||
.should((iframe) => expect(iframe.contents().find('body')).to.exist)
|
||||
.then((iframe) => cy.wrap(iframe.contents().find('body')))
|
||||
.within({}, callback));
|
16
frontend/cypress/support/index.js
Normal file
16
frontend/cypress/support/index.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import './commands';
|
||||
|
||||
beforeEach(() => {
|
||||
cy.server({
|
||||
ignore: (xhr) => {
|
||||
// Ignore the webpack dev server calls that interfere in the tests
|
||||
// when testing with `yarn serve`.
|
||||
if (xhr.url.indexOf('sockjs-node/') > -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return the default cypress whitelist filer.
|
||||
return xhr.method === 'GET' && /\.(jsx?|html|css)(\?.*)?$/.test(xhr.url);
|
||||
},
|
||||
});
|
||||
});
|
6
frontend/cypress/support/reset.sh
Executable file
6
frontend/cypress/support/reset.sh
Executable file
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
pkill -9 listmonk
|
||||
cd ../
|
||||
./listmonk --install --yes
|
||||
./listmonk > /dev/null 2>/dev/null &
|
7
frontend/package.json
vendored
7
frontend/package.json
vendored
|
@ -15,9 +15,9 @@
|
|||
"codeflask": "^1.4.1",
|
||||
"core-js": "^3.6.5",
|
||||
"dayjs": "^1.8.28",
|
||||
"elliptic": "^6.5.3",
|
||||
"elliptic": "^6.5.4",
|
||||
"humps": "^2.0.1",
|
||||
"lodash": "^4.17.19",
|
||||
"lodash": "^4.17.21",
|
||||
"node-forge": "^0.10.0",
|
||||
"node-sass": "^4.14.1",
|
||||
"qs": "^6.9.4",
|
||||
|
@ -25,6 +25,7 @@
|
|||
"quill-delta": "^4.2.2",
|
||||
"sass-loader": "^8.0.2",
|
||||
"textversionjs": "^1.1.3",
|
||||
"turndown": "^7.0.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue-c3": "^1.2.11",
|
||||
"vue-i18n": "^8.22.2",
|
||||
|
@ -40,6 +41,8 @@
|
|||
"@vue/cli-service": "~4.4.0",
|
||||
"@vue/eslint-config-airbnb": "^5.0.2",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"cypress": "^6.4.0",
|
||||
"cypress-file-upload": "^5.0.2",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
|
|
|
@ -32,63 +32,63 @@
|
|||
</b-menu-item><!-- dashboard -->
|
||||
|
||||
<b-menu-item :expanded="activeGroup.lists"
|
||||
:active="activeGroup.lists"
|
||||
:active="activeGroup.lists" data-cy="lists"
|
||||
v-on:update:active="(state) => toggleGroup('lists', state)"
|
||||
icon="format-list-bulleted-square" :label="$t('globals.terms.lists')">
|
||||
<b-menu-item :to="{name: 'lists'}" tag="router-link"
|
||||
:active="activeItem.lists"
|
||||
:active="activeItem.lists" data-cy="all-lists"
|
||||
icon="format-list-bulleted-square" :label="$t('menu.allLists')"></b-menu-item>
|
||||
|
||||
<b-menu-item :to="{name: 'forms'}" tag="router-link"
|
||||
:active="activeItem.forms"
|
||||
:active="activeItem.forms" class="forms"
|
||||
icon="newspaper-variant-outline" :label="$t('menu.forms')"></b-menu-item>
|
||||
</b-menu-item><!-- lists -->
|
||||
|
||||
<b-menu-item :expanded="activeGroup.subscribers"
|
||||
:active="activeGroup.subscribers"
|
||||
:active="activeGroup.subscribers" data-cy="subscribers"
|
||||
v-on:update:active="(state) => toggleGroup('subscribers', state)"
|
||||
icon="account-multiple" :label="$t('globals.terms.subscribers')">
|
||||
<b-menu-item :to="{name: 'subscribers'}" tag="router-link"
|
||||
:active="activeItem.subscribers"
|
||||
:active="activeItem.subscribers" data-cy="all-subscribers"
|
||||
icon="account-multiple" :label="$t('menu.allSubscribers')"></b-menu-item>
|
||||
|
||||
<b-menu-item :to="{name: 'import'}" tag="router-link"
|
||||
:active="activeItem.import"
|
||||
:active="activeItem.import" data-cy="import"
|
||||
icon="file-upload-outline" :label="$t('menu.import')"></b-menu-item>
|
||||
</b-menu-item><!-- subscribers -->
|
||||
|
||||
<b-menu-item :expanded="activeGroup.campaigns"
|
||||
:active="activeGroup.campaigns"
|
||||
:active="activeGroup.campaigns" data-cy="campaigns"
|
||||
v-on:update:active="(state) => toggleGroup('campaigns', state)"
|
||||
icon="rocket-launch-outline" :label="$t('globals.terms.campaigns')">
|
||||
<b-menu-item :to="{name: 'campaigns'}" tag="router-link"
|
||||
:active="activeItem.campaigns"
|
||||
:active="activeItem.campaigns" data-cy="all-campaigns"
|
||||
icon="rocket-launch-outline" :label="$t('menu.allCampaigns')"></b-menu-item>
|
||||
|
||||
<b-menu-item :to="{name: 'campaign', params: {id: 'new'}}" tag="router-link"
|
||||
:active="activeItem.campaign"
|
||||
:active="activeItem.campaign" data-cy="new-campaign"
|
||||
icon="plus" :label="$t('menu.newCampaign')"></b-menu-item>
|
||||
|
||||
<b-menu-item :to="{name: 'media'}" tag="router-link"
|
||||
:active="activeItem.media"
|
||||
:active="activeItem.media" data-cy="media"
|
||||
icon="image-outline" :label="$t('menu.media')"></b-menu-item>
|
||||
|
||||
<b-menu-item :to="{name: 'templates'}" tag="router-link"
|
||||
:active="activeItem.templates"
|
||||
:active="activeItem.templates" data-cy="templates"
|
||||
icon="file-image-outline" :label="$t('globals.terms.templates')"></b-menu-item>
|
||||
</b-menu-item><!-- campaigns -->
|
||||
|
||||
<b-menu-item :expanded="activeGroup.settings"
|
||||
:active="activeGroup.settings"
|
||||
:active="activeGroup.settings" data-cy="settings"
|
||||
v-on:update:active="(state) => toggleGroup('settings', state)"
|
||||
icon="cog-outline" :label="$t('menu.settings')">
|
||||
|
||||
<b-menu-item :to="{name: 'settings'}" tag="router-link"
|
||||
:active="activeItem.settings"
|
||||
:active="activeItem.settings" data-cy="all-settings"
|
||||
icon="cog-outline" :label="$t('menu.settings')"></b-menu-item>
|
||||
|
||||
<b-menu-item :to="{name: 'logs'}" tag="router-link"
|
||||
:active="activeItem.logs"
|
||||
:active="activeItem.logs" data-cy="logs"
|
||||
icon="newspaper-variant-outline" :label="$t('menu.logs')"></b-menu-item>
|
||||
</b-menu-item><!-- settings -->
|
||||
</b-menu-list>
|
||||
|
|
|
@ -156,6 +156,9 @@ export const getCampaignStats = async () => http.get('/api/campaigns/running/sta
|
|||
export const createCampaign = async (data) => http.post('/api/campaigns', data,
|
||||
{ loading: models.campaigns });
|
||||
|
||||
export const convertCampaignContent = async (data) => http.post(`/api/campaigns/${data.id}/content`, data,
|
||||
{ loading: models.campaigns });
|
||||
|
||||
export const testCampaign = async (data) => http.post(`/api/campaigns/${data.id}/test`, data,
|
||||
{ loading: models.campaigns });
|
||||
|
||||
|
|
|
@ -57,6 +57,10 @@ ul.no {
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.content pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
section {
|
||||
&.wrap {
|
||||
max-width: 1100px;
|
||||
|
@ -65,6 +69,7 @@ section {
|
|||
max-width: 900px;
|
||||
}
|
||||
}
|
||||
|
||||
.spinner.is-tiny {
|
||||
display: inline-block;
|
||||
height: 10px;
|
||||
|
@ -244,7 +249,7 @@ section {
|
|||
padding: 15px 10px;
|
||||
border-color: $grey-lightest;
|
||||
}
|
||||
.actions a {
|
||||
.actions a, .actions .a {
|
||||
margin: 0 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<section expanded class="modal-card-body preview">
|
||||
<b-loading :active="isLoading" :is-full-page="false"></b-loading>
|
||||
<form v-if="body" method="post" :action="previewURL" target="iframe" ref="form">
|
||||
<input type="hidden" name="content_type" :value="contentType" />
|
||||
<input type="hidden" name="body" :value="body" />
|
||||
</form>
|
||||
|
||||
|
@ -42,6 +43,7 @@ export default {
|
|||
// campaign | template.
|
||||
type: String,
|
||||
body: String,
|
||||
contentType: String,
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
|
@ -7,13 +7,20 @@
|
|||
<div>
|
||||
<b-radio v-model="form.radioFormat"
|
||||
@input="onChangeFormat" :disabled="disabled" name="format"
|
||||
native-value="richtext">{{ $t('campaigns.richText') }}</b-radio>
|
||||
native-value="richtext"
|
||||
data-cy="check-richtext">{{ $t('campaigns.richText') }}</b-radio>
|
||||
<b-radio v-model="form.radioFormat"
|
||||
@input="onChangeFormat" :disabled="disabled" name="format"
|
||||
native-value="html">{{ $t('campaigns.rawHTML') }}</b-radio>
|
||||
native-value="html"
|
||||
data-cy="check-html">{{ $t('campaigns.rawHTML') }}</b-radio>
|
||||
<b-radio v-model="form.radioFormat"
|
||||
@input="onChangeFormat" :disabled="disabled" name="format"
|
||||
native-value="plain">{{ $t('campaigns.plainText') }}</b-radio>
|
||||
native-value="markdown"
|
||||
data-cy="check-markdown">{{ $t('campaigns.markdown') }}</b-radio>
|
||||
<b-radio v-model="form.radioFormat"
|
||||
@input="onChangeFormat" :disabled="disabled" name="format"
|
||||
native-value="plain"
|
||||
data-cy="check-plain">{{ $t('campaigns.plainText') }}</b-radio>
|
||||
</div>
|
||||
</b-field>
|
||||
</div>
|
||||
|
@ -40,16 +47,18 @@
|
|||
<div v-if="form.format === 'html'"
|
||||
ref="htmlEditor" id="html-editor" class="html-editor"></div>
|
||||
|
||||
<!-- plain text editor //-->
|
||||
<b-input v-if="form.format === 'plain'" v-model="form.body" @input="onEditorChange"
|
||||
type="textarea" ref="plainEditor" class="plain-editor" />
|
||||
<!-- plain text / markdown editor //-->
|
||||
<b-input v-if="form.format === 'plain' || form.format === 'markdown'"
|
||||
v-model="form.body" @input="onEditorChange"
|
||||
type="textarea" name="content" ref="plainEditor" class="plain-editor" />
|
||||
|
||||
<!-- campaign preview //-->
|
||||
<campaign-preview v-if="isPreviewing"
|
||||
@close="onTogglePreview"
|
||||
type='campaign'
|
||||
:id='id'
|
||||
:title='title'
|
||||
type="campaign"
|
||||
:id="id"
|
||||
:title="title"
|
||||
:contentType="form.format"
|
||||
:body="form.body"></campaign-preview>
|
||||
|
||||
<!-- image picker -->
|
||||
|
@ -69,6 +78,7 @@ import 'quill/dist/quill.core.css';
|
|||
|
||||
import { quillEditor, Quill } from 'vue-quill-editor';
|
||||
import CodeFlask from 'codeflask';
|
||||
import TurndownService from 'turndown';
|
||||
|
||||
import CampaignPreview from './CampaignPreview.vue';
|
||||
import Media from '../views/Media.vue';
|
||||
|
@ -89,6 +99,8 @@ const regLink = new RegExp(/{{(\s+)?TrackLink(\s+)?"(.+?)"(\s+)?}}/);
|
|||
const Link = Quill.import('formats/link');
|
||||
Link.sanitize = (l) => l.replace(regLink, '{{ TrackLink `$3`}}');
|
||||
|
||||
const turndown = new TurndownService();
|
||||
|
||||
// Custom class to override the default indent behaviour to get inline CSS
|
||||
// style instead of classes.
|
||||
class IndentAttributor extends Quill.import('parchment').Attributor.Style {
|
||||
|
@ -182,6 +194,9 @@ export default {
|
|||
},
|
||||
},
|
||||
},
|
||||
|
||||
// HTML editor.
|
||||
flask: null,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -195,7 +210,7 @@ export default {
|
|||
},
|
||||
() => {
|
||||
// On cancel, undo the radio selection.
|
||||
this.form.radioFormat = format === 'richtext' ? 'html' : 'richtext';
|
||||
this.form.radioFormat = this.form.format;
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -232,22 +247,25 @@ export default {
|
|||
`;
|
||||
this.$refs.htmlEditor.appendChild(el);
|
||||
|
||||
const flask = new CodeFlask(el.shadowRoot.getElementById('area'), {
|
||||
this.flask = new CodeFlask(el.shadowRoot.getElementById('area'), {
|
||||
language: 'html',
|
||||
lineNumbers: false,
|
||||
styleParent: el.shadowRoot,
|
||||
readonly: this.disabled,
|
||||
});
|
||||
|
||||
flask.updateCode(this.form.body);
|
||||
flask.onUpdate((b) => {
|
||||
this.flask.onUpdate((b) => {
|
||||
this.form.body = b;
|
||||
this.$emit('input', { contentType: this.form.format, body: this.form.body });
|
||||
});
|
||||
|
||||
this.updateHTMLEditor();
|
||||
this.isReady = true;
|
||||
},
|
||||
|
||||
updateHTMLEditor() {
|
||||
this.flask.updateCode(this.form.body);
|
||||
},
|
||||
|
||||
onTogglePreview() {
|
||||
this.isPreviewing = !this.isPreviewing;
|
||||
},
|
||||
|
@ -269,6 +287,46 @@ export default {
|
|||
onMediaSelect(m) {
|
||||
this.$refs.quill.quill.insertEmbed(this.lastSel.index || 0, 'image', m.url);
|
||||
},
|
||||
|
||||
beautifyHTML(str) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = str.trim();
|
||||
return this.formatHTMLNode(div, 0).innerHTML;
|
||||
},
|
||||
|
||||
formatHTMLNode(node, level) {
|
||||
const lvl = level + 1;
|
||||
const indentBefore = new Array(lvl + 1).join(' ');
|
||||
const indentAfter = new Array(lvl - 1).join(' ');
|
||||
let textNode = null;
|
||||
|
||||
for (let i = 0; i < node.children.length; i += 1) {
|
||||
textNode = document.createTextNode(`\n${indentBefore}`);
|
||||
node.insertBefore(textNode, node.children[i]);
|
||||
|
||||
this.formatHTMLNode(node.children[i], lvl);
|
||||
if (node.lastElementChild === node.children[i]) {
|
||||
textNode = document.createTextNode(`\n${indentAfter}`);
|
||||
node.appendChild(textNode);
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
},
|
||||
|
||||
trimLines(str, removeEmptyLines) {
|
||||
const out = str.split('\n');
|
||||
for (let i = 0; i < out.length; i += 1) {
|
||||
const line = out[i].trim();
|
||||
if (removeEmptyLines) {
|
||||
out[i] = line;
|
||||
} else if (line === '') {
|
||||
out[i] = '';
|
||||
}
|
||||
}
|
||||
|
||||
return out.join('\n').replace(/\n\s*\n\s*\n/g, '\n\n');
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
@ -283,6 +341,10 @@ export default {
|
|||
this.form.format = f;
|
||||
this.form.radioFormat = f;
|
||||
|
||||
if (f === 'plain' || f === 'markdown') {
|
||||
this.isReady = true;
|
||||
}
|
||||
|
||||
// Trigger the change event so that the body and content type
|
||||
// are propagated to the parent on first load.
|
||||
this.onEditorChange();
|
||||
|
@ -293,14 +355,45 @@ export default {
|
|||
this.onEditorChange();
|
||||
},
|
||||
|
||||
htmlFormat(f) {
|
||||
if (f !== 'html') {
|
||||
return;
|
||||
}
|
||||
|
||||
htmlFormat(to, from) {
|
||||
// On switch to HTML, initialize the HTML editor.
|
||||
if (to === 'html') {
|
||||
this.$nextTick(() => {
|
||||
this.initHTMLEditor();
|
||||
});
|
||||
}
|
||||
|
||||
if ((from === 'richtext' || from === 'html') && to === 'plain') {
|
||||
// richtext, html => plain
|
||||
|
||||
// Preserve line breaks when converting HTML to plaintext. Quill produces
|
||||
// HTML without any linebreaks.
|
||||
const d = document.createElement('div');
|
||||
d.innerHTML = this.beautifyHTML(this.form.body);
|
||||
this.form.body = this.trimLines(d.innerText.trim(), true);
|
||||
} else if ((from === 'richtext' || from === 'html') && to === 'markdown') {
|
||||
// richtext, html => markdown
|
||||
this.form.body = turndown.turndown(this.form.body).replace(/\n\n+/ig, '\n\n');
|
||||
} else if (from === 'plain' && (to === 'richtext' || to === 'html')) {
|
||||
// plain => richtext, html
|
||||
this.form.body = this.form.body.replace(/\n/ig, '<br>\n');
|
||||
} else if (from === 'richtext' && to === 'html') {
|
||||
// richtext => html
|
||||
this.form.body = this.trimLines(this.beautifyHTML(this.form.body), false);
|
||||
} else if (from === 'markdown' && (to === 'richtext' || to === 'html')) {
|
||||
// markdown => richtext, html.
|
||||
this.$api.convertCampaignContent({
|
||||
id: 1, body: this.form.body, from, to,
|
||||
}).then((data) => {
|
||||
this.form.body = this.beautifyHTML(data.trim());
|
||||
// Update the HTML editor.
|
||||
if (to === 'html') {
|
||||
this.updateHTMLEditor();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.onEditorChange();
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="field">
|
||||
<div class="field list-selector">
|
||||
<div :class="['list-tags', ...classes]">
|
||||
<b-taglist>
|
||||
<b-tag v-for="l in selectedItems"
|
||||
|
|
|
@ -9,6 +9,17 @@ dayjs.extend(relativeTime);
|
|||
|
||||
const reEmail = /(.+?)@(.+?)/ig;
|
||||
|
||||
const htmlEntities = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'/': '/',
|
||||
'`': '`',
|
||||
'=': '=',
|
||||
};
|
||||
|
||||
export default class Utils {
|
||||
constructor(i18n) {
|
||||
this.i18n = i18n;
|
||||
|
@ -21,7 +32,7 @@ export default class Utils {
|
|||
}
|
||||
|
||||
const d = new Date(stamp);
|
||||
const day = this.i18n.t(`globals.days.${(d.getDay() + 1)}`);
|
||||
const day = this.i18n.t(`globals.days.${(d.getDay())}`);
|
||||
const month = this.i18n.t(`globals.months.${(d.getMonth() + 1)}`);
|
||||
let out = `${day}, ${d.getDate()}`;
|
||||
out += ` ${month} ${d.getFullYear()}`;
|
||||
|
@ -67,6 +78,9 @@ export default class Utils {
|
|||
return out.toFixed(2) + pfx;
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/12034334
|
||||
escapeHTML = (html) => html.replace(/[&<>"'`=/]/g, (s) => htmlEntities[s]);
|
||||
|
||||
// UI shortcuts.
|
||||
confirm = (msg, onConfirm, onCancel) => {
|
||||
Dialog.confirm({
|
||||
|
@ -98,7 +112,7 @@ export default class Utils {
|
|||
|
||||
toast = (msg, typ, duration) => {
|
||||
Toast.open({
|
||||
message: msg,
|
||||
message: this.escapeHTML(msg),
|
||||
type: !typ ? 'is-success' : typ,
|
||||
queue: false,
|
||||
duration: duration || 2000,
|
||||
|
|
|
@ -17,15 +17,15 @@
|
|||
<div class="column">
|
||||
<div class="buttons" v-if="isEditing && canEdit">
|
||||
<b-button @click="onSubmit" :loading="loading.campaigns"
|
||||
type="is-primary" icon-left="content-save-outline">
|
||||
type="is-primary" icon-left="content-save-outline" data-cy="btn-save">
|
||||
{{ $t('globals.buttons.saveChanges') }}
|
||||
</b-button>
|
||||
<b-button v-if="canStart" @click="startCampaign" :loading="loading.campaigns"
|
||||
type="is-primary" icon-left="rocket-launch-outline">
|
||||
type="is-primary" icon-left="rocket-launch-outline" data-cy="btn-start">
|
||||
{{ $t('campaigns.start') }}
|
||||
</b-button>
|
||||
<b-button v-if="canSchedule" @click="startCampaign" :loading="loading.campaigns"
|
||||
type="is-primary" icon-left="clock-start">
|
||||
type="is-primary" icon-left="clock-start" data-cy="btn-schedule">
|
||||
{{ $t('campaigns.schedule') }}
|
||||
</b-button>
|
||||
</div>
|
||||
|
@ -42,17 +42,20 @@
|
|||
<div class="column is-7">
|
||||
<form @submit.prevent="onSubmit">
|
||||
<b-field :label="$t('globals.fields.name')" label-position="on-border">
|
||||
<b-input :maxlength="200" :ref="'focus'" v-model="form.name" :disabled="!canEdit"
|
||||
<b-input :maxlength="200" :ref="'focus'" v-model="form.name"
|
||||
name="name" :disabled="!canEdit"
|
||||
:placeholder="$t('globals.fields.name')" required></b-input>
|
||||
</b-field>
|
||||
|
||||
<b-field :label="$t('campaigns.subject')" label-position="on-border">
|
||||
<b-input :maxlength="200" v-model="form.subject" :disabled="!canEdit"
|
||||
<b-input :maxlength="200" v-model="form.subject"
|
||||
name="subject" :disabled="!canEdit"
|
||||
:placeholder="$t('campaigns.subject')" required></b-input>
|
||||
</b-field>
|
||||
|
||||
<b-field :label="$t('campaigns.fromAddress')" label-position="on-border">
|
||||
<b-input :maxlength="200" v-model="form.fromEmail" :disabled="!canEdit"
|
||||
<b-input :maxlength="200" v-model="form.fromEmail"
|
||||
name="from_email" :disabled="!canEdit"
|
||||
:placeholder="$t('campaigns.fromAddressPlaceholder')" required></b-input>
|
||||
</b-field>
|
||||
|
||||
|
@ -67,34 +70,34 @@
|
|||
|
||||
<b-field :label="$tc('globals.terms.template')" label-position="on-border">
|
||||
<b-select :placeholder="$tc('globals.terms.template')" v-model="form.templateId"
|
||||
:disabled="!canEdit" required>
|
||||
name="template" :disabled="!canEdit" required>
|
||||
<option v-for="t in templates" :value="t.id" :key="t.id">{{ t.name }}</option>
|
||||
</b-select>
|
||||
</b-field>
|
||||
|
||||
<b-field :label="$tc('globals.terms.messenger')" label-position="on-border">
|
||||
<b-select :placeholder="$tc('globals.terms.messenger')" v-model="form.messenger"
|
||||
:disabled="!canEdit" required>
|
||||
name="messenger" :disabled="!canEdit" required>
|
||||
<option v-for="m in messengers"
|
||||
:value="m" :key="m">{{ m }}</option>
|
||||
</b-select>
|
||||
</b-field>
|
||||
|
||||
<b-field :label="$t('globals.terms.tags')" label-position="on-border">
|
||||
<b-taginput v-model="form.tags" :disabled="!canEdit"
|
||||
<b-taginput v-model="form.tags" name="tags" :disabled="!canEdit"
|
||||
ellipsis icon="tag-outline" :placeholder="$t('globals.terms.tags')" />
|
||||
</b-field>
|
||||
<hr />
|
||||
|
||||
<div class="columns">
|
||||
<div class="column is-4">
|
||||
<b-field :label="$t('campaigns.sendLater')">
|
||||
<b-field :label="$t('campaigns.sendLater')" data-cy="btn-send-later">
|
||||
<b-switch v-model="form.sendLater" :disabled="!canEdit" />
|
||||
</b-field>
|
||||
</div>
|
||||
<div class="column">
|
||||
<br />
|
||||
<b-field v-if="form.sendLater"
|
||||
<b-field v-if="form.sendLater" data-cy="send_at"
|
||||
:message="form.sendAtDate ? $utils.duration(Date(), form.sendAtDate) : ''">
|
||||
<b-datetimepicker
|
||||
v-model="form.sendAtDate"
|
||||
|
@ -112,7 +115,9 @@
|
|||
|
||||
<b-field v-if="isNew">
|
||||
<b-button native-type="submit" type="is-primary"
|
||||
:loading="loading.campaigns">{{ $t('campaigns.continue') }}</b-button>
|
||||
:loading="loading.campaigns" data-cy="btn-continue">
|
||||
{{ $t('campaigns.continue') }}
|
||||
</b-button>
|
||||
</b-field>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -8,13 +8,15 @@
|
|||
</div>
|
||||
<div class="column has-text-right">
|
||||
<b-button :to="{name: 'campaign', params:{id: 'new'}}" tag="router-link"
|
||||
type="is-primary" icon-left="plus">{{ $t('globals.buttons.new') }}</b-button>
|
||||
type="is-primary" icon-left="plus" data-cy="btn-new">
|
||||
{{ $t('globals.buttons.new') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<form @submit.prevent="getCampaigns">
|
||||
<b-field grouped>
|
||||
<b-input v-model="queryParams.query"
|
||||
<b-input v-model="queryParams.query" name="query"
|
||||
:placeholder="$t('campaigns.queryPlaceholder')" icon="magnify" ref="query"></b-input>
|
||||
<b-button native-type="submit" type="is-primary" icon-left="magnify"></b-button>
|
||||
</b-field>
|
||||
|
@ -29,7 +31,8 @@
|
|||
hoverable backend-sorting @sort="onSort">
|
||||
<template slot-scope="props">
|
||||
<b-table-column class="status" field="status" :label="$t('globals.fields.status')"
|
||||
width="10%" :id="props.row.id" sortable>
|
||||
width="10%" :id="props.row.id" sortable
|
||||
header-class="cy-status" :data-id="props.row.id">
|
||||
<div>
|
||||
<p>
|
||||
<router-link :to="{ name: 'campaign', params: { 'id': props.row.id }}">
|
||||
|
@ -46,13 +49,14 @@
|
|||
<span class="is-size-7 has-text-grey scheduled">
|
||||
<b-icon icon="alarm" size="is-small" />
|
||||
{{ $utils.duration(Date(), props.row.sendAt, true) }}
|
||||
– {{ $utils.niceDate(props.row.sendAt, true) }}
|
||||
<br />{{ $utils.niceDate(props.row.sendAt, true) }}
|
||||
</span>
|
||||
</b-tooltip>
|
||||
</p>
|
||||
</div>
|
||||
</b-table-column>
|
||||
<b-table-column field="name" :label="$t('globals.fields.name')" sortable width="25%">
|
||||
<b-table-column field="name" :label="$t('globals.fields.name')" sortable width="25%"
|
||||
header-class="cy-name">
|
||||
<div>
|
||||
<p>
|
||||
<b-tag v-if="props.row.type !== 'regular'" class="is-small">
|
||||
|
@ -78,7 +82,7 @@
|
|||
</ul>
|
||||
</b-table-column>
|
||||
<b-table-column field="created_at" :label="$t('campaigns.timestamps')"
|
||||
width="19%" sortable>
|
||||
width="19%" sortable header-class="cy-timestamp">
|
||||
<div class="fields timestamps" :set="stats = getCampaignStats(props.row)">
|
||||
<p>
|
||||
<label>{{ $t('globals.fields.createdAt') }}</label>
|
||||
|
@ -136,33 +140,33 @@
|
|||
<div>
|
||||
<a href="" v-if="canStart(props.row)"
|
||||
@click.prevent="$utils.confirm(null,
|
||||
() => changeCampaignStatus(props.row, 'running'))">
|
||||
() => changeCampaignStatus(props.row, 'running'))" data-cy="btn-start">
|
||||
<b-tooltip :label="$t('campaigns.start')" type="is-dark">
|
||||
<b-icon icon="rocket-launch-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a href="" v-if="canPause(props.row)"
|
||||
@click.prevent="$utils.confirm(null,
|
||||
() => changeCampaignStatus(props.row, 'paused'))">
|
||||
() => changeCampaignStatus(props.row, 'paused'))" data-cy="btn-pause">
|
||||
<b-tooltip :label="$t('campaigns.pause')" type="is-dark">
|
||||
<b-icon icon="pause-circle-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a href="" v-if="canResume(props.row)"
|
||||
@click.prevent="$utils.confirm(null,
|
||||
() => changeCampaignStatus(props.row, 'running'))">
|
||||
() => changeCampaignStatus(props.row, 'running'))" data-cy="btn-resume">
|
||||
<b-tooltip :label="$t('campaigns.send')" type="is-dark">
|
||||
<b-icon icon="rocket-launch-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a href="" v-if="canSchedule(props.row)"
|
||||
@click.prevent="$utils.confirm($t('campaigns.confirmSchedule'),
|
||||
() => changeCampaignStatus(props.row, 'scheduled'))">
|
||||
() => changeCampaignStatus(props.row, 'scheduled'))" data-cy="btn-schedule">
|
||||
<b-tooltip :label="$t('campaigns.schedule')" type="is-dark">
|
||||
<b-icon icon="clock-start" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a href="" @click.prevent="previewCampaign(props.row)">
|
||||
<a href="" @click.prevent="previewCampaign(props.row)" data-cy="btn-preview">
|
||||
<b-tooltip :label="$t('campaigns.preview')" type="is-dark">
|
||||
<b-icon icon="file-find-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
|
@ -170,20 +174,22 @@
|
|||
<a href="" @click.prevent="$utils.prompt($t('globals.buttons.clone'),
|
||||
{ placeholder: $t('globals.fields.name'),
|
||||
value: $t('campaigns.copyOf', { name: props.row.name }) },
|
||||
(name) => cloneCampaign(name, props.row))">
|
||||
(name) => cloneCampaign(name, props.row))"
|
||||
data-cy="btn-clone">
|
||||
<b-tooltip :label="$t('globals.buttons.clone')" type="is-dark">
|
||||
<b-icon icon="file-multiple-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a href="" v-if="canCancel(props.row)"
|
||||
@click.prevent="$utils.confirm(null,
|
||||
() => changeCampaignStatus(props.row, 'cancelled'))">
|
||||
() => changeCampaignStatus(props.row, 'cancelled'))"
|
||||
data-cy="btn-cancel">
|
||||
<b-tooltip :label="$t('globals.buttons.cancel')" type="is-dark">
|
||||
<b-icon icon="cancel" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a href="" @click.prevent="$utils.confirm($tc('campaigns.confirmDelete'),
|
||||
() => deleteCampaign(props.row))">
|
||||
() => deleteCampaign(props.row))" data-cy="btn-delete">
|
||||
<b-icon icon="trash-can-outline" size="is-small" />
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<div class="tile">
|
||||
<div class="tile is-parent is-vertical relative">
|
||||
<b-loading v-if="isCountsLoading" active :is-full-page="false" />
|
||||
<article class="tile is-child notification">
|
||||
<article class="tile is-child notification" data-cy="lists">
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-6">
|
||||
<p class="title">{{ $utils.niceNumber(counts.lists.total) }}</p>
|
||||
|
@ -43,7 +43,7 @@
|
|||
</div>
|
||||
</article><!-- lists -->
|
||||
|
||||
<article class="tile is-child notification">
|
||||
<article class="tile is-child notification" data-cy="campaigns">
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-6">
|
||||
<p class="title">{{ $utils.niceNumber(counts.campaigns.total) }}</p>
|
||||
|
@ -64,7 +64,7 @@
|
|||
|
||||
<div class="tile is-parent relative">
|
||||
<b-loading v-if="isCountsLoading" active :is-full-page="false" />
|
||||
<article class="tile is-child notification">
|
||||
<article class="tile is-child notification" data-cy="subscribers">
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-6">
|
||||
<p class="title">{{ $utils.niceNumber(counts.subscribers.total) }}</p>
|
||||
|
@ -87,7 +87,7 @@
|
|||
</div><!-- subscriber breakdown -->
|
||||
</div><!-- subscriber columns -->
|
||||
<hr />
|
||||
<div class="columns">
|
||||
<div class="columns" data-cy="messages">
|
||||
<div class="column is-12">
|
||||
<p class="title">{{ $utils.niceNumber(counts.messages) }}</p>
|
||||
<p class="is-size-6 has-text-grey">
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<p>{{ $t('forms.selectHelp') }}</p>
|
||||
|
||||
<b-loading :active="loading.lists" :is-full-page="false" />
|
||||
<ul class="no">
|
||||
<ul class="no" data-cy="lists">
|
||||
<li v-for="l in publicLists" :key="l.id">
|
||||
<b-checkbox v-model="checked"
|
||||
:native-value="l.uuid">{{ l.name }}</b-checkbox>
|
||||
|
@ -27,11 +27,11 @@
|
|||
<h4>{{ $t('forms.publicSubPage') }}</h4>
|
||||
<p>
|
||||
<a :href="`${settings['app.root_url']}/subscription/form`"
|
||||
target="_blank">{{ settings['app.root_url'] }}/subscription/form</a>
|
||||
target="_blank" data-cy="url">{{ settings['app.root_url'] }}/subscription/form</a>
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="column" data-cy="form">
|
||||
<h4>{{ $t('forms.formHTML') }}</h4>
|
||||
<p>
|
||||
{{ $t('forms.formHTMLHelp') }}
|
||||
|
|
|
@ -11,9 +11,11 @@
|
|||
<b-field :label="$t('import.mode')">
|
||||
<div>
|
||||
<b-radio v-model="form.mode" name="mode"
|
||||
native-value="subscribe">{{ $t('import.subscribe') }}</b-radio>
|
||||
native-value="subscribe"
|
||||
data-cy="check-subscribe">{{ $t('import.subscribe') }}</b-radio>
|
||||
<b-radio v-model="form.mode" name="mode"
|
||||
native-value="blocklist">{{ $t('import.blocklist') }}</b-radio>
|
||||
native-value="blocklist"
|
||||
data-cy="check-blocklist">{{ $t('import.blocklist') }}</b-radio>
|
||||
</div>
|
||||
</b-field>
|
||||
</div>
|
||||
|
@ -276,11 +278,7 @@ export default Vue.extend({
|
|||
// Post.
|
||||
this.$api.importSubscribers(params).then(() => {
|
||||
// On file upload, show a confirmation.
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('import.importStarted'),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('import.importStarted'));
|
||||
|
||||
// Start polling status.
|
||||
this.pollStatus();
|
||||
|
|
|
@ -12,13 +12,13 @@
|
|||
</header>
|
||||
<section expanded class="modal-card-body">
|
||||
<b-field :label="$t('globals.fields.name')" label-position="on-border">
|
||||
<b-input :maxlength="200" :ref="'focus'" v-model="form.name"
|
||||
<b-input :maxlength="200" :ref="'focus'" v-model="form.name" name="name"
|
||||
:placeholder="$t('globals.fields.name')" required></b-input>
|
||||
</b-field>
|
||||
|
||||
<b-field :label="$t('lists.type')" label-position="on-border"
|
||||
:message="$t('lists.typeHelp')">
|
||||
<b-select v-model="form.type" :placeholder="$t('lists.typeHelp')" required>
|
||||
<b-select v-model="form.type" name="type" :placeholder="$t('lists.typeHelp')" required>
|
||||
<option value="private">{{ $t('lists.types.private') }}</option>
|
||||
<option value="public">{{ $t('lists.types.public') }}</option>
|
||||
</b-select>
|
||||
|
@ -26,14 +26,14 @@
|
|||
|
||||
<b-field :label="$t('lists.optin')" label-position="on-border"
|
||||
:message="$t('lists.optinHelp')">
|
||||
<b-select v-model="form.optin" placeholder="Opt-in type" required>
|
||||
<b-select v-model="form.optin" name="optin" placeholder="Opt-in type" required>
|
||||
<option value="single">{{ $t('lists.optins.single') }}</option>
|
||||
<option value="double">{{ $t('lists.optins.double') }}</option>
|
||||
</b-select>
|
||||
</b-field>
|
||||
|
||||
<b-field :label="$t('globals.terms.tags')" label-position="on-border">
|
||||
<b-taginput v-model="form.tags" ellipsis
|
||||
<b-taginput v-model="form.tags" name="tags" ellipsis
|
||||
icon="tag-outline" :placeholder="$t('globals.terms.tags')"></b-taginput>
|
||||
</b-field>
|
||||
</section>
|
||||
|
@ -84,11 +84,7 @@ export default Vue.extend({
|
|||
this.$api.createList(this.form).then((data) => {
|
||||
this.$emit('finished');
|
||||
this.$parent.close();
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.created', { name: data.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.created', { name: data.name }));
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -96,11 +92,7 @@ export default Vue.extend({
|
|||
this.$api.updateList({ id: this.data.id, ...this.form }).then((data) => {
|
||||
this.$emit('finished');
|
||||
this.$parent.close();
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.updated', { name: data.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.updated', { name: data.name }));
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</h1>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<b-button type="is-primary" icon-left="plus" @click="showNewForm">
|
||||
<b-button type="is-primary" icon-left="plus" @click="showNewForm" data-cy="btn-new">
|
||||
{{ $t('globals.buttons.new') }}
|
||||
</b-button>
|
||||
</div>
|
||||
|
@ -23,9 +23,9 @@
|
|||
backend-sorting @sort="onSort"
|
||||
>
|
||||
<template slot-scope="props">
|
||||
<b-table-column field="name" :label="$t('globals.fields.name')"
|
||||
<b-table-column field="name" :label="$t('globals.fields.name')" header-class="cy-name"
|
||||
sortable width="25%" paginated backend-pagination pagination-position="both"
|
||||
@page-change="onPageChange">
|
||||
@page-change="onPageChange" :data-id="props.row.id">
|
||||
<div>
|
||||
<router-link :to="{name: 'subscribers_list', params: { listID: props.row.id }}">
|
||||
{{ props.row.name }}
|
||||
|
@ -36,20 +36,22 @@
|
|||
</div>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="type" :label="$t('globals.fields.type')" sortable>
|
||||
<b-table-column field="type" :label="$t('globals.fields.type')" header-class="cy-type"
|
||||
sortable>
|
||||
<div>
|
||||
<b-tag :class="props.row.type">
|
||||
<b-tag :class="props.row.type" :data-cy="`type-${props.row.type}`">
|
||||
{{ $t('lists.types.' + props.row.type) }}
|
||||
</b-tag>
|
||||
{{ ' ' }}
|
||||
<b-tag>
|
||||
<b-tag :data-cy="`optin-${props.row.optin}`">
|
||||
<b-icon :icon="props.row.optin === 'double' ?
|
||||
'account-check-outline' : 'account-off-outline'" size="is-small" />
|
||||
{{ ' ' }}
|
||||
{{ $t('lists.optins.' + props.row.optin) }}
|
||||
</b-tag>{{ ' ' }}
|
||||
<a v-if="props.row.optin === 'double'" class="is-size-7 send-optin"
|
||||
href="#" @click="$utils.confirm(null, () => createOptinCampaign(props.row))">
|
||||
href="#" @click="$utils.confirm(null, () => createOptinCampaign(props.row))"
|
||||
data-cy="btn-send-optin-campaign">
|
||||
<b-tooltip :label="$t('lists.sendOptinCampaign')" type="is-dark">
|
||||
<b-icon icon="rocket-launch-outline" size="is-small" />
|
||||
{{ $t('lists.sendOptinCampaign') }}
|
||||
|
@ -58,33 +60,35 @@
|
|||
</div>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="subscriber_count" :label="$t('globals.terms.lists')"
|
||||
numeric sortable centered>
|
||||
<b-table-column field="subscriber_count" :label="$t('globals.terms.subscribers')"
|
||||
header-class="cy-subscribers" numeric sortable centered>
|
||||
<router-link :to="`/subscribers/lists/${props.row.id}`">
|
||||
{{ props.row.subscriberCount }}
|
||||
</router-link>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="created_at" :label="$t('globals.fields.createdAt')" sortable>
|
||||
<b-table-column field="created_at" :label="$t('globals.fields.createdAt')"
|
||||
header-class="cy-created_at" sortable>
|
||||
{{ $utils.niceDate(props.row.createdAt) }}
|
||||
</b-table-column>
|
||||
<b-table-column field="updated_at" :label="$t('globals.fields.updatedAt')" sortable>
|
||||
<b-table-column field="updated_at" :label="$t('globals.fields.updatedAt')"
|
||||
header-class="cy-updated_at" sortable>
|
||||
{{ $utils.niceDate(props.row.updatedAt) }}
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column class="actions" align="right">
|
||||
<div>
|
||||
<router-link :to="`/campaigns/new?list_id=${props.row.id}`">
|
||||
<router-link :to="`/campaigns/new?list_id=${props.row.id}`" data-cy="btn-campaign">
|
||||
<b-tooltip :label="$t('lists.sendCampaign')" type="is-dark">
|
||||
<b-icon icon="rocket-launch-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</router-link>
|
||||
<a href="" @click.prevent="showEditForm(props.row)">
|
||||
<a href="" @click.prevent="showEditForm(props.row)" data-cy="btn-edit">
|
||||
<b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
|
||||
<b-icon icon="pencil-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a href="" @click.prevent="deleteList(props.row)">
|
||||
<a href="" @click.prevent="deleteList(props.row)" data-cy="btn-delete">
|
||||
<b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
|
||||
<b-icon icon="trash-can-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
|
@ -177,11 +181,7 @@ export default Vue.extend({
|
|||
this.$api.deleteList(list.id).then(() => {
|
||||
this.getLists();
|
||||
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.deleted', { name: list.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.deleted', { name: list.name }));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
<div class="column has-text-right">
|
||||
<b-button :disabled="!hasFormChanged"
|
||||
type="is-primary" icon-left="content-save-outline"
|
||||
@click="onSubmit" class="isSaveEnabled">{{ $t('globals.buttons.save') }}</b-button>
|
||||
@click="onSubmit" class="isSaveEnabled" data-cy="btn-save">
|
||||
{{ $t('globals.buttons.save') }}
|
||||
</b-button>
|
||||
</div>
|
||||
</header>
|
||||
<hr />
|
||||
|
@ -57,6 +59,12 @@
|
|||
name="app.enable_public_subscription_page" />
|
||||
</b-field>
|
||||
|
||||
<b-field :label="$t('settings.general.checkUpdates')"
|
||||
:message="$t('settings.general.checkUpdatesHelp')">
|
||||
<b-switch v-model="form['app.check_updates']"
|
||||
name="app.check_updates" />
|
||||
</b-field>
|
||||
|
||||
<hr />
|
||||
<b-field :label="$t('settings.general.language')" label-position="on-border">
|
||||
<b-select v-model="form['app.lang']" name="app.lang">
|
||||
|
@ -278,11 +286,11 @@
|
|||
<div class="column is-2">
|
||||
<b-field :label="$t('globals.buttons.enabled')">
|
||||
<b-switch v-model="item.enabled" name="enabled"
|
||||
:native-value="true" />
|
||||
:native-value="true" data-cy="btn-enable-smtp" />
|
||||
</b-field>
|
||||
<b-field v-if="form.smtp.length > 1">
|
||||
<a @click.prevent="$utils.confirm(null, () => removeSMTP(n))"
|
||||
href="#" class="is-size-7">
|
||||
href="#" class="is-size-7" data-cy="btn-delete-smtp">
|
||||
<b-icon icon="trash-can-outline" size="is-small" />
|
||||
{{ $t('globals.buttons.delete') }}
|
||||
</a>
|
||||
|
@ -659,7 +667,8 @@ export default Vue.extend({
|
|||
|
||||
getSettings() {
|
||||
this.$api.getSettings().then((data) => {
|
||||
const d = data;
|
||||
const d = JSON.parse(JSON.stringify(data));
|
||||
|
||||
// Serialize the `email_headers` array map to display on the form.
|
||||
for (let i = 0; i < d.smtp.length; i += 1) {
|
||||
d.smtp[i].strEmailHeaders = JSON.stringify(d.smtp[i].email_headers, null, 4);
|
||||
|
|
|
@ -8,16 +8,19 @@
|
|||
<section expanded class="modal-card-body">
|
||||
<b-field label="Action">
|
||||
<div>
|
||||
<b-radio v-model="form.action" name="action" native-value="add">
|
||||
<b-radio v-model="form.action" name="action" native-value="add"
|
||||
data-cy="check-list-add">
|
||||
{{ $t('globals.buttons.add') }}
|
||||
</b-radio>
|
||||
<b-radio v-model="form.action" name="action" native-value="remove">
|
||||
<b-radio v-model="form.action" name="action" native-value="remove"
|
||||
data-cy="check-list-remove">
|
||||
{{ $t('globals.buttons.remove') }}
|
||||
</b-radio>
|
||||
<b-radio
|
||||
v-model="form.action"
|
||||
name="action"
|
||||
native-value="unsubscribe"
|
||||
data-cy="check-list-unsubscribe"
|
||||
>{{ $t('subscribers.markUnsubscribed') }}</b-radio>
|
||||
</div>
|
||||
</b-field>
|
||||
|
|
|
@ -8,24 +8,25 @@
|
|||
<h4 v-else>{{ $t('subscribers.newSubscriber') }}</h4>
|
||||
|
||||
<p v-if="isEditing" class="has-text-grey is-size-7">
|
||||
{{ $t('globals.fields.id') }}: {{ data.id }} /
|
||||
{{ $t('globals.fields.id') }}: <span data-cy="id">{{ data.id }}</span> /
|
||||
{{ $t('globals.fields.uuid') }}: {{ data.uuid }}
|
||||
</p>
|
||||
</header>
|
||||
<section expanded class="modal-card-body">
|
||||
<b-field :label="$t('subscribers.email')" label-position="on-border">
|
||||
<b-input :maxlength="200" v-model="form.email" :ref="'focus'"
|
||||
<b-input :maxlength="200" v-model="form.email" name="email" :ref="'focus'"
|
||||
:placeholder="$t('subscribers.email')" required></b-input>
|
||||
</b-field>
|
||||
|
||||
<b-field :label="$t('globals.fields.name')" label-position="on-border">
|
||||
<b-input :maxlength="200" v-model="form.name"
|
||||
<b-input :maxlength="200" v-model="form.name" name="name"
|
||||
:placeholder="$t('globals.fields.name')"></b-input>
|
||||
</b-field>
|
||||
|
||||
<b-field :label="$t('globals.fields.status')" label-position="on-border"
|
||||
:message="$t('subscribers.blocklistedHelp')">
|
||||
<b-select v-model="form.status" :placeholder="$t('globals.fields.status')" required>
|
||||
<b-select v-model="form.status" name="status" :placeholder="$t('globals.fields.status')"
|
||||
required>
|
||||
<option value="enabled">{{ $t('subscribers.status.enabled') }}</option>
|
||||
<option value="blocklisted">{{ $t('subscribers.status.blocklisted') }}</option>
|
||||
</b-select>
|
||||
|
@ -42,7 +43,7 @@
|
|||
|
||||
<b-field :label="$t('subscribers.attribs')" label-position="on-border"
|
||||
:message="$t('subscribers.attribsHelp') + ' ' + egAttribs">
|
||||
<b-input v-model="form.strAttribs" type="textarea" />
|
||||
<b-input v-model="form.strAttribs" name="attribs" type="textarea" />
|
||||
</b-field>
|
||||
<a href="https://listmonk.app/docs/concepts"
|
||||
target="_blank" rel="noopener noreferrer" class="is-size-7">
|
||||
|
@ -97,10 +98,13 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
createSubscriber() {
|
||||
const attribs = this.validateAttribs(this.form.strAttribs);
|
||||
let attribs = {};
|
||||
if (this.form.strAttribs) {
|
||||
attribs = this.validateAttribs(this.form.strAttribs);
|
||||
if (!attribs) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
email: this.form.email,
|
||||
|
@ -115,19 +119,18 @@ export default Vue.extend({
|
|||
this.$api.createSubscriber(data).then((d) => {
|
||||
this.$emit('finished');
|
||||
this.$parent.close();
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.created', { name: d.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.created', { name: d.name }));
|
||||
});
|
||||
},
|
||||
|
||||
updateSubscriber() {
|
||||
const attribs = this.validateAttribs(this.form.strAttribs);
|
||||
let attribs = {};
|
||||
if (this.form.strAttribs) {
|
||||
attribs = this.validateAttribs(this.form.strAttribs);
|
||||
if (!attribs) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
id: this.form.id,
|
||||
|
@ -143,11 +146,7 @@ export default Vue.extend({
|
|||
this.$api.updateSubscriber(data).then((d) => {
|
||||
this.$emit('finished');
|
||||
this.$parent.close();
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.updated', { name: d.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.updated', { name: d.name }));
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -157,21 +156,12 @@ export default Vue.extend({
|
|||
try {
|
||||
attribs = JSON.parse(str);
|
||||
} catch (e) {
|
||||
this.$buefy.toast.open({
|
||||
message: `${this.$t('subscribers.invalidJSON')}: e.toString()`,
|
||||
type: 'is-danger',
|
||||
duration: 3000,
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(`${this.$t('subscribers.invalidJSON')}: ${e.toString()}`,
|
||||
'is-danger', 3000);
|
||||
return null;
|
||||
}
|
||||
if (attribs instanceof Array) {
|
||||
this.$buefy.toast.open({
|
||||
message: 'Attributes should be a map {} and not an array []',
|
||||
type: 'is-danger',
|
||||
duration: 3000,
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast('Attributes should be a map {} and not an array []', 'is-danger', 3000);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,14 +3,16 @@
|
|||
<header class="columns">
|
||||
<div class="column is-half">
|
||||
<h1 class="title is-4">{{ $t('globals.terms.subscribers') }}
|
||||
<span v-if="!isNaN(subscribers.total)">({{ subscribers.total }})</span>
|
||||
<span v-if="!isNaN(subscribers.total)">
|
||||
(<span data-cy="count">{{ subscribers.total }}</span>)
|
||||
</span>
|
||||
<span v-if="currentList">
|
||||
» {{ currentList.name }}
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="column has-text-right">
|
||||
<b-button type="is-primary" icon-left="plus" @click="showNewForm">
|
||||
<b-button type="is-primary" icon-left="plus" @click="showNewForm" data-cy="btn-new">
|
||||
{{ $t('globals.buttons.new') }}
|
||||
</b-button>
|
||||
</div>
|
||||
|
@ -23,13 +25,13 @@
|
|||
<b-field grouped>
|
||||
<b-input @input="onSimpleQueryInput" v-model="queryInput"
|
||||
:placeholder="$t('subscribers.queryPlaceholder')" icon="magnify" ref="query"
|
||||
:disabled="isSearchAdvanced"></b-input>
|
||||
:disabled="isSearchAdvanced" data-cy="search"></b-input>
|
||||
<b-button native-type="submit" type="is-primary" icon-left="magnify"
|
||||
:disabled="isSearchAdvanced"></b-button>
|
||||
:disabled="isSearchAdvanced" data-cy="btn-search"></b-button>
|
||||
</b-field>
|
||||
|
||||
<p>
|
||||
<a href="#" @click.prevent="toggleAdvancedSearch">
|
||||
<a href="#" @click.prevent="toggleAdvancedSearch" data-cy="btn-advanced-search">
|
||||
<b-icon icon="cog-outline" size="is-small" />
|
||||
{{ $t('subscribers.advancedQuery') }}
|
||||
</a>
|
||||
|
@ -40,7 +42,8 @@
|
|||
<b-input v-model="queryParams.queryExp"
|
||||
@keydown.native.enter="onAdvancedQueryEnter"
|
||||
type="textarea" ref="queryExp"
|
||||
placeholder="subscribers.name LIKE '%user%' or subscribers.status='blocklisted'">
|
||||
placeholder="subscribers.name LIKE '%user%' or subscribers.status='blocklisted'"
|
||||
data-cy="query">
|
||||
</b-input>
|
||||
</b-field>
|
||||
<b-field>
|
||||
|
@ -55,8 +58,9 @@
|
|||
|
||||
<div class="buttons">
|
||||
<b-button native-type="submit" type="is-primary"
|
||||
icon-left="magnify">{{ $t('subscribers.query') }}</b-button>
|
||||
<b-button @click.prevent="toggleAdvancedSearch" icon-left="cancel">
|
||||
icon-left="magnify" data-cy="btn-query">{{ $t('subscribers.query') }}</b-button>
|
||||
<b-button @click.prevent="toggleAdvancedSearch" icon-left="cancel"
|
||||
data-cy="btn-query-reset">
|
||||
{{ $t('subscribers.reset') }}
|
||||
</b-button>
|
||||
</div>
|
||||
|
@ -80,15 +84,15 @@
|
|||
</p>
|
||||
|
||||
<p class="actions">
|
||||
<a href='' @click.prevent="showBulkListForm">
|
||||
<a href='' @click.prevent="showBulkListForm" data-cy="btn-manage-lists">
|
||||
<b-icon icon="format-list-bulleted-square" size="is-small" /> Manage lists
|
||||
</a>
|
||||
|
||||
<a href='' @click.prevent="deleteSubscribers">
|
||||
<a href='' @click.prevent="deleteSubscribers" data-cy="btn-delete-subscribers">
|
||||
<b-icon icon="trash-can-outline" size="is-small" /> Delete
|
||||
</a>
|
||||
|
||||
<a href='' @click.prevent="blocklistSubscribers">
|
||||
<a href='' @click.prevent="blocklistSubscribers" data-cy="btn-manage-blocklist">
|
||||
<b-icon icon="account-off-outline" size="is-small" /> Blocklist
|
||||
</a>
|
||||
</p><!-- selection actions //-->
|
||||
|
@ -110,7 +114,8 @@
|
|||
</a>
|
||||
</template>
|
||||
<template slot-scope="props">
|
||||
<b-table-column field="status" :label="$t('globals.fields.status')" sortable>
|
||||
<b-table-column field="status" :label="$t('globals.fields.status')"
|
||||
header-class="cy-status" :data-id="props.row.id" sortable>
|
||||
<a :href="`/subscribers/${props.row.id}`"
|
||||
@click.prevent="showEditForm(props.row)">
|
||||
<b-tag :class="props.row.status">
|
||||
|
@ -119,55 +124,62 @@
|
|||
</a>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="email" :label="$t('subscribers.email')" sortable>
|
||||
<b-table-column field="email" :label="$t('subscribers.email')"
|
||||
header-class="cy-email" sortable>
|
||||
<a :href="`/subscribers/${props.row.id}`"
|
||||
@click.prevent="showEditForm(props.row)">
|
||||
{{ props.row.email }}
|
||||
</a>
|
||||
<b-taglist>
|
||||
<router-link :to="`/subscribers/lists/${props.row.id}`">
|
||||
<b-tag :class="l.subscriptionStatus" v-for="l in props.row.lists"
|
||||
size="is-small" :key="l.id">
|
||||
<template v-for="l in props.row.lists">
|
||||
<router-link :to="`/subscribers/lists/${l.id}`"
|
||||
v-bind:key="l.id" style="padding-right:0.5em;">
|
||||
<b-tag :class="l.subscriptionStatus" size="is-small" :key="l.id">
|
||||
{{ l.name }}
|
||||
<sup>{{ $t('subscribers.status.'+ l.subscriptionStatus) }}</sup>
|
||||
</b-tag>
|
||||
</router-link>
|
||||
</template>
|
||||
</b-taglist>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="name" :label="$t('globals.fields.name')" sortable>
|
||||
<b-table-column field="name" :label="$t('globals.fields.name')"
|
||||
header-class="cy-name" sortable>
|
||||
<a :href="`/subscribers/${props.row.id}`"
|
||||
@click.prevent="showEditForm(props.row)">
|
||||
{{ props.row.name }}
|
||||
</a>
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="lists" :label="$t('globals.terms.lists')" numeric centered>
|
||||
<b-table-column field="lists" :label="$t('globals.terms.lists')"
|
||||
header-class="cy-lists" numeric centered>
|
||||
{{ listCount(props.row.lists) }}
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="created_at" :label="$t('globals.fields.createdAt')" sortable>
|
||||
<b-table-column field="created_at" :label="$t('globals.fields.createdAt')"
|
||||
header-class="cy-created_at" sortable>
|
||||
{{ $utils.niceDate(props.row.createdAt) }}
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column field="updated_at" :label="$t('globals.fields.updatedAt')" sortable>
|
||||
<b-table-column field="updated_at" :label="$t('globals.fields.updatedAt')"
|
||||
header-class="cy-updated_at" sortable>
|
||||
{{ $utils.niceDate(props.row.updatedAt) }}
|
||||
</b-table-column>
|
||||
|
||||
<b-table-column class="actions" align="right">
|
||||
<div>
|
||||
<a :href="`/api/subscribers/${props.row.id}/export`">
|
||||
<a :href="`/api/subscribers/${props.row.id}/export`" data-cy="btn-download">
|
||||
<b-tooltip :label="$t('subscribers.downloadData')" type="is-dark">
|
||||
<b-icon icon="cloud-download-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a :href="`/subscribers/${props.row.id}`"
|
||||
@click.prevent="showEditForm(props.row)">
|
||||
@click.prevent="showEditForm(props.row)" data-cy="btn-edit">
|
||||
<b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
|
||||
<b-icon icon="pencil-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a href='' @click.prevent="deleteSubscriber(props.row)">
|
||||
<a href='' @click.prevent="deleteSubscriber(props.row)" data-cy="btn-delete">
|
||||
<b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
|
||||
<b-icon icon="trash-can-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
|
@ -243,7 +255,7 @@ export default Vue.extend({
|
|||
methods: {
|
||||
// Count the lists from which a subscriber has not unsubscribed.
|
||||
listCount(lists) {
|
||||
return lists.reduce((defVal, item) => (defVal + item.status !== 'unsubscribed' ? 1 : 0), 0);
|
||||
return lists.reduce((defVal, item) => (defVal + (item.subscriptionStatus !== 'unsubscribed' ? 1 : 0)), 0);
|
||||
},
|
||||
|
||||
toggleAdvancedSearch() {
|
||||
|
@ -343,11 +355,7 @@ export default Vue.extend({
|
|||
this.$api.deleteSubscriber(sub.id).then(() => {
|
||||
this.querySubscribers();
|
||||
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.deleted', { name: sub.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.deleted', { name: sub.name }));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
@ -394,11 +402,7 @@ export default Vue.extend({
|
|||
.then(() => {
|
||||
this.querySubscribers();
|
||||
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('subscribers.subscribersDeleted', { num: this.numSelectedSubscribers }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('subscribers.subscribersDeleted', { num: this.numSelectedSubscribers }));
|
||||
});
|
||||
};
|
||||
} else {
|
||||
|
@ -410,11 +414,8 @@ export default Vue.extend({
|
|||
}).then(() => {
|
||||
this.querySubscribers();
|
||||
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('subscribers.subscribersDeleted', { num: this.numSelectedSubscribers }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('subscribers.subscribersDeleted',
|
||||
{ num: this.numSelectedSubscribers }));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -442,11 +443,7 @@ export default Vue.extend({
|
|||
|
||||
fn(data).then(() => {
|
||||
this.querySubscribers();
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('subscribers.listChangeApplied'),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('subscribers.listChangeApplied'));
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
</header>
|
||||
<section expanded class="modal-card-body">
|
||||
<b-field :label="$t('globals.fields.name')" label-position="on-border">
|
||||
<b-input :maxlength="200" :ref="'focus'" v-model="form.name"
|
||||
placeholder="$t('globals.fields.name')" required></b-input>
|
||||
<b-input :maxlength="200" :ref="'focus'" v-model="form.name" name="name"
|
||||
:placeholder="$t('globals.fields.name')" required />
|
||||
</b-field>
|
||||
|
||||
<b-field :label="$t('globals.fields.rawHTML')" label-position="on-border">
|
||||
<b-input v-model="form.body" type="textarea" required />
|
||||
<b-field :label="$t('templates.rawHTML')" label-position="on-border">
|
||||
<b-input v-model="form.body" type="textarea" name="body" required />
|
||||
</b-field>
|
||||
|
||||
<p class="is-size-7">
|
||||
|
@ -98,11 +98,7 @@ export default Vue.extend({
|
|||
this.$api.createTemplate(data).then((d) => {
|
||||
this.$emit('finished');
|
||||
this.$parent.close();
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.created', { name: d.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.created', { name: d.name }));
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -116,11 +112,7 @@ export default Vue.extend({
|
|||
this.$api.updateTemplate(data).then((d) => {
|
||||
this.$emit('finished');
|
||||
this.$parent.close();
|
||||
this.$buefy.toast.open({
|
||||
message: `'${d.name}' updated`,
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(`'${d.name}' updated`);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
@ -32,35 +32,45 @@
|
|||
|
||||
<b-table-column class="actions" align="right">
|
||||
<div>
|
||||
<a href="#" @click.prevent="previewTemplate(props.row)">
|
||||
<a href="#" @click.prevent="previewTemplate(props.row)" data-cy="btn-preview">
|
||||
<b-tooltip :label="$t('templates.preview')" type="is-dark">
|
||||
<b-icon icon="file-find-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a href="#" @click.prevent="showEditForm(props.row)">
|
||||
<a href="#" @click.prevent="showEditForm(props.row)" data-cy="btn-edit">
|
||||
<b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
|
||||
<b-icon icon="pencil-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a href="" @click.prevent="$utils.prompt(`Clone template`,
|
||||
{ placeholder: 'Name', value: `Copy of ${props.row.name}`},
|
||||
(name) => cloneTemplate(name, props.row))">
|
||||
(name) => cloneTemplate(name, props.row))"
|
||||
data-cy="btn-clone">
|
||||
<b-tooltip :label="$t('globals.buttons.clone')" type="is-dark">
|
||||
<b-icon icon="file-multiple-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a v-if="!props.row.isDefault" href="#"
|
||||
@click.prevent="$utils.confirm(null, () => makeTemplateDefault(props.row))">
|
||||
@click.prevent="$utils.confirm(null, () => makeTemplateDefault(props.row))"
|
||||
data-cy="btn-set-default">
|
||||
<b-tooltip :label="$t('templates.makeDefault')" type="is-dark">
|
||||
<b-icon icon="check-circle-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<a v-if="!props.row.isDefault"
|
||||
href="#" @click.prevent="$utils.confirm(null, () => deleteTemplate(props.row))">
|
||||
<span v-else class="a has-text-grey-light">
|
||||
<b-icon icon="check-circle-outline" size="is-small" />
|
||||
</span>
|
||||
|
||||
<a v-if="!props.row.isDefault" href="#"
|
||||
@click.prevent="$utils.confirm(null, () => deleteTemplate(props.row))"
|
||||
data-cy="btn-delete">
|
||||
<b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
|
||||
<b-icon icon="trash-can-outline" size="is-small" />
|
||||
</b-tooltip>
|
||||
</a>
|
||||
<span v-else class="a has-text-grey-light">
|
||||
<b-icon icon="trash-can-outline" size="is-small" />
|
||||
</span>
|
||||
</div>
|
||||
</b-table-column>
|
||||
</template>
|
||||
|
@ -140,35 +150,21 @@ export default Vue.extend({
|
|||
this.$api.createTemplate(data).then((d) => {
|
||||
this.$api.getTemplates();
|
||||
this.$emit('finished');
|
||||
this.$buefy.toast.open({
|
||||
message: `'${d.name}' created`,
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(`'${d.name}' created`);
|
||||
});
|
||||
},
|
||||
|
||||
makeTemplateDefault(tpl) {
|
||||
this.$api.makeTemplateDefault(tpl.id).then(() => {
|
||||
this.$api.getTemplates();
|
||||
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.created', { name: tpl.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.created', { name: tpl.name }));
|
||||
});
|
||||
},
|
||||
|
||||
deleteTemplate(tpl) {
|
||||
this.$api.deleteTemplate(tpl.id).then(() => {
|
||||
this.$api.getTemplates();
|
||||
|
||||
this.$buefy.toast.open({
|
||||
message: this.$t('globals.messages.deleted', { name: tpl.name }),
|
||||
type: 'is-success',
|
||||
queue: false,
|
||||
});
|
||||
this.$utils.toast(this.$t('globals.messages.deleted', { name: tpl.name }));
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
712
frontend/yarn.lock
vendored
712
frontend/yarn.lock
vendored
File diff suppressed because it is too large
Load diff
6
go.mod
6
go.mod
|
@ -3,6 +3,7 @@ module github.com/knadh/listmonk
|
|||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/gofrs/uuid v3.2.0+incompatible
|
||||
|
@ -16,13 +17,16 @@ require (
|
|||
github.com/labstack/gommon v0.3.0 // indirect
|
||||
github.com/lib/pq v1.3.0
|
||||
github.com/mailru/easyjson v0.7.6
|
||||
github.com/mitchellh/copystructure v1.1.2 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.4 // indirect
|
||||
github.com/rhnvrm/simples3 v0.5.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 // indirect
|
||||
github.com/yuin/goldmark v1.3.4 // indirect
|
||||
golang.org/x/mod v0.3.0
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/volatiletech/null.v6 v6.0.0-20170828023728-0bef4e07ae1b
|
||||
)
|
||||
|
||||
replace github.com/imdario/mergo => github.com/imdario/mergo v0.3.8
|
||||
|
|
40
go.sum
40
go.sum
|
@ -1,5 +1,11 @@
|
|||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
|
||||
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -15,8 +21,14 @@ github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
|
|||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
|
||||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195 h1:j0UEFmS7wSjAwKEIkgKBn8PRDfjcuggzr93R9wk53nQ=
|
||||
github.com/jaytaylor/html2text v0.0.0-20200220170450-61d9dc4d7195/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||
|
@ -40,7 +52,6 @@ github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8
|
|||
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
|
@ -55,8 +66,14 @@ github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+tw
|
|||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.1.2 h1:Th2TIvG1+6ma3e/0/bopBKohOTY7s4dA8V2q4EUcBJ0=
|
||||
github.com/mitchellh/copystructure v1.1.2/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
|
||||
github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
|
||||
github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
|
||||
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||
|
@ -67,35 +84,39 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rhnvrm/simples3 v0.5.0 h1:X+WX0hqoKScdoJAw/G3GArfZ6Ygsn8q+6MdocTMKXOw=
|
||||
github.com/rhnvrm/simples3 v0.5.0/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
github.com/yuin/goldmark v1.3.4 h1:pd9FbZYGoTk0XaRHfu9oRrAiD8F5/MVZ1aMgLK2+S/w=
|
||||
github.com/yuin/goldmark v1.3.4/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -103,7 +124,6 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
|
@ -113,13 +133,11 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/volatiletech/null.v6 v6.0.0-20170828023728-0bef4e07ae1b h1:P+3+n9hUbqSDkSdtusWHVPQRrpRpLiLFzlZ02xXskM0=
|
||||
gopkg.in/volatiletech/null.v6 v6.0.0-20170828023728-0bef4e07ae1b/go.mod h1:0LRKfykySnChgQpG3Qpk+bkZFWazQ+MMfc5oldQCwnY=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
86
i18n/de.json
86
i18n/de.json
|
@ -2,10 +2,11 @@
|
|||
"_.code": "de",
|
||||
"_.name": "Deutsch (de)",
|
||||
"admin.errorMarshallingConfig": "Fehler beim einlesen der Konfigration: {error}",
|
||||
"campaigns.addAltText": "Add alternate plain text message",
|
||||
"campaigns.cantUpdate": "Eine laufende oder abgeschlossene Kampagne kann nicht geändert werden",
|
||||
"campaigns.clicks": "Klicks",
|
||||
"campaigns.confirmDelete": "Lösche {name}",
|
||||
"campaigns.confirmSchedule": "Diese Kampagne started zu einem konfigurierten Zeitpunkt. Jetzt starten?",
|
||||
"campaigns.confirmSchedule": "Diese Kampagne startet zu einem konfigurierten Zeitpunkt. Jetzt starten?",
|
||||
"campaigns.confirmSwitchFormat": "Du wirst die Formatierung des Inhalts vielleicht verlieren. Fortfahren?",
|
||||
"campaigns.content": "Inhalt",
|
||||
"campaigns.contentHelp": "Inhalt hier",
|
||||
|
@ -13,8 +14,8 @@
|
|||
"campaigns.copyOf": "Kopie von {name}",
|
||||
"campaigns.dateAndTime": "Datum und Zeit",
|
||||
"campaigns.ended": "Beendet",
|
||||
"campaigns.errorSendTest": "Fehler beim senden der Testmail: {error}",
|
||||
"campaigns.fieldInvalidBody": "Fehler beim erstellen des Kampagneninhalts: {error}",
|
||||
"campaigns.errorSendTest": "Fehler beim Senden der Testmail: {error}",
|
||||
"campaigns.fieldInvalidBody": "Fehler beim Erstellen des Kampagneninhalts: {error}",
|
||||
"campaigns.fieldInvalidFromEmail": "Ungültiges Format `from_email`.",
|
||||
"campaigns.fieldInvalidListIDs": "Ungültige Listen IDs.",
|
||||
"campaigns.fieldInvalidMessenger": "Unbekannter Messenger {name}.",
|
||||
|
@ -24,7 +25,8 @@
|
|||
"campaigns.fromAddress": "Absender Adresse",
|
||||
"campaigns.fromAddressPlaceholder": "Dein Name <noreply@deineseite.de>",
|
||||
"campaigns.invalid": "Ungültige Kampagne",
|
||||
"campaigns.needsSendAt": "Die Kampgane benötigt eine `send_at` Sendedatum um automatisch verschickt zu werden.",
|
||||
"campaigns.markdown": "Markdown",
|
||||
"campaigns.needsSendAt": "Die Kampagne benötigt eine `send_at` Sendedatum um automatisch verschickt zu werden.",
|
||||
"campaigns.newCampaign": "Neue Kampagne",
|
||||
"campaigns.noKnownSubsToTest": "Keine Abonnenten für den Test vorhanden.",
|
||||
"campaigns.noOptinLists": "Keine Opt-In Liste gefunden um die Kampagne anzulegen.",
|
||||
|
@ -42,6 +44,7 @@
|
|||
"campaigns.progress": "Fortschritt",
|
||||
"campaigns.queryPlaceholder": "Name oder Betreff",
|
||||
"campaigns.rawHTML": "HTML Code",
|
||||
"campaigns.removeAltText": "Remove alternate plain text message",
|
||||
"campaigns.richText": "Rich text",
|
||||
"campaigns.schedule": "Kampagne planen",
|
||||
"campaigns.scheduled": "geplant",
|
||||
|
@ -71,7 +74,7 @@
|
|||
"dashboard.linkClicks": "Linkklicks",
|
||||
"dashboard.messagesSent": "Nachrichten gesendet",
|
||||
"dashboard.orphanSubs": "Verwaiste",
|
||||
"email.data.info": "Eine Kopie aller gespeicherten Daten sind in der angehängten JSON datei gespeichert. Sie kann in einem Texteditor angezeigt werden.",
|
||||
"email.data.info": "Eine Kopie aller gespeicherten Daten sind in der angehängten JSON-Datei gespeichert. Sie kann in einem Texteditor angezeigt werden.",
|
||||
"email.data.title": "Deine Daten",
|
||||
"email.optin.confirmSub": "Abonnement bestätigen",
|
||||
"email.optin.confirmSubHelp": "Bestätige dein Abonnement mit einem Klick auf den nachfolgenden Knopf.",
|
||||
|
@ -89,10 +92,12 @@
|
|||
"email.unsub": "Abmelden",
|
||||
"email.unsubHelp": "Du möchtest diese E-Mails nicht mehr?",
|
||||
"forms.formHTML": "Formular HTML",
|
||||
"forms.formHTMLHelp": "Benutze den folgenden HTML Code um das Formular zum anmelden auf einer externen Seite anzuseigen. Das Formuar sollte das `email` Feld und eins oder mehr `l` (Listen UUID) Felder. `name` ist optional.",
|
||||
"forms.formHTMLHelp": "Benutze den folgenden HTML Code um das Formular zum Anmelden auf einer externen Seite anzuseigen. Das Formular sollte das `email` Feld und eins oder mehr `l` (Listen UUID) Felder enthalten. `name` ist optional.",
|
||||
"forms.noPublicLists": "There are no public lists to generate a forms.",
|
||||
"forms.publicLists": "Öffentliche Listen",
|
||||
"forms.selectHelp": "Wähle die Listen die du zum Formulat hinzufügen möchtest.",
|
||||
"forms.title": "Formulate",
|
||||
"forms.publicSubPage": "Public subscription page",
|
||||
"forms.selectHelp": "Wähle die Listen die du zum Formular hinzufügen möchtest.",
|
||||
"forms.title": "Formulare",
|
||||
"globals.buttons.add": "Hinzufügen",
|
||||
"globals.buttons.addNew": "Neu hinzufügen",
|
||||
"globals.buttons.cancel": "Abbrechen",
|
||||
|
@ -108,13 +113,13 @@
|
|||
"globals.buttons.remove": "Entfernen",
|
||||
"globals.buttons.save": "Speichern",
|
||||
"globals.buttons.saveChanges": "Änderungen speichern",
|
||||
"globals.days.0": "So",
|
||||
"globals.days.1": "Mo",
|
||||
"globals.days.2": "Di",
|
||||
"globals.days.3": "Mi",
|
||||
"globals.days.4": "Do",
|
||||
"globals.days.5": "Fr",
|
||||
"globals.days.6": "Sa",
|
||||
"globals.days.7": "So",
|
||||
"globals.fields.createdAt": "Erstellt",
|
||||
"globals.fields.id": "ID",
|
||||
"globals.fields.name": "Name",
|
||||
|
@ -126,11 +131,11 @@
|
|||
"globals.messages.created": "\"{name}\" erstellt",
|
||||
"globals.messages.deleted": "\"{name}\" gelöscht",
|
||||
"globals.messages.emptyState": "Hier ist nichts",
|
||||
"globals.messages.errorCreating": "Fehler beim erstellen von {name}: {error}",
|
||||
"globals.messages.errorDeleting": "Fehler beim löschen von {name}: {error}",
|
||||
"globals.messages.errorFetching": "Fehler beim abrufen von {name}: {error}",
|
||||
"globals.messages.errorUUID": "Fehler beim erzeugen einer UUID: {error}",
|
||||
"globals.messages.errorUpdating": "Fehler beim aktualisieren von {name}: {error}",
|
||||
"globals.messages.errorCreating": "Fehler beim Erstellen von {name}: {error}",
|
||||
"globals.messages.errorDeleting": "Fehler beim Löschen von {name}: {error}",
|
||||
"globals.messages.errorFetching": "Fehler beim Abrufen von {name}: {error}",
|
||||
"globals.messages.errorUUID": "Fehler beim Erzeugen einer UUID: {error}",
|
||||
"globals.messages.errorUpdating": "Fehler beim Aktualisieren von {name}: {error}",
|
||||
"globals.messages.invalidID": "Ungültige ID",
|
||||
"globals.messages.invalidUUID": "Ungültige UUID",
|
||||
"globals.messages.notFound": "{name} nicht gefunden",
|
||||
|
@ -165,13 +170,13 @@
|
|||
"globals.terms.templates": "Templates",
|
||||
"import.alreadyRunning": "Es läuft gerade ein Importvorgang. Bitte warte bis dieser beendet ist und versuche es noch einmal.",
|
||||
"import.blocklist": "Sperrliste",
|
||||
"import.csvDelim": "CSV Trennzeichen",
|
||||
"import.csvDelimHelp": "Standard Trennzeichen ist Komma.",
|
||||
"import.csvDelim": "CSV-Trennzeichen",
|
||||
"import.csvDelimHelp": "Standard-Trennzeichen ist Komma.",
|
||||
"import.csvExample": "Beispiel CSV(Rohdaten)",
|
||||
"import.csvFile": "CSV oder ZIP Datei",
|
||||
"import.csvFileHelp": "Klicke oder ziehe eine CSV oder ZIP Datei hierher",
|
||||
"import.errorCopyingFile": "Fehler beim kopieren der Datei: {error}",
|
||||
"import.errorProcessingZIP": "Fehler beim verarbeiten der ZIP Datei: {error}",
|
||||
"import.csvFile": "CSV- oder ZIP-Datei",
|
||||
"import.csvFileHelp": "Klicke oder ziehe eine CSV- oder ZIP-Datei hierher",
|
||||
"import.errorCopyingFile": "Fehler beim Kopieren der Datei: {error}",
|
||||
"import.errorProcessingZIP": "Fehler beim Verarbeiten der ZIP Datei: {error}",
|
||||
"import.errorStarting": "Fehler beim Import: {error}",
|
||||
"import.importDone": "Abgeschlossen",
|
||||
"import.importStarted": "Import gestartet",
|
||||
|
@ -180,8 +185,8 @@
|
|||
"import.invalidDelim": "`delim` muss ein einzelnes Zeichen sein",
|
||||
"import.invalidFile": "Ungültige Datei: {error}",
|
||||
"import.invalidMode": "Ungültiger Modus",
|
||||
"import.invalidParams": "Ungüliger Parameter: {error}",
|
||||
"import.listSubHelp": "Listen die Abonniert werden.",
|
||||
"import.invalidParams": "Ungültiger Parameter: {error}",
|
||||
"import.listSubHelp": "Listen die abonniert werden.",
|
||||
"import.mode": "Mode",
|
||||
"import.overwrite": "Überschreiben?",
|
||||
"import.overwriteHelp": "Überschreibe Name und Attribute von bestehenden Abonnenten?",
|
||||
|
@ -202,14 +207,14 @@
|
|||
"lists.sendCampaign": "Kampagne abschicken",
|
||||
"lists.sendOptinCampaign": "Opt-In Kampagne senden",
|
||||
"lists.type": "Typ",
|
||||
"lists.typeHelp": "Öffentliche Listen können von allen abonniert werden. Die namen der Abonnenten könnten auf einer Öffentlichen Seite, wie der Verwaltungsseite auftauceh.",
|
||||
"lists.typeHelp": "Öffentliche Listen können von allen abonniert werden. Die Namen der Abonnenten könnten auf einer öffentlichen Seite, wie der Verwaltungsseite auftauchen.",
|
||||
"lists.types.private": "Privat",
|
||||
"lists.types.public": "Öffentlich",
|
||||
"logs.title": "Logs",
|
||||
"media.errorReadingFile": "Fehler beim lesen der Datei: {error}",
|
||||
"media.errorResizing": "Fehler beim anpassen der Größe des Bildes: {error}",
|
||||
"media.errorSavingThumbnail": "Fehler beim speichern des Thumbnails: {error}",
|
||||
"media.errorUploading": "Fehler beim hochladen der Datei: {error}",
|
||||
"media.errorReadingFile": "Fehler beim Lesen der Datei: {error}",
|
||||
"media.errorResizing": "Fehler beim Anpassen der Größe des Bildes: {error}",
|
||||
"media.errorSavingThumbnail": "Fehler beim Speichern des Thumbnails: {error}",
|
||||
"media.errorUploading": "Fehler beim Hochladen der Datei: {error}",
|
||||
"media.invalidFile": "Ungültige Datei: {error}",
|
||||
"media.title": "Medien",
|
||||
"media.unsupportedFileType": "Nicht unterstützter Dateityp ({type})",
|
||||
|
@ -237,12 +242,14 @@
|
|||
"public.dataSentTitle": "Daten gesendet",
|
||||
"public.errorFetchingCampaign": "Fehler beim abrufen der E-Mail",
|
||||
"public.errorFetchingEmail": "E-Mail Nachricht nicht gefunden",
|
||||
"public.errorFetchingLists": "Fehler beim abrufen der Listen. Bitte noch einmal probieren.",
|
||||
"public.errorFetchingLists": "Fehler beim Abrufen der Listen. Bitte noch einmal probieren.",
|
||||
"public.errorProcessingRequest": "Fehler bei der Anfrage. Bitte noch einmal probieren.",
|
||||
"public.errorTitle": "Fehler",
|
||||
"public.invalidFeature": "Dieses Feature ist nicht verfügbar",
|
||||
"public.invalidLink": "Ungültiger Link",
|
||||
"public.noSubInfo": "Es gibt keine zu Bestätigenden Abonnements",
|
||||
"public.noListsAvailable": "Keine Listen zum Abonnieren verfügbar.",
|
||||
"public.noListsSelected": "Keine Liste zum Abonnieren ausgewählt.",
|
||||
"public.noSubInfo": "Es gibt keine zu bestätigenden Abonnements",
|
||||
"public.noSubTitle": "Keine Abonnements",
|
||||
"public.notFoundTitle": "Nicht gefunden",
|
||||
"public.privacyConfirmWipe": "Bist du sicher, dass du alle Abonnements und Daten löschen möchtest?",
|
||||
|
@ -251,10 +258,14 @@
|
|||
"public.privacyTitle": "Privatsphäre und Datenschutz",
|
||||
"public.privacyWipe": "Alle Daten löschen.",
|
||||
"public.privacyWipeHelp": "Alle deine Daten und Abonnements, sowie die dazugehörigen Daten werden dauerhaft gelöscht.",
|
||||
"public.sub": "Abonnieren",
|
||||
"public.subConfirmed": "Abonnement erfolgreich",
|
||||
"public.subConfirmedTitle": "Bestätigt",
|
||||
"public.subName": "Name (optional)",
|
||||
"public.subNotFound": "Abonnement nicht gefunden.",
|
||||
"public.subOptinPending": "Dir wurde eine E-Mail zur Bestätigung geschickt.",
|
||||
"public.subPrivateList": "Private Liste",
|
||||
"public.subTitle": "Abonnieren",
|
||||
"public.unsub": "Abmelden",
|
||||
"public.unsubFull": "Auch von allen zukünftigen E-Mails abmelden.",
|
||||
"public.unsubHelp": "Möchtest du dich von der Liste abmelden?",
|
||||
|
@ -262,11 +273,16 @@
|
|||
"public.unsubbedInfo": "Du wurdest erfolgreich abgemeldet",
|
||||
"public.unsubbedTitle": "Abgemeldet",
|
||||
"public.unsubscribeTitle": "Von einer Liste abmelden.",
|
||||
"settings.confirmRestart": "Ensure running campaigns are paused. Restart?",
|
||||
"settings.duplicateMessengerName": "Doppelter Nachrichtendienstname: {name}",
|
||||
"settings.errorEncoding": "Fehler bei der Codierung der Einstellungen: {error}",
|
||||
"settings.errorNoSMTP": "Mindestens ein SMTP Block muss aktiviert sein",
|
||||
"settings.general.adminNotifEmails": "Admin Benachrichtigungen",
|
||||
"settings.general.adminNotifEmailsHelp": "Komma getrennte Liste von E-Mail Adressen welche Admin Benachrichtigungen erhalten. Wie Importupdates, Fertigstellung von Kapganen, Fehler usw.",
|
||||
"settings.general.checkUpdates": "Check for updates",
|
||||
"settings.general.checkUpdatesHelp": "Periodically check for new app releases and notify.",
|
||||
"settings.general.enablePublicSubPage": "Enable public subscription page",
|
||||
"settings.general.enablePublicSubPageHelp": "Show a public subscription page with all the public lists for people to subscribe.",
|
||||
"settings.general.faviconURL": "Favicon URL",
|
||||
"settings.general.faviconURLHelp": "(Optional) komplette URL für ein statisches Favicon für die angezeigten Seiten (wie Abmelden).",
|
||||
"settings.general.fromEmail": "Standard `von` E-Mail",
|
||||
|
@ -310,6 +326,7 @@
|
|||
"settings.messengers.url": "URL",
|
||||
"settings.messengers.urlHelp": "Root URL des Postback servers.",
|
||||
"settings.messengers.username": "Benutzername",
|
||||
"settings.needsRestart": "Settings changed. Pause all running campaigns and restart the app",
|
||||
"settings.performance.batchSize": "Batchgröße",
|
||||
"settings.performance.batchSizeHelp": "Die Anzahl der Abonnenten die gleichzeitig von der Datenbank geladen werden. Jeder Schritt holt die Abonnenten und schickt die Nachrichten. Dies sollte idealerweise höher sein als der maximal erreichbare Durchsatz (Anzahl Threads * Nachrichtenrate).",
|
||||
"settings.performance.concurrency": "Anzahl Threads",
|
||||
|
@ -336,6 +353,7 @@
|
|||
"settings.privacy.listUnsubHeader": "Inkludiere `List-Unsubscribe` Header",
|
||||
"settings.privacy.listUnsubHeaderHelp": "Inkludiere Header zum einfachen Abmelden in den E-Mails. Erlaubt den E-Mail Klients den Usern einen Ein Klick Abmeldug anzubieten.",
|
||||
"settings.privacy.name": "Privatsphäre",
|
||||
"settings.restart": "Restart",
|
||||
"settings.smtp.authProtocol": "Autentifizierungsprotokoll",
|
||||
"settings.smtp.customHeaders": "Benutzerdefinierte Header",
|
||||
"settings.smtp.customHeadersHelp": "(Optional) Array von Benutzerdefinierten E-Mail Headern welche in die Nachricht eingefügt werden sollen. Z.B.: [{\"X-Custom\": \"value\"}, {\"X-Custom2\": \"value\"}]",
|
||||
|
@ -364,6 +382,7 @@
|
|||
"settings.smtp.waitTimeout": "Maximale Wartezeit",
|
||||
"settings.smtp.waitTimeoutHelp": "Wartezeit auf neue Aktivität bevor eine Verbindung geschlossen wird. (s für Sekunden, m für Minuten).",
|
||||
"settings.title": "Einstellungen",
|
||||
"settings.updateAvailable": "A new update {version} is available.",
|
||||
"subscribers.advancedQuery": "Erweitert",
|
||||
"subscribers.advancedQueryHelp": "Partieller SQL Ausdruck um Attribute der Abonnenten abzufragen",
|
||||
"subscribers.attribs": "Attribute",
|
||||
|
@ -376,11 +395,11 @@
|
|||
"subscribers.email": "E-Mail",
|
||||
"subscribers.emailExists": "E-Mail existiert bereits",
|
||||
"subscribers.errorBlocklisting": "Fehler, Abonnement ist geblockt: {error}",
|
||||
"subscribers.errorInvalidIDs": "Eine oder meherer IDs sind ungültig: {error}",
|
||||
"subscribers.errorInvalidIDs": "Eine oder mehrere IDs sind ungültig: {error}",
|
||||
"subscribers.errorNoIDs": "Keine IDs Angegeben",
|
||||
"subscribers.errorNoListsGiven": "Keine Listen angegeben",
|
||||
"subscribers.errorPreparingQuery": "Fehler beim vorbereiten der Abonnentenabfrage: {error}",
|
||||
"subscribers.errorSendingOptin": "Fehler beim sender der Opt-In E-Mail",
|
||||
"subscribers.errorPreparingQuery": "Fehler beim Vorbereiten der Abonnentenabfrage: {error}",
|
||||
"subscribers.errorSendingOptin": "Fehler beim Senden der Opt-In E-Mail",
|
||||
"subscribers.export": "Export",
|
||||
"subscribers.invalidAction": "Ungültiger Vorgang",
|
||||
"subscribers.invalidEmail": "Ungültige E-Mail",
|
||||
|
@ -400,6 +419,7 @@
|
|||
"subscribers.reset": "Zurücksetzen",
|
||||
"subscribers.selectAll": "Wähle alle {num}",
|
||||
"subscribers.status.blocklisted": "Blockiert",
|
||||
"subscribers.status.confirmed": "Confirmed",
|
||||
"subscribers.status.enabled": "Aktiviert",
|
||||
"subscribers.status.subscribed": "Angemeldet",
|
||||
"subscribers.status.unconfirmed": "Bestätigung ausstehend",
|
||||
|
@ -409,7 +429,7 @@
|
|||
"templates.default": "Standard",
|
||||
"templates.dummyName": "Test Kampagne",
|
||||
"templates.dummySubject": "Test Kampagnen name",
|
||||
"templates.errorCompiling": "Fehler beim kompilieren des Templates: {error}",
|
||||
"templates.errorCompiling": "Fehler beim Kompilieren des Templates: {error}",
|
||||
"templates.errorRendering": "Fehler beim Rendern der Nachricht: {error}",
|
||||
"templates.fieldInvalidName": "Ungültige Länge für `name`.",
|
||||
"templates.makeDefault": "Als Standard",
|
||||
|
|
13
i18n/en.json
13
i18n/en.json
|
@ -25,6 +25,7 @@
|
|||
"campaigns.fromAddress": "From address",
|
||||
"campaigns.fromAddressPlaceholder": "Your Name <noreply@yoursite.com>",
|
||||
"campaigns.invalid": "Invalid campaign",
|
||||
"campaigns.markdown": "Markdown",
|
||||
"campaigns.needsSendAt": "Campaign needs a date to be scheduled.",
|
||||
"campaigns.newCampaign": "New campaign",
|
||||
"campaigns.noKnownSubsToTest": "No known subscribers to test.",
|
||||
|
@ -112,13 +113,13 @@
|
|||
"globals.buttons.remove": "Remove",
|
||||
"globals.buttons.save": "Save",
|
||||
"globals.buttons.saveChanges": "Save changes",
|
||||
"globals.days.0": "Sun",
|
||||
"globals.days.1": "Mon",
|
||||
"globals.days.2": "Tue",
|
||||
"globals.days.3": "Wed",
|
||||
"globals.days.4": "Thu",
|
||||
"globals.days.5": "Fri",
|
||||
"globals.days.6": "Sat",
|
||||
"globals.days.7": "Sun",
|
||||
"globals.fields.createdAt": "Created",
|
||||
"globals.fields.id": "ID",
|
||||
"globals.fields.name": "Name",
|
||||
|
@ -259,10 +260,10 @@
|
|||
"public.privacyWipeHelp": "Delete all your subscriptions and related data from the database permanently.",
|
||||
"public.sub": "Subscribe",
|
||||
"public.subConfirmed": "Subscribed successfully.",
|
||||
"public.subOptinPending": "An e-mail has been sent to you to confirm your subscription(s).",
|
||||
"public.subConfirmedTitle": "Confirmed",
|
||||
"public.subName": "Name (optional)",
|
||||
"public.subNotFound": "Subscription not found.",
|
||||
"public.subOptinPending": "An e-mail has been sent to you to confirm your subscription(s).",
|
||||
"public.subPrivateList": "Private list",
|
||||
"public.subTitle": "Subscribe",
|
||||
"public.unsub": "Unsubscribe",
|
||||
|
@ -272,15 +273,14 @@
|
|||
"public.unsubbedInfo": "You have unsubscribed successfully.",
|
||||
"public.unsubbedTitle": "Unsubscribed",
|
||||
"public.unsubscribeTitle": "Unsubscribe from mailing list",
|
||||
"settings.needsRestart": "Settings changed. Pause all running campaigns and restart the app",
|
||||
"settings.confirmRestart": "Ensure running campaigns are paused. Restart?",
|
||||
"settings.updateAvailable": "A new update {version} is available.",
|
||||
"settings.restart": "Restart",
|
||||
"settings.duplicateMessengerName": "Duplicate messenger name: {name}",
|
||||
"settings.errorEncoding": "Error encoding settings: {error}",
|
||||
"settings.errorNoSMTP": "At least one SMTP block should be enabled",
|
||||
"settings.general.adminNotifEmails": "Admin notification e-mails",
|
||||
"settings.general.adminNotifEmailsHelp": "Comma separated list of e-mail addresses to which admin notifications such as import updates, campaign completion, failure etc. should be sent.",
|
||||
"settings.general.checkUpdates": "Check for updates",
|
||||
"settings.general.checkUpdatesHelp": "Periodically check for new app releases and notify.",
|
||||
"settings.general.enablePublicSubPage": "Enable public subscription page",
|
||||
"settings.general.enablePublicSubPageHelp": "Show a public subscription page with all the public lists for people to subscribe.",
|
||||
"settings.general.faviconURL": "Favicon URL",
|
||||
|
@ -326,6 +326,7 @@
|
|||
"settings.messengers.url": "URL",
|
||||
"settings.messengers.urlHelp": "Root URL of the Postback server.",
|
||||
"settings.messengers.username": "Username",
|
||||
"settings.needsRestart": "Settings changed. Pause all running campaigns and restart the app",
|
||||
"settings.performance.batchSize": "Batch size",
|
||||
"settings.performance.batchSizeHelp": "The number of subscribers to pull from the database in a single iteration. Each iteration pulls subscribers from the database, sends messages to them, and then moves on to the next iteration to pull the next batch. This should ideally be higher than the maximum achievable throughput (concurrency * message_rate).",
|
||||
"settings.performance.concurrency": "Concurrency",
|
||||
|
@ -352,6 +353,7 @@
|
|||
"settings.privacy.listUnsubHeader": "Include `List-Unsubscribe` header",
|
||||
"settings.privacy.listUnsubHeaderHelp": "Include unsubscription headers that allow e-mail clients to allow users to unsubscribe in a single click.",
|
||||
"settings.privacy.name": "Privacy",
|
||||
"settings.restart": "Restart",
|
||||
"settings.smtp.authProtocol": "Auth protocol",
|
||||
"settings.smtp.customHeaders": "Custom headers",
|
||||
"settings.smtp.customHeadersHelp": "Optional array of e-mail headers to include in all messages sent from this server. eg: [{\"X-Custom\": \"value\"}, {\"X-Custom2\": \"value\"}]",
|
||||
|
@ -380,6 +382,7 @@
|
|||
"settings.smtp.waitTimeout": "Wait timeout",
|
||||
"settings.smtp.waitTimeoutHelp": "Time to wait for new activity on a connection before closing it and removing it from the pool (s for second, m for minute).",
|
||||
"settings.title": "Settings",
|
||||
"settings.updateAvailable": "A new update {version} is available.",
|
||||
"subscribers.advancedQuery": "Advanced",
|
||||
"subscribers.advancedQueryHelp": "Partial SQL expression to query subscriber attributes",
|
||||
"subscribers.attribs": "Attributes",
|
||||
|
|
440
i18n/es.json
Normal file
440
i18n/es.json
Normal file
|
@ -0,0 +1,440 @@
|
|||
{
|
||||
"_.code": "es",
|
||||
"_.name": "Español (es)",
|
||||
"admin.errorMarshallingConfig": "Error al ordenar la configuración: {error}",
|
||||
"campaigns.addAltText": "Agregar mensaje en texto plano alternativo",
|
||||
"campaigns.cantUpdate": "No es posible actualizar una campaña iniciada o finalizada.",
|
||||
"campaigns.clicks": "Clics",
|
||||
"campaigns.confirmDelete": "Eliminar {name}",
|
||||
"campaigns.confirmSchedule": "Esta campaña comenzará automáticamente en la fecha y hora establecida. ¿Agendar ahora?",
|
||||
"campaigns.confirmSwitchFormat": "Este contenido podría perder el formato. ¿Continuar?",
|
||||
"campaigns.content": "Contenido",
|
||||
"campaigns.contentHelp": "Contenido aqui",
|
||||
"campaigns.continue": "Continuar",
|
||||
"campaigns.copyOf": "Copia de {name}",
|
||||
"campaigns.dateAndTime": "Fecha y hora",
|
||||
"campaigns.ended": "Finalizado",
|
||||
"campaigns.errorSendTest": "Error al enviar la prueba: {error}",
|
||||
"campaigns.fieldInvalidBody": "Error al compilar el cuerpo de la campaña: {error}",
|
||||
"campaigns.fieldInvalidFromEmail": "Correo origen inválido.",
|
||||
"campaigns.fieldInvalidListIDs": "IDs de lista inválidos",
|
||||
"campaigns.fieldInvalidMessenger": "Mensajero desconocido {name}.",
|
||||
"campaigns.fieldInvalidName": "Largo de nombre inválido",
|
||||
"campaigns.fieldInvalidSendAt": "La hora agendada debe ser en el futuro.",
|
||||
"campaigns.fieldInvalidSubject": "Largo de asunto inválido",
|
||||
"campaigns.fromAddress": "Dirección origen",
|
||||
"campaigns.fromAddressPlaceholder": "Su Nombre <noresponder@susitio.com>",
|
||||
"campaigns.invalid": "Campaña inválida",
|
||||
"campaigns.markdown": "Reduccion",
|
||||
"campaigns.needsSendAt": "Una campaña necesita una fecha pra ser agendada.",
|
||||
"campaigns.newCampaign": "Nueva campaña",
|
||||
"campaigns.noKnownSubsToTest": "No existen subscriptores para probar.",
|
||||
"campaigns.noOptinLists": "No se encontraron listas para crear la campaña",
|
||||
"campaigns.noSubs": "No hay subscriptores en la lista seleccionada para crear la campaña",
|
||||
"campaigns.noSubsToTest": "No hay subscriptores objetivo.",
|
||||
"campaigns.notFound": "No se encontró la camapaña.",
|
||||
"campaigns.onlyActiveCancel": "Solo campañas activas pueden ser canceladas.",
|
||||
"campaigns.onlyActivePause": "Solo campañas activas pueden ser pausadas.",
|
||||
"campaigns.onlyDraftAsScheduled": "Solo campañas en borrador pueden ser agendadas.",
|
||||
"campaigns.onlyPausedDraft": "Solo campañas en borrador pueden ser comanzadas.",
|
||||
"campaigns.onlyScheduledAsDraft": "Solo campañas agendadas pueden ser guardadas como borrador.",
|
||||
"campaigns.pause": "Pausa",
|
||||
"campaigns.plainText": "Texto plano",
|
||||
"campaigns.preview": "Vista previa",
|
||||
"campaigns.progress": "Progreso",
|
||||
"campaigns.queryPlaceholder": "Nombre o asunto",
|
||||
"campaigns.rawHTML": "HTML crudo",
|
||||
"campaigns.removeAltText": "Remover mensaje en texto plano alternativo",
|
||||
"campaigns.richText": "Texto enriquecido",
|
||||
"campaigns.schedule": "Agendar campaña",
|
||||
"campaigns.scheduled": "Agendada",
|
||||
"campaigns.send": "Enviar",
|
||||
"campaigns.sendLater": "Enviar después",
|
||||
"campaigns.sendTest": "Enviar mensaje de prueba",
|
||||
"campaigns.sendTestHelp": "Presionar Enter después de escribir una dirección para agregar múltiples destinatarios. Las direcciones deben corresponder a subscriptores existentes.",
|
||||
"campaigns.sendToLists": "Listas a eviar a",
|
||||
"campaigns.sent": "Enviado",
|
||||
"campaigns.start": "Comenzar campaña",
|
||||
"campaigns.started": "\"{name}\" comenzada",
|
||||
"campaigns.startedAt": "Comenzada",
|
||||
"campaigns.stats": "Estadisticas",
|
||||
"campaigns.status.cancelled": "Candelada",
|
||||
"campaigns.status.draft": "Borrador",
|
||||
"campaigns.status.finished": "Finalizada",
|
||||
"campaigns.status.paused": "Pausada",
|
||||
"campaigns.status.running": "Corriendo",
|
||||
"campaigns.status.scheduled": "Agendada",
|
||||
"campaigns.statusChanged": "\"{name}\" fue {status}",
|
||||
"campaigns.subject": "Asunto",
|
||||
"campaigns.testEmails": "Correos electrónicos",
|
||||
"campaigns.testSent": "Mensaje de prueba enviado",
|
||||
"campaigns.timestamps": "Marca de timepo",
|
||||
"campaigns.views": "Vistas",
|
||||
"dashboard.campaignViews": "Vista de campañas",
|
||||
"dashboard.linkClicks": "Vinculos cliqueados",
|
||||
"dashboard.messagesSent": "Mensajes enviados",
|
||||
"dashboard.orphanSubs": "Huérfanos",
|
||||
"email.data.info": "Una copia de todos sus datos recopilados está adjunta en un archivo de formato JSON. Puede ser visto en un editor de textos.",
|
||||
"email.data.title": "Sus datos",
|
||||
"email.optin.confirmSub": "Subscripcion confirmada",
|
||||
"email.optin.confirmSubHelp": "Para confirmar su subscripción debe hacer clic en el siguiente botón.",
|
||||
"email.optin.confirmSubInfo": "Ud ha sido agregado a las siguientes listas:",
|
||||
"email.optin.confirmSubTitle": "Subscripción confirmada",
|
||||
"email.optin.confirmSubWelcome": "Hola",
|
||||
"email.optin.privateList": "Lista privada",
|
||||
"email.status.campaignReason": "Razón",
|
||||
"email.status.campaignSent": "Enviada",
|
||||
"email.status.campaignUpdateTitle": "Actualización de camáña",
|
||||
"email.status.importFile": "Archivo",
|
||||
"email.status.importRecords": "Registros",
|
||||
"email.status.importTitle": "Actualización importada",
|
||||
"email.status.status": "Estado",
|
||||
"email.unsub": "Des-subscribir",
|
||||
"email.unsubHelp": "¿No quiere recibir estos correos electrónicos?",
|
||||
"forms.formHTML": "Formulario HTML",
|
||||
"forms.formHTMLHelp": "Use este códgo HTML para mostrar el formulario de subscripcion en un sitio web externo. El formulario debe contener el campo \"correo electronico\" y uno o mas campos `l` (UUID de lista). El campo nombre es opcional.",
|
||||
"forms.noPublicLists": "No hay listas publicas para generar formularios",
|
||||
"forms.publicLists": "Listas públicas",
|
||||
"forms.publicSubPage": "Página de subscripción pública",
|
||||
"forms.selectHelp": "Seleccione las listas para agregar al formulario",
|
||||
"forms.title": "Formularios",
|
||||
"globals.buttons.add": "Agregar",
|
||||
"globals.buttons.addNew": "Agregar nuevo",
|
||||
"globals.buttons.cancel": "Cancelar",
|
||||
"globals.buttons.clone": "Clonar",
|
||||
"globals.buttons.close": "Cerrar",
|
||||
"globals.buttons.continue": "Continuar",
|
||||
"globals.buttons.delete": "Borrar",
|
||||
"globals.buttons.edit": "Editar",
|
||||
"globals.buttons.enabled": "Habilitar",
|
||||
"globals.buttons.learnMore": "Aprender mas",
|
||||
"globals.buttons.new": "Nuevo",
|
||||
"globals.buttons.ok": "Ok",
|
||||
"globals.buttons.remove": "Remover",
|
||||
"globals.buttons.save": "Guardar",
|
||||
"globals.buttons.saveChanges": "Guardar cambios",
|
||||
"globals.days.0": "Domingo",
|
||||
"globals.days.1": "Lunes",
|
||||
"globals.days.2": "Martes",
|
||||
"globals.days.3": "Miércoles",
|
||||
"globals.days.4": "Jueves",
|
||||
"globals.days.5": "Viernes",
|
||||
"globals.days.6": "Sábado",
|
||||
"globals.fields.createdAt": "Creado",
|
||||
"globals.fields.id": "ID",
|
||||
"globals.fields.name": "Nombre",
|
||||
"globals.fields.status": "Estado",
|
||||
"globals.fields.type": "Tipo",
|
||||
"globals.fields.updatedAt": "Actualizado",
|
||||
"globals.fields.uuid": "UUID",
|
||||
"globals.messages.confirm": "¿Está seguro?",
|
||||
"globals.messages.created": "\"{name}\" creado",
|
||||
"globals.messages.deleted": "\"{name}\" borrado",
|
||||
"globals.messages.emptyState": "Nada aqui",
|
||||
"globals.messages.errorCreating": "Error creando {name}: {error}",
|
||||
"globals.messages.errorDeleting": "Error borrando {name}: {error}",
|
||||
"globals.messages.errorFetching": "Error buscando {name}: {error}",
|
||||
"globals.messages.errorUUID": "Error generando UUID: {error}",
|
||||
"globals.messages.errorUpdating": "Error actualizando {name}: {error}",
|
||||
"globals.messages.invalidID": "ID inválido",
|
||||
"globals.messages.invalidUUID": "UUID inválido",
|
||||
"globals.messages.notFound": "{name} no encontrado",
|
||||
"globals.messages.passwordChange": "Ingresar una contraseña para cambiar",
|
||||
"globals.messages.updated": "\"{name}\" actualizado",
|
||||
"globals.months.1": "Enero",
|
||||
"globals.months.10": "Octubre",
|
||||
"globals.months.11": "Noviembre",
|
||||
"globals.months.12": "Diciembre",
|
||||
"globals.months.2": "Febrero",
|
||||
"globals.months.3": "Marzo",
|
||||
"globals.months.4": "Abril",
|
||||
"globals.months.5": "Mayo",
|
||||
"globals.months.6": "Junio",
|
||||
"globals.months.7": "Julio",
|
||||
"globals.months.8": "Agosto",
|
||||
"globals.months.9": "Setiembre",
|
||||
"globals.terms.campaign": "Campaña | Campañas",
|
||||
"globals.terms.campaigns": "Campañas",
|
||||
"globals.terms.dashboard": "Panel",
|
||||
"globals.terms.list": "Lista | Listas",
|
||||
"globals.terms.lists": "Listas",
|
||||
"globals.terms.media": "Media | Media",
|
||||
"globals.terms.messenger": "Mensajero | Mensajeros",
|
||||
"globals.terms.messengers": "Mensajeros",
|
||||
"globals.terms.settings": "Configuraciones",
|
||||
"globals.terms.subscriber": "Subscriptor | Subscriptores",
|
||||
"globals.terms.subscribers": "Subscriptores",
|
||||
"globals.terms.tag": "Tag | Tags",
|
||||
"globals.terms.tags": "Tags",
|
||||
"globals.terms.template": "Plantilla | Plantillas",
|
||||
"globals.terms.templates": "Plantillas",
|
||||
"import.alreadyRunning": "Una importacion está ejecutándose. Espere a que termine, o detengala antes de intentar otra vez.",
|
||||
"import.blocklist": "Lista de bloqueados",
|
||||
"import.csvDelim": "Delimitador CSV",
|
||||
"import.csvDelimHelp": "El delimitador por defecto es la coma ','",
|
||||
"import.csvExample": "Ejemplo de CSV en crudo",
|
||||
"import.csvFile": "Archivo CSV o ZIP",
|
||||
"import.csvFileHelp": "Seleccione o arrastre un archivo CSV o ZIP aquí",
|
||||
"import.errorCopyingFile": "Error copiando archivo: {error}",
|
||||
"import.errorProcessingZIP": "Error procesando archivo ZIP: {error}",
|
||||
"import.errorStarting": "Error comanzando la importacion {error}",
|
||||
"import.importDone": "Hecho",
|
||||
"import.importStarted": "Importacion comenzada",
|
||||
"import.instructions": "Instrucciones",
|
||||
"import.instructionsHelp": "Cargue un archivo CSV o un archivo ZIP con un único archivo CSV en él para importar subscriptores a granel.",
|
||||
"import.invalidDelim": "El delimitador debe ser un carácter único.",
|
||||
"import.invalidFile": "Archivo inválido: {error}",
|
||||
"import.invalidMode": "Modo inválido",
|
||||
"import.invalidParams": "Paramétros inválidos: {error}",
|
||||
"import.listSubHelp": "Listas a subscribir",
|
||||
"import.mode": "Modo",
|
||||
"import.overwrite": "¿Sobre-escribir?",
|
||||
"import.overwriteHelp": "¿Sobre-escribir nombre y atributos de subscriptores existentes?",
|
||||
"import.recordsCount": "{num} / {total} registros",
|
||||
"import.stopImport": "Detener importacion",
|
||||
"import.subscribe": "Subscribir",
|
||||
"import.title": "Importar subscriptores",
|
||||
"import.upload": "Cargar",
|
||||
"lists.confirmDelete": "¿Está seguro? Esto no elimina subscriptores",
|
||||
"lists.confirmSub": "Subscripcion confirmada a {name}",
|
||||
"lists.invalidName": "Nombre inválido",
|
||||
"lists.newList": "Nueva lista",
|
||||
"lists.optin": "Optar por por la inclusión (opt-in)",
|
||||
"lists.optinHelp": "Doble opt-in envía un correo al subscriptor consultando por su confirmación.. En las listas con la opción doble opt-in, las campañas son enviadas solo a subscriptores confirmados..",
|
||||
"lists.optinTo": "Opt-in a {name}",
|
||||
"lists.optins.double": "Doble opt-in",
|
||||
"lists.optins.single": "Simple opt-in",
|
||||
"lists.sendCampaign": "Enviar campaña",
|
||||
"lists.sendOptinCampaign": "Enviar campaña opt-in",
|
||||
"lists.type": "Tipo",
|
||||
"lists.typeHelp": "Las listas públicas están abiertas al mundo y sus nombres pueden aparecen en páginas públicas tales como páginas de gestión de subscripciones.",
|
||||
"lists.types.private": "Privada",
|
||||
"lists.types.public": "Pública",
|
||||
"logs.title": "Registros",
|
||||
"media.errorReadingFile": "Error leyendo archivo: {error}",
|
||||
"media.errorResizing": "Error cambiando tamaño de imágen: {error}",
|
||||
"media.errorSavingThumbnail": "Error guardando miniatura: {error}",
|
||||
"media.errorUploading": "Error cargando archivo: {error}",
|
||||
"media.invalidFile": "Archivo inválido: {error}",
|
||||
"media.title": "Media",
|
||||
"media.unsupportedFileType": "Tipo de archivo no soportado ({type})",
|
||||
"media.upload": "Cargar",
|
||||
"media.uploadHelp": "Seleccion o arrastre una o mas imágenes aqui",
|
||||
"media.uploadImage": "Cargar imágen",
|
||||
"menu.allCampaigns": "Todas las campañas",
|
||||
"menu.allLists": "Todas las listas",
|
||||
"menu.allSubscribers": "Todos los subscriptores",
|
||||
"menu.dashboard": "Tablero",
|
||||
"menu.forms": "Formularios",
|
||||
"menu.import": "Importar",
|
||||
"menu.logs": "Registros",
|
||||
"menu.media": "Media",
|
||||
"menu.newCampaign": "Crear nueva",
|
||||
"menu.settings": "Configuraciones",
|
||||
"public.campaignNotFound": "El mensaje de correo electrónico no fue encontrado",
|
||||
"public.confirmOptinSubTitle": "Confirmar subscripción",
|
||||
"public.confirmSub": "Confirmar subscripción",
|
||||
"public.confirmSubInfo": "Ud ha sido agregado a las siguietnes listas:",
|
||||
"public.confirmSubTitle": "Confirmar",
|
||||
"public.dataRemoved": "Su subscripcion y todos sus datos asociados han sido removidos.",
|
||||
"public.dataRemovedTitle": "Datos removidos",
|
||||
"public.dataSent": "Sus datos han sido enviados a Ud. como un archivo adjunto.",
|
||||
"public.dataSentTitle": "Datos enviados por correo electrónico",
|
||||
"public.errorFetchingCampaign": "Error obteniendo el mensaje de correo electrónico",
|
||||
"public.errorFetchingEmail": "Mensaje de correo electrónico no encontrado",
|
||||
"public.errorFetchingLists": "Error obteniendo listas. Por favor reintente.",
|
||||
"public.errorProcessingRequest": "Error procesando requerimiento. Por favor reintente.",
|
||||
"public.errorTitle": "Error",
|
||||
"public.invalidFeature": "Esta característica no está disponible",
|
||||
"public.invalidLink": "Link inválido",
|
||||
"public.noListsAvailable": "No hay listas disponibles para subscribirse",
|
||||
"public.noListsSelected": "No se seleccionaron listas válidas a las cuales subscribirse",
|
||||
"public.noSubInfo": "No hay subscripciones para confirmar.",
|
||||
"public.noSubTitle": "No hay subscripciones",
|
||||
"public.notFoundTitle": "No encontrado",
|
||||
"public.privacyConfirmWipe": "¿Está seguro que quiere borrar todos sus datos de subscripcion permanentemente?",
|
||||
"public.privacyExport": "Exportar sus datos",
|
||||
"public.privacyExportHelp": "Una copia de sus datos le será enviada por correo electrónico.",
|
||||
"public.privacyTitle": "Privacidad y datos",
|
||||
"public.privacyWipe": "Borrar sus datos",
|
||||
"public.privacyWipeHelp": "Borrar todas sus subscripciones y datos relacionados de la base de datos en forma permanente.",
|
||||
"public.sub": "Subscribirse",
|
||||
"public.subConfirmed": "Subscripcion satisfactoria.",
|
||||
"public.subConfirmedTitle": "Confirmada",
|
||||
"public.subName": "Nombre (opcional)",
|
||||
"public.subNotFound": "Subscripcion no encontrada",
|
||||
"public.subOptinPending": "Un correo electrónico le fue enviado para confirmar su(s) subcripcion(es)",
|
||||
"public.subPrivateList": "Lista privada",
|
||||
"public.subTitle": "Subscribirse",
|
||||
"public.unsub": "Des-Subscribirse",
|
||||
"public.unsubFull": "Adicionalemnte des-subscribirse de cualquer correo electrónico futuro.",
|
||||
"public.unsubHelp": "¿Desea des-subscribirse de esta lista de correo?",
|
||||
"public.unsubTitle": "Des-Subscribirse",
|
||||
"public.unsubbedInfo": "Ud se ha des-subscrito en forma satisfactoria",
|
||||
"public.unsubbedTitle": "Des-subscrito.",
|
||||
"public.unsubscribeTitle": "Des-subscribirse de una lista de correo",
|
||||
"settings.confirmRestart": "Asegúrese de que las campañas ejecutándose están en pause. ¿Reiniciar?",
|
||||
"settings.duplicateMessengerName": "Nombre de mensajero duplicado: {name}",
|
||||
"settings.errorEncoding": "Error codificado configuración: {error}",
|
||||
"settings.errorNoSMTP": "Al menos un bloque SMTP debe estar habilitado",
|
||||
"settings.general.adminNotifEmails": "Correos electrónicos para notificacion de administradores",
|
||||
"settings.general.adminNotifEmailsHelp": "Lista de correos electrónicos separados por comas, a donde las notificaciones como actualizaciones de importación, campañas completadas, fallas, etc deben ser enviadas.",
|
||||
"settings.general.checkUpdates": "Check for updates",
|
||||
"settings.general.checkUpdatesHelp": "Periodically check for new app releases and notify.",
|
||||
"settings.general.enablePublicSubPage": "Habilitar pagina publica de subscripción",
|
||||
"settings.general.enablePublicSubPageHelp": "Muestra una página con todas las listas públicas para subscribirse.",
|
||||
"settings.general.faviconURL": "Favicon URL",
|
||||
"settings.general.faviconURLHelp": "(Opcional) URL completa del favicon estático que debe mostrarse de cara a los usuarios en paginas como la pagina de des-subscripción",
|
||||
"settings.general.fromEmail": "Correo electrónico remitente por defecto.",
|
||||
"settings.general.fromEmailHelp": "Correo electrónico remitente para mostrar en campañas salientes de correos. Esto puede ser cambiado por campaña.",
|
||||
"settings.general.language": "Lenguaje",
|
||||
"settings.general.logoURL": "Logo URL",
|
||||
"settings.general.logoURLHelp": "(Opcional) URL completa del logo estático que debe ser mostrado de cara al usuario en páginas como la página de des-subscripción",
|
||||
"settings.general.name": "General",
|
||||
"settings.general.rootURL": "URL raíz",
|
||||
"settings.general.rootURLHelp": "URL pública de la instalación (sin la barra final)",
|
||||
"settings.invalidMessengerName": "Nombre de mensajero inválido.",
|
||||
"settings.media.provider": "Proveedor",
|
||||
"settings.media.s3.bucket": "Contenedor",
|
||||
"settings.media.s3.bucketPath": "Ruta del contenedor",
|
||||
"settings.media.s3.bucketPathHelp": "Ruta dentro del contenedor donde cargar archivos. Por defecto es /",
|
||||
"settings.media.s3.bucketType": "Tipo de contenedor",
|
||||
"settings.media.s3.bucketTypePrivate": "Privado",
|
||||
"settings.media.s3.bucketTypePublic": "Publico",
|
||||
"settings.media.s3.key": "Llave de acceso a AWS (key)",
|
||||
"settings.media.s3.region": "Region",
|
||||
"settings.media.s3.secret": "Secrete de acceso a AWS (secret)",
|
||||
"settings.media.s3.uploadExpiry": "Expiración de carga",
|
||||
"settings.media.s3.uploadExpiryHelp": "(Opcional) TTL específico (en segundos) para la URL pre firmada generada. Solo es aplicable para contenedores privados (s, m, h, d para segundos, minutos, horas, dias)",
|
||||
"settings.media.title": "Cargas de media",
|
||||
"settings.media.upload.path": "Ruta de carga",
|
||||
"settings.media.upload.pathHelp": "Ruta al directorio donde la media será cargada.",
|
||||
"settings.media.upload.uri": "URI de carga",
|
||||
"settings.media.upload.uriHelp": "La URI de carga es visible hacia afuera. La media cargada en el directorio de carga será accesible públicamente bajo [root_url}, por ejemplo, https://listmonk.susitio.com/uploads",
|
||||
"settings.messengers.maxConns": "Conexiones máximas",
|
||||
"settings.messengers.maxConnsHelp": "Número máximo de conexiones al servidor",
|
||||
"settings.messengers.messageDiscard": "¿Descartar cambios?",
|
||||
"settings.messengers.messageSaved": "Configuracion guardada. Recargando la aplicación.",
|
||||
"settings.messengers.name": "Mensajeros",
|
||||
"settings.messengers.nameHelp": "Ejemplo: my-sms. Alfanumerico / barra",
|
||||
"settings.messengers.password": "Contraseña",
|
||||
"settings.messengers.retries": "Reintentos",
|
||||
"settings.messengers.retriesHelp": "Número de reintentos cuando un mensaje falla",
|
||||
"settings.messengers.skipTLSHelp": "Saltar el checkeo del nombre de host en un certificado TLS",
|
||||
"settings.messengers.timeout": "Timeout por inactividad",
|
||||
"settings.messengers.timeoutHelp": "Tiempo de espara para nueva actividad en una conexion antes de cerrarla y elminarla del pool (s para segundos, m para minutos).",
|
||||
"settings.messengers.url": "URL",
|
||||
"settings.messengers.urlHelp": "URL raíz del servidor Postback",
|
||||
"settings.messengers.username": "Nombre de usuario",
|
||||
"settings.needsRestart": "Configuración cambiada. Pause todas las campañas y renicie la aplicación.",
|
||||
"settings.performance.batchSize": "Tamaño del lote",
|
||||
"settings.performance.batchSizeHelp": "Número de subscriptores a extraer de la base de datos en un iteración simple. Cada iteración extrae subscriptores de la base de datos, envia mensajes a ellos y luego avanza a la siguiente iteración para obtener el siguiente lote. Este número idealmente debería ser mayor que el máximo rendimiento alcanzable (concurrencia * tasa de envios)",
|
||||
"settings.performance.concurrency": "Concurrencia",
|
||||
"settings.performance.concurrencyHelp": "Número máximo de hilos que intentarán enviar mensajes en forma simultánea.",
|
||||
"settings.performance.maxErrThreshold": "Umbral de errores máximo.",
|
||||
"settings.performance.maxErrThresholdHelp": "El número de errores (Por ejemplo: SMTP timeouts mientras se envia correo) que una camaña en proceso debería tolerar antes de ser pausada para una invesitigación manual o intervención. 0 para no detenerse nunca.",
|
||||
"settings.performance.messageRate": "Tasa de envíos",
|
||||
"settings.performance.messageRateHelp": "Número máximo de mensajes enviados por segundo por hilo. Si la concurrencia = 10 y la tasa de envíos = 10, entonces hasta 10x10=100 mensajes podrían ser sacados en cada segundo. Esto junto con la concurrencia deberían ser modificados para que el numero de mensajes salientes no supere las tasas de envío de los servidores, si es que existen.",
|
||||
"settings.performance.name": "Rendimiento",
|
||||
"settings.performance.slidingWindow": "Habilitar limite de corrimiento de ventana",
|
||||
"settings.performance.slidingWindowDuration": "Duración",
|
||||
"settings.performance.slidingWindowDurationHelp": "Duración del período del corrimiento de ventana (m para minutos, h para horas).",
|
||||
"settings.performance.slidingWindowHelp": "Limite total de mensajes que son enviados en un periodo. Cuando se alcanza este liminte, los mensajes son retenidos hasta que se libere la ventana de tiempo.",
|
||||
"settings.performance.slidingWindowRate": "Máximo de mensajes",
|
||||
"settings.performance.slidingWindowRateHelp": "Máximo numero de mensajes a enviar dentro de la duración de la ventana.",
|
||||
"settings.privacy.allowBlocklist": "Permitir blocklisting",
|
||||
"settings.privacy.allowBlocklistHelp": "¿Permitir a los subscriptores des-subscribirse de todas las listas de correo y marcarlas como \"blocklisted\"?",
|
||||
"settings.privacy.allowExport": "Permitir exportar",
|
||||
"settings.privacy.allowExportHelp": "¿Permitir a los subscriptores exportar los datos recabados de ellos?",
|
||||
"settings.privacy.allowWipe": "Permitir limpieza de datos",
|
||||
"settings.privacy.allowWipeHelp": "Permitir a los subscriptores eliminarse incluyendo sus subscripciones y todos sus datos de la base de datos. Las vistas de las campañas y los vínculos cliqueados también son removidos mientras que las vistas y el conteo de clics se mantienen. (sin subscriptores asociados a ellos) de manera que las estadísticas y el análisis no se vea afectado.",
|
||||
"settings.privacy.individualSubTracking": "Seguimiento de subscriptor inválido.",
|
||||
"settings.privacy.individualSubTrackingHelp": "Seguir a nivel de subscriptor las vistas y clics en una campaña. Cuando está des-habilitado, el seguimiento de vistas y clics continua sin ser asociado con subscriptores individuales.",
|
||||
"settings.privacy.listUnsubHeader": "Incluir el encabezado `Des-subscribirse` de la lista",
|
||||
"settings.privacy.listUnsubHeaderHelp": "Incluye los encabezados de des-subscripcion para permitir a los clientes de correo que permitan a los usuarios des-subscribirse con un simple clic.",
|
||||
"settings.privacy.name": "Privacidad",
|
||||
"settings.restart": "Reinicar",
|
||||
"settings.smtp.authProtocol": "Protocolo de autenticación",
|
||||
"settings.smtp.customHeaders": "Encabezados personalizados",
|
||||
"settings.smtp.customHeadersHelp": "Arreglo de encabezados opcionales a incluir en todos los mensajes enviados desde este servidor. Por ejemplo {{\"X-Custom\": \"value\"}, {\"X-Custom2\": \"value\"}]",
|
||||
"settings.smtp.enabled": "Habilitado",
|
||||
"settings.smtp.heloHost": "HELO hostname",
|
||||
"settings.smtp.heloHostHelp": "Opcional. Algunos servidores SMTP requieren un FQDN en el nombre de host. Por defecto, los HELLO van con 'localhost'. Setear este si debe usarse un nombre de host personalizado.",
|
||||
"settings.smtp.host": "Host",
|
||||
"settings.smtp.hostHelp": "Dirección del servidor SMTP",
|
||||
"settings.smtp.idleTimeout": "Timeout por inactividad",
|
||||
"settings.smtp.idleTimeoutHelp": "Tiempo de espara para nueva actividad en una conexión antes de cerrarla y elminarla del pool (s para segundos, m para minutos).",
|
||||
"settings.smtp.maxConns": "Máximo de conexiones",
|
||||
"settings.smtp.maxConnsHelp": "Máximo de conexiones concurrentes hacia el servidor SMTP.",
|
||||
"settings.smtp.name": "SMTP",
|
||||
"settings.smtp.password": "Contraseña",
|
||||
"settings.smtp.passwordHelp": "Ingresar contraseña para cambiar",
|
||||
"settings.smtp.port": "Puerto",
|
||||
"settings.smtp.portHelp": "Puerto del servidor SMTP",
|
||||
"settings.smtp.retries": "Reintentos",
|
||||
"settings.smtp.retriesHelp": "Numero de reintentos cuando un mensaje falla.",
|
||||
"settings.smtp.setCustomHeaders": "Configurar encabezados personalizados.",
|
||||
"settings.smtp.skipTLS": "Saltar verificacion TLS",
|
||||
"settings.smtp.skipTLSHelp": "Saltar chequeo de nombre de servidor en un certificado TLS.",
|
||||
"settings.smtp.tls": "TLS",
|
||||
"settings.smtp.tlsHelp": "Habilitar STARTTLS",
|
||||
"settings.smtp.username": "Nombre de usuario",
|
||||
"settings.smtp.waitTimeout": "Timeout de espera",
|
||||
"settings.smtp.waitTimeoutHelp": "Tiempo de espera para nueva actividad en una conexión antes de cerrarla y eliminarla del pool (s para segundos, m para minutos).",
|
||||
"settings.title": "Configuraciones",
|
||||
"settings.updateAvailable": "Una actualización {version} está disponible.",
|
||||
"subscribers.advancedQuery": "Avanzado",
|
||||
"subscribers.advancedQueryHelp": "Expresión SQL parcial para consultar por los atributos de un subscriptor",
|
||||
"subscribers.attribs": "Atributos",
|
||||
"subscribers.attribsHelp": "Los atributos son definidos como un mapa JSON, por ejemplo:",
|
||||
"subscribers.blocklistedHelp": "Subscriptores blocklisted nunca recibirán correos.",
|
||||
"subscribers.confirmBlocklist": "Blocklist {num} subscriptor(es)?",
|
||||
"subscribers.confirmDelete": "Borrar {num} subscriptor(es)?",
|
||||
"subscribers.confirmExport": "Exportar {num} subscriptor(es)?",
|
||||
"subscribers.downloadData": "Descargar datos",
|
||||
"subscribers.email": "Correo electrónico",
|
||||
"subscribers.emailExists": "El correo electrónico ya existe.",
|
||||
"subscribers.errorBlocklisting": "Error blocklisting subscriptrores: {error}",
|
||||
"subscribers.errorInvalidIDs": "Uno o mas IDs inválidos fueron ingresados: {error}",
|
||||
"subscribers.errorNoIDs": "No se ingresaron IDs.",
|
||||
"subscribers.errorNoListsGiven": "Se se ingresaron listas.",
|
||||
"subscribers.errorPreparingQuery": "Error preprando la consulta del subscripto: {error}",
|
||||
"subscribers.errorSendingOptin": "Error enviado correo opt-in ",
|
||||
"subscribers.export": "Exportar",
|
||||
"subscribers.invalidAction": "Accion inválida",
|
||||
"subscribers.invalidEmail": "Correo electrónico inválidoo",
|
||||
"subscribers.invalidJSON": "Atributos JSON inválidos.",
|
||||
"subscribers.invalidName": "Nombre inválido.",
|
||||
"subscribers.listChangeApplied": "Cambio de lista aplicado.",
|
||||
"subscribers.lists": "Listas",
|
||||
"subscribers.listsHelp": "Listas desde donde los subscriptores se han des-subscrito no pueden ser eliminadas.",
|
||||
"subscribers.listsPlaceholder": "Lista a subscribir a",
|
||||
"subscribers.manageLists": "Adminstrar listas",
|
||||
"subscribers.markUnsubscribed": "Marcar como des-subscrito",
|
||||
"subscribers.newSubscriber": "Nuevo subscriptor",
|
||||
"subscribers.numSelected": "{num} subscriptores seleccionados",
|
||||
"subscribers.optinSubject": "Condirmar subscripcion",
|
||||
"subscribers.query": "Consulta",
|
||||
"subscribers.queryPlaceholder": "Correo electrónico o nombre",
|
||||
"subscribers.reset": "Reset",
|
||||
"subscribers.selectAll": "Seleccionar todos {num}",
|
||||
"subscribers.status.blocklisted": "Blocklisted",
|
||||
"subscribers.status.confirmed": "Confirmado",
|
||||
"subscribers.status.enabled": "Habilitado",
|
||||
"subscribers.status.subscribed": "Subscripto",
|
||||
"subscribers.status.unconfirmed": "NoConformado",
|
||||
"subscribers.status.unsubscribed": "Des-Subscrito",
|
||||
"subscribers.subscribersDeleted": "{num} subscriptor(es) borrados",
|
||||
"templates.cantDeleteDefault": "No se puede borrar la plantilla por defecto",
|
||||
"templates.default": "Por defecto",
|
||||
"templates.dummyName": "Campaña de prueba",
|
||||
"templates.dummySubject": "Asunto de campaña de prueba",
|
||||
"templates.errorCompiling": "Error compilado planitlla: {error}",
|
||||
"templates.errorRendering": "Error representando mensaje: {error}",
|
||||
"templates.fieldInvalidName": "Largo de nombre inválido",
|
||||
"templates.makeDefault": "Setar por defecto",
|
||||
"templates.newTemplate": "Nueva plantilla",
|
||||
"templates.placeholderHelp": "El marcador de posicion {placeholder} debería aparecer exactamente un vez en la plantilla.",
|
||||
"templates.preview": "Vista premiminar",
|
||||
"templates.rawHTML": "HTML crudo"
|
||||
}
|
454
i18n/fr.json
454
i18n/fr.json
|
@ -2,40 +2,41 @@
|
|||
"_.code": "fr",
|
||||
"_.name": "Français (fr)",
|
||||
"admin.errorMarshallingConfig": "Erreur lors de la lecture de la configuration : {error}",
|
||||
"campaigns.addAltText": "Ajouter un message de replacement en texte brut",
|
||||
"campaigns.addAltText": "Ajouter un message alternatif en texte brut",
|
||||
"campaigns.cantUpdate": "Impossible de mettre à jour une campagne en cours ou terminée.",
|
||||
"campaigns.clicks": "Clics",
|
||||
"campaigns.confirmDelete": "Supprimer {name}",
|
||||
"campaigns.confirmSchedule": "Cette campagne démarrera automatiquement à la date et à l'heure planifiées. Planifier maintenant ?",
|
||||
"campaigns.clicks": "clics",
|
||||
"campaigns.confirmDelete": "Supprimer la campagne {name}",
|
||||
"campaigns.confirmSchedule": "Cette campagne démarrera automatiquement à la date et à l'heure planifiées. Confirmer la planification ?",
|
||||
"campaigns.confirmSwitchFormat": "Le contenu peut perdre sa mise en forme. Continuer ?",
|
||||
"campaigns.content": "Contenu",
|
||||
"campaigns.contentHelp": "Contenu ici",
|
||||
"campaigns.contentHelp": "Rédigez le contenu ici.",
|
||||
"campaigns.continue": "Continuer",
|
||||
"campaigns.copyOf": "Copie de {name}",
|
||||
"campaigns.dateAndTime": "Date et heure",
|
||||
"campaigns.ended": "Terminé",
|
||||
"campaigns.errorSendTest": "Erreur lors de l'envoi test : {error}",
|
||||
"campaigns.ended": "Terminée",
|
||||
"campaigns.errorSendTest": "Erreur lors de l'envoi du test : {error}",
|
||||
"campaigns.fieldInvalidBody": "Erreur lors de la compilation du corps de la campagne : {error}",
|
||||
"campaigns.fieldInvalidFromEmail": "`Émetteur` non valide.",
|
||||
"campaigns.fieldInvalidListIDs": "ID de liste non valides.",
|
||||
"campaigns.fieldInvalidMessenger": "Messager inconnu {name}.",
|
||||
"campaigns.fieldInvalidName": "Longueur du nom non valide.",
|
||||
"campaigns.fieldInvalidFromEmail": "Adresse d'envoi invalide.",
|
||||
"campaigns.fieldInvalidListIDs": "ID de liste invalides.",
|
||||
"campaigns.fieldInvalidMessenger": "Service de messagerie inconnu : {name}.",
|
||||
"campaigns.fieldInvalidName": "Longueur du nom invalide.",
|
||||
"campaigns.fieldInvalidSendAt": "La date planifiée doit être future.",
|
||||
"campaigns.fieldInvalidSubject": "Longueur d'objet non valide.",
|
||||
"campaigns.fromAddress": "Émetteur",
|
||||
"campaigns.fromAddressPlaceholder": "Votre nom <noreply@votresite.com>",
|
||||
"campaigns.fromAddress": "Adresse d'envoi",
|
||||
"campaigns.fromAddressPlaceholder": "Nom à afficher <noreply@votresite.com>",
|
||||
"campaigns.invalid": "Campagne non valide",
|
||||
"campaigns.markdown": "Markdown",
|
||||
"campaigns.needsSendAt": "Une date est nécessaire pour planifier la campagne.",
|
||||
"campaigns.newCampaign": "Nouvelle campagne",
|
||||
"campaigns.noKnownSubsToTest": "Aucun abonné connu à tester.",
|
||||
"campaigns.noKnownSubsToTest": "Aucun·e abonné·e connu à tester.",
|
||||
"campaigns.noOptinLists": "Aucune liste opt-in trouvée pour créer une campagne.",
|
||||
"campaigns.noSubs": "Il n'y a aucun abonné dans les listes sélectionnées pour créer la campagne.",
|
||||
"campaigns.noSubsToTest": "Il n'y a aucun abonné à cibler.",
|
||||
"campaigns.noSubs": "Il n'y a aucun·e abonné·e dans les listes sélectionnées pour créer la campagne.",
|
||||
"campaigns.noSubsToTest": "Il n'y a aucun·e abonné·e à cibler.",
|
||||
"campaigns.notFound": "Campagne introuvable.",
|
||||
"campaigns.onlyActiveCancel": "Seules les campagnes actives peuvent être annulées.",
|
||||
"campaigns.onlyActivePause": "Seules les campagnes actives peuvent être mises en pause.",
|
||||
"campaigns.onlyDraftAsScheduled": "Seuls les brouillons de campagnes peuvent être planifiés.",
|
||||
"campaigns.onlyPausedDraft": "Seuls les brouillons et les campagnes mis en pause peuvent être lancés.",
|
||||
"campaigns.onlyDraftAsScheduled": "Seuls les campagnes à l'état de brouillon peuvent être planifiées.",
|
||||
"campaigns.onlyPausedDraft": "Seuls les brouillons et les campagnes mises en pause peuvent être lancés.",
|
||||
"campaigns.onlyScheduledAsDraft": "Seules les campagnes planifiées peuvent être enregistrées en tant que brouillons.",
|
||||
"campaigns.pause": "Pause",
|
||||
"campaigns.plainText": "Texte brut",
|
||||
|
@ -43,164 +44,164 @@
|
|||
"campaigns.progress": "Avancement",
|
||||
"campaigns.queryPlaceholder": "Nom ou objet",
|
||||
"campaigns.rawHTML": "HTML brut",
|
||||
"campaigns.removeAltText": "Supprimer le message de replacement en texte brut",
|
||||
"campaigns.removeAltText": "Supprimer le message alternatif en texte brut",
|
||||
"campaigns.richText": "Texte riche",
|
||||
"campaigns.schedule": "Planifier la campagne",
|
||||
"campaigns.scheduled": "Planifiée",
|
||||
"campaigns.send": "Envoyer",
|
||||
"campaigns.sendLater": "Envoyer plus tard",
|
||||
"campaigns.sendTest": "Envoyer un message de test",
|
||||
"campaigns.sendTestHelp": "Pour ajouter plusieurs destinataires, appuyez sur Entrée après avoir tapé une adresse. Les adresses doivent appartenir à des abonnés existants.",
|
||||
"campaigns.sendToLists": "Listes à envoyer à",
|
||||
"campaigns.sent": "Envoyé",
|
||||
"campaigns.sendTestHelp": "Pour ajouter plusieurs destinataires, appuyez sur Entrée après avoir tapé une adresse. Les adresses doivent faire partie des abonné·es existant·es.",
|
||||
"campaigns.sendToLists": "Envoyer aux listes",
|
||||
"campaigns.sent": "Envoyée",
|
||||
"campaigns.start": "Lancer la campagne",
|
||||
"campaigns.started": "\"{name}\" a commencé",
|
||||
"campaigns.startedAt": "Commencé",
|
||||
"campaigns.started": "La campagne \"{name}\" est lancée",
|
||||
"campaigns.startedAt": "Début",
|
||||
"campaigns.stats": "Statistiques",
|
||||
"campaigns.status.cancelled": "Annulé",
|
||||
"campaigns.status.draft": "Brouillon",
|
||||
"campaigns.status.finished": "Terminé",
|
||||
"campaigns.status.paused": "En pause",
|
||||
"campaigns.status.running": "En cours",
|
||||
"campaigns.status.scheduled": "Planifiée",
|
||||
"campaigns.statusChanged": "\"{name}\" est {status}",
|
||||
"campaigns.status.cancelled": "annulée",
|
||||
"campaigns.status.draft": "en brouillon",
|
||||
"campaigns.status.finished": "terminée",
|
||||
"campaigns.status.paused": "en pause",
|
||||
"campaigns.status.running": "active",
|
||||
"campaigns.status.scheduled": "planifiée",
|
||||
"campaigns.statusChanged": "La campagne \"{name}\" est {status}",
|
||||
"campaigns.subject": "Objet",
|
||||
"campaigns.testEmails": "Emails de test",
|
||||
"campaigns.testSent": "Message de test envoyé",
|
||||
"campaigns.timestamps": "Horodatages",
|
||||
"campaigns.views": "Vues",
|
||||
"dashboard.campaignViews": "Vues de la campagne",
|
||||
"dashboard.linkClicks": "Clics sur les liens",
|
||||
"dashboard.messagesSent": "Messages envoyés",
|
||||
"dashboard.orphanSubs": "Orphelins",
|
||||
"email.data.info": "Un fichier au format JSON contenant l'ensemble des données enregistrées à votre sujet est jointe. Il peut être visualisé dans un éditeur de texte.",
|
||||
"email.data.title": "Vos données",
|
||||
"email.optin.confirmSub": "Confirmer l'abonnement",
|
||||
"email.optin.confirmSubHelp": "Confirmez votre abonnement en cliquant sur le bouton ci-dessous.",
|
||||
"email.optin.confirmSubInfo": "Vous avez été ajouté aux listes suivantes :",
|
||||
"email.optin.confirmSubTitle": "Confirmer l'abonnement",
|
||||
"dashboard.campaignViews": "vues de campagne",
|
||||
"dashboard.linkClicks": "clics sur liens",
|
||||
"dashboard.messagesSent": "messages envoyés",
|
||||
"dashboard.orphanSubs": "abonnements sans retour",
|
||||
"email.data.info": "Vous trouverez un fichier au format JSON contenant l'ensemble des données enregistrées à votre sujet en pièce jointe. Il peut être visualisé dans un éditeur de texte.",
|
||||
"email.data.title": "Vos données personnelles",
|
||||
"email.optin.confirmSub": "Confirmer votre abonnement",
|
||||
"email.optin.confirmSubHelp": "Confirmez votre abonnement en cliquant sur le bouton ci-dessous :",
|
||||
"email.optin.confirmSubInfo": "Vous avez été ajouté·e aux listes suivantes :",
|
||||
"email.optin.confirmSubTitle": "Confirmer votre abonnement",
|
||||
"email.optin.confirmSubWelcome": "Bonjour,",
|
||||
"email.optin.privateList": "Liste privée",
|
||||
"email.status.campaignReason": "Raison",
|
||||
"email.status.campaignSent": "Envoyé",
|
||||
"email.status.campaignUpdateTitle": "Mise à jour de la campagne",
|
||||
"email.status.campaignReason": "Description",
|
||||
"email.status.campaignSent": "Envoyée",
|
||||
"email.status.campaignUpdateTitle": "Mise à jour de campagne",
|
||||
"email.status.importFile": "Fichier",
|
||||
"email.status.importRecords": "Enregistrements",
|
||||
"email.status.importRecords": "Contacts importés",
|
||||
"email.status.importTitle": "Importer la mise à jour",
|
||||
"email.status.status": "Statut",
|
||||
"email.unsub": "Se désabonner",
|
||||
"email.unsubHelp": "Vous ne souhaitez pas recevoir ces emails ?",
|
||||
"forms.formHTML": "Formulaire HTML",
|
||||
"forms.formHTMLHelp": "Utilisez le code HTML suivant pour afficher un formulaire d'abonnement sur une page Web externe. Le formulaire doit avoir le champ email et un ou plusieurs champs `l` (listes UUID). Le champ nom est facultatif.",
|
||||
"forms.formHTMLHelp": "Utilisez le code HTML suivant pour afficher un formulaire d'abonnement sur une page Web externe. Le formulaire doit avoir le champ email et un ou plusieurs champs `l` (listes UUID). Le champ \"nom\" est facultatif.",
|
||||
"forms.noPublicLists": "Il n'y a pas de listes publiques pour générer un formulaire.",
|
||||
"forms.publicLists": "Listes publiques",
|
||||
"forms.publicSubPage": "Page d'abonnement publique",
|
||||
"forms.selectHelp": "Sélectionnez les listes à ajouter au formulaire.",
|
||||
"forms.title": "Formulaires",
|
||||
"globals.buttons.add": "Ajouter",
|
||||
"globals.buttons.addNew": "Ajouter nouveau",
|
||||
"globals.buttons.addNew": "Ajouter",
|
||||
"globals.buttons.cancel": "Annuler",
|
||||
"globals.buttons.clone": "Cloner",
|
||||
"globals.buttons.close": "Fermer",
|
||||
"globals.buttons.continue": "Continuer",
|
||||
"globals.buttons.delete": "Supprimer",
|
||||
"globals.buttons.edit": "Éditer",
|
||||
"globals.buttons.enabled": "Activée",
|
||||
"globals.buttons.enabled": "Activé·e",
|
||||
"globals.buttons.learnMore": "En savoir plus",
|
||||
"globals.buttons.new": "Nouveau",
|
||||
"globals.buttons.ok": "Ok",
|
||||
"globals.buttons.new": "Ajouter",
|
||||
"globals.buttons.ok": "Valider",
|
||||
"globals.buttons.remove": "Supprimer",
|
||||
"globals.buttons.save": "Enregistrer",
|
||||
"globals.buttons.saveChanges": "Enregistrer les changements",
|
||||
"globals.days.1": "lun",
|
||||
"globals.days.2": "mar",
|
||||
"globals.days.3": "mer",
|
||||
"globals.days.4": "jeu",
|
||||
"globals.days.5": "ven",
|
||||
"globals.days.6": "sam",
|
||||
"globals.days.7": "dim",
|
||||
"globals.fields.createdAt": "Créé le",
|
||||
"globals.days.0": "dim.",
|
||||
"globals.days.1": "lun.",
|
||||
"globals.days.2": "mar.",
|
||||
"globals.days.3": "mer.",
|
||||
"globals.days.4": "jeu.",
|
||||
"globals.days.5": "ven.",
|
||||
"globals.days.6": "sam.",
|
||||
"globals.fields.createdAt": "Créé·e le",
|
||||
"globals.fields.id": "ID",
|
||||
"globals.fields.name": "Nom",
|
||||
"globals.fields.status": "Statut",
|
||||
"globals.fields.type": "Type",
|
||||
"globals.fields.updatedAt": "Mis à jour le",
|
||||
"globals.fields.uuid": "UUID",
|
||||
"globals.messages.confirm": "Êtes-vous sûr ?",
|
||||
"globals.messages.created": "\"{name}\" créé",
|
||||
"globals.messages.deleted": "\"{name}\" supprimé",
|
||||
"globals.messages.confirm": "Confirmer ?",
|
||||
"globals.messages.created": "Création de \"{name}\"",
|
||||
"globals.messages.deleted": "Suppression de \"{name}\"",
|
||||
"globals.messages.emptyState": "Rien",
|
||||
"globals.messages.errorCreating": "Erreur lors de la création de {name} : {error}",
|
||||
"globals.messages.errorDeleting": "Erreur lors de la suppression de {name} : {error}",
|
||||
"globals.messages.errorFetching": "Erreur lors de la récupération de {name} : {error}",
|
||||
"globals.messages.errorUUID": "Erreur lors de la génération de l'UUID : {error}",
|
||||
"globals.messages.errorUpdating": "Erreur lors de la mise à jour de {name}: {error}",
|
||||
"globals.messages.invalidID": "ID non valide",
|
||||
"globals.messages.invalidUUID": "UUID non valide",
|
||||
"globals.messages.errorUpdating": "Erreur lors de la mise à jour de {name} : {error}",
|
||||
"globals.messages.invalidID": "ID invalide",
|
||||
"globals.messages.invalidUUID": "UUID invalide",
|
||||
"globals.messages.notFound": "{name} introuvable",
|
||||
"globals.messages.passwordChange": "Entrez une valeur à modifier",
|
||||
"globals.messages.updated": "\"{name}\" mis à jour",
|
||||
"globals.months.1": "jan",
|
||||
"globals.months.10": "oct",
|
||||
"globals.months.11": "nov",
|
||||
"globals.months.12": "déc",
|
||||
"globals.months.2": "fév",
|
||||
"globals.messages.passwordChange": "Entrez un nouveau mot de passe pour en changer",
|
||||
"globals.messages.updated": "Mise à jour de \"{name}\"",
|
||||
"globals.months.1": "jan.",
|
||||
"globals.months.10": "oct.",
|
||||
"globals.months.11": "nov.",
|
||||
"globals.months.12": "déc.",
|
||||
"globals.months.2": "fév.",
|
||||
"globals.months.3": "mars",
|
||||
"globals.months.4": "avr",
|
||||
"globals.months.4": "avr.",
|
||||
"globals.months.5": "mai",
|
||||
"globals.months.6": "juin",
|
||||
"globals.months.7": "juil",
|
||||
"globals.months.7": "juil.",
|
||||
"globals.months.8": "août",
|
||||
"globals.months.9": "sept",
|
||||
"globals.months.9": "sept.",
|
||||
"globals.terms.campaign": "Campagne | Campagnes",
|
||||
"globals.terms.campaigns": "Campagnes",
|
||||
"globals.terms.dashboard": "Tableau de bord",
|
||||
"globals.terms.list": "Liste | Listes",
|
||||
"globals.terms.lists": "Listes",
|
||||
"globals.terms.media": "Médias | Médias",
|
||||
"globals.terms.messenger": "Messenger | Messagers",
|
||||
"globals.terms.messengers": "Messagers",
|
||||
"globals.terms.messenger": "Service de messagerie | Services de messagerie",
|
||||
"globals.terms.messengers": "Services de messagerie",
|
||||
"globals.terms.settings": "Paramètres",
|
||||
"globals.terms.subscriber": "Abonné | Abonnés",
|
||||
"globals.terms.subscribers": "Abonnés",
|
||||
"globals.terms.tag": "Étiquette | Étiquettes",
|
||||
"globals.terms.tags": "Étiquettes",
|
||||
"globals.terms.subscriber": "Abonné·e | Abonné·es",
|
||||
"globals.terms.subscribers": "Abonné·es",
|
||||
"globals.terms.tag": "Tag | Tags",
|
||||
"globals.terms.tags": "Tags",
|
||||
"globals.terms.template": "Modèle | Modèles",
|
||||
"globals.terms.templates": "Modèles",
|
||||
"import.alreadyRunning": "Une importation est déjà en cours. Attendez qu'elle se termine ou arrêtez-la avant de réessayer.",
|
||||
"import.blocklist": "Liste des adresses bloquées",
|
||||
"import.blocklist": "Bloquer les adresses importées",
|
||||
"import.csvDelim": "Délimiteur CSV",
|
||||
"import.csvDelimHelp": "Le délimiteur par défaut est la virgule.",
|
||||
"import.csvExample": "Exemple CSV brut",
|
||||
"import.csvExample": "Exemple de CSV brut",
|
||||
"import.csvFile": "Fichier CSV ou ZIP",
|
||||
"import.csvFileHelp": "Cliquez ou glissez-déposez ici un fichier CSV ou ZIP",
|
||||
"import.errorCopyingFile": "Erreur lors de la copie du fichier : {error}",
|
||||
"import.errorProcessingZIP": "Erreur lors du traitement du fichier ZIP : {error}",
|
||||
"import.errorStarting": "Erreur lors du démarrage de l'importation : {error}",
|
||||
"import.importDone": "Terminé",
|
||||
"import.importDone": "Importation terminée",
|
||||
"import.importStarted": "L'importation a commencé",
|
||||
"import.instructions": "Instructions",
|
||||
"import.instructionsHelp": "Téléchargez un fichier CSV ou un fichier ZIP contenant un seul fichier CSV pour importer des abonnés en masse. Le fichier CSV doit avoir les en-têtes suivants avec les noms de colonne exacts. Les attributs (facultatifs) doivent être des chaînes JSON valides entre guillemets doubles.",
|
||||
"import.instructionsHelp": "Téléchargez un fichier CSV (ou un fichier ZIP contenant un seul fichier CSV) pour importer des contacts en masse. Le fichier CSV doit avoir les en-têtes suivantes avec ces noms de colonnes exacts. Les attributs (facultatifs) doivent être des chaînes JSON valides entre guillemets doubles.",
|
||||
"import.invalidDelim": "Le délimiteur doit être un seul caractère.",
|
||||
"import.invalidFile": "Fichier non valide : {error}",
|
||||
"import.invalidMode": "Mode invalide",
|
||||
"import.invalidParams": "Paramètres non valides : {error}",
|
||||
"import.listSubHelp": "Listes auxquelles s'abonner.",
|
||||
"import.listSubHelp": "Abonner aux listes",
|
||||
"import.mode": "Mode",
|
||||
"import.overwrite": "Écraser ?",
|
||||
"import.overwriteHelp": "Remplacer le nom et les attributs des abonnés existants ?",
|
||||
"import.recordsCount": "{num} / {total} enregistrements",
|
||||
"import.overwriteHelp": "Remplacer le nom et les attributs des abonné·es existant·es ?",
|
||||
"import.recordsCount": "{num} / {total} contacts importés",
|
||||
"import.stopImport": "Arrêter l'importation",
|
||||
"import.subscribe": "S'abonner",
|
||||
"import.title": "Importer des abonnés",
|
||||
"import.upload": "Télécharger",
|
||||
"lists.confirmDelete": "Êtes-vous sûr ? Cela ne supprime pas les abonnés.",
|
||||
"import.title": "Importer des abonné·es",
|
||||
"import.upload": "Envoyer",
|
||||
"lists.confirmDelete": "Êtes-vous sûr·e de supprimer cette liste ? Cela ne supprimera pas les abonné·es.",
|
||||
"lists.confirmSub": "Confirmer les abonnements à {name}",
|
||||
"lists.invalidName": "Nom incorrect",
|
||||
"lists.newList": "Nouvelle liste",
|
||||
"lists.optin": "Abonnement",
|
||||
"lists.optinHelp": "Opt-in double envoie un email à l'abonné demandant sa confirmation. Pour les listes opt-in double, les campagnes ne sont envoyées qu'aux abonnés s'étant confirmés.",
|
||||
"lists.optinTo": "Activer {name}",
|
||||
"lists.optin": "Abonnement \"opt-in\" (ajout par défaut)",
|
||||
"lists.optinHelp": "L'option \"opt-in double\" envoie un email à l'abonné·e demandant sa confirmation. Pour les listes en \"opt-in double\", les campagnes ne sont envoyées qu'aux abonné·es s'étant confirmé·es.",
|
||||
"lists.optinTo": "Activer l'option opt-in pour {name}",
|
||||
"lists.optins.double": "Opt-in double",
|
||||
"lists.optins.single": "Opt-in simple",
|
||||
"lists.sendCampaign": "Envoyer la campagne",
|
||||
|
@ -209,221 +210,228 @@
|
|||
"lists.typeHelp": "Les listes publiques sont libres d'accès en abonnement et leurs noms sont visibles sur les pages publiques telles que la page de gestion des abonnements.",
|
||||
"lists.types.private": "Privée",
|
||||
"lists.types.public": "Publique",
|
||||
"logs.title": "Journaux",
|
||||
"logs.title": "Logs",
|
||||
"media.errorReadingFile": "Erreur de lecture du fichier : {error}",
|
||||
"media.errorResizing": "Erreur de redimensionnement de l'image : {error}",
|
||||
"media.errorResizing": "Erreur lors du redimensionnement de l'image : {error}",
|
||||
"media.errorSavingThumbnail": "Erreur lors de l'enregistrement de la miniature : {error}",
|
||||
"media.errorUploading": "Erreur lors du téléchargement du fichier : {error}",
|
||||
"media.errorUploading": "Erreur lors de l'envoi du fichier : {error}",
|
||||
"media.invalidFile": "Fichier non valide : {error}",
|
||||
"media.title": "Médias",
|
||||
"media.title": "Fichiers",
|
||||
"media.unsupportedFileType": "Type de fichier non pris en charge ({type})",
|
||||
"media.upload": "Télécharger",
|
||||
"media.uploadHelp": "Cliquez ou glissez-déposez ici une ou plusieurs images",
|
||||
"media.uploadImage": "Télécharger l'image",
|
||||
"media.upload": "Importer",
|
||||
"media.uploadHelp": "Cliquez ou glissez-déposez ici une ou plusieurs image(s)",
|
||||
"media.uploadImage": "Importer une image",
|
||||
"menu.allCampaigns": "Toutes les campagnes",
|
||||
"menu.allLists": "Toutes les listes",
|
||||
"menu.allSubscribers": "Tous les abonnés",
|
||||
"menu.allSubscribers": "Tou·tes les abonné·es",
|
||||
"menu.dashboard": "Tableau de bord",
|
||||
"menu.forms": "Formulaires",
|
||||
"menu.import": "Importer",
|
||||
"menu.logs": "Journaux",
|
||||
"menu.media": "Médias",
|
||||
"menu.newCampaign": "Créer nouveau",
|
||||
"menu.logs": "Logs",
|
||||
"menu.media": "Fichiers",
|
||||
"menu.newCampaign": "Nouvelle campagne",
|
||||
"menu.settings": "Paramètres",
|
||||
"public.campaignNotFound": "La liste de diffusion est introuvable.",
|
||||
"public.confirmOptinSubTitle": "Confirmer l'abonnement",
|
||||
"public.confirmSub": "Confirmer l'abonnement",
|
||||
"public.confirmSubInfo": "Vous avez été ajouté aux listes suivantes :",
|
||||
"public.confirmSubTitle": "Confirmer",
|
||||
"public.confirmOptinSubTitle": "Confirmer votre abonnement",
|
||||
"public.confirmSub": "Confirmer votre abonnement",
|
||||
"public.confirmSubInfo": "Vous avez été ajouté·e aux listes suivantes :",
|
||||
"public.confirmSubTitle": "Confirmer votre abonnement",
|
||||
"public.dataRemoved": "Vos abonnements et toutes les données associées ont été supprimés.",
|
||||
"public.dataRemovedTitle": "Données supprimées",
|
||||
"public.dataSent": "Vos données vous ont été envoyées par email.",
|
||||
"public.dataSentTitle": "Données envoyées par email",
|
||||
"public.dataRemovedTitle": "Données personnelles supprimées",
|
||||
"public.dataSent": "Vos données personnelles vous ont été envoyées par email.",
|
||||
"public.dataSentTitle": "Données personnelles envoyées",
|
||||
"public.errorFetchingCampaign": "Erreur lors de la récupération de l'email.",
|
||||
"public.errorFetchingEmail": "Message email introuvable",
|
||||
"public.errorFetchingEmail": "Email introuvable",
|
||||
"public.errorFetchingLists": "Erreur lors de la récupération des listes. Veuillez réessayer.",
|
||||
"public.errorProcessingRequest": "Erreur lors du traitement de la demande. Veuillez réessayer.",
|
||||
"public.errorTitle": "Erreur",
|
||||
"public.invalidFeature": "Cette fonctionnalité n'est pas disponible.",
|
||||
"public.invalidLink": "Lien invalide",
|
||||
"public.noListsAvailable": "Aucune liste disponible pour vous abonner.",
|
||||
"public.noListsAvailable": "Aucune liste n'est disponible pour vous abonner.",
|
||||
"public.noListsSelected": "Aucune liste valide sélectionnée pour s'abonner.",
|
||||
"public.noSubInfo": "Il n'y a pas d'abonnement à confirmer.",
|
||||
"public.noSubTitle": "Aucun abonnement",
|
||||
"public.notFoundTitle": "Pas trouvé",
|
||||
"public.privacyConfirmWipe": "Êtes-vous sûr de vouloir supprimer définitivement toutes vos données d'abonnement ?",
|
||||
"public.privacyExport": "Exportez vos données",
|
||||
"public.notFoundTitle": "Non trouvé",
|
||||
"public.privacyConfirmWipe": "Voulez-vous vraiment supprimer définitivement toutes vos données d'abonnement ?",
|
||||
"public.privacyExport": "Exportez vos données personnelles",
|
||||
"public.privacyExportHelp": "Une copie de vos données vous sera envoyée par email.",
|
||||
"public.privacyTitle": "Confidentialité et données",
|
||||
"public.privacyWipe": "Effacez vos données",
|
||||
"public.privacyWipeHelp": "Supprimez définitivement tous vos abonnements et données associées de la base de données.",
|
||||
"public.privacyTitle": "Confidentialité et données personnelles",
|
||||
"public.privacyWipe": "Effacez toutes vos données personnelles",
|
||||
"public.privacyWipeHelp": "Supprimez définitivement tous vos abonnements et données associées de notre base de données.",
|
||||
"public.sub": "S'abonner",
|
||||
"public.subConfirmed": "Abonné avec succès.",
|
||||
"public.subConfirmedTitle": "Confirmé",
|
||||
"public.subConfirmed": "Vous voici abonné·e avec succès.",
|
||||
"public.subConfirmedTitle": "Abonnement confirmé",
|
||||
"public.subName": "Nom (facultatif)",
|
||||
"public.subNotFound": "Abonnement introuvable.",
|
||||
"public.subOptinPending": "Un email de confirmation d'inscription(s) vous a été envoyé.",
|
||||
"public.subPrivateList": "Liste privée",
|
||||
"public.subTitle": "S'abonner",
|
||||
"public.unsub": "Se désabonner",
|
||||
"public.unsubFull": "Se désabonner aussi de tous futurs emails.",
|
||||
"public.unsubFull": "Se désabonner également de tous futurs emails.",
|
||||
"public.unsubHelp": "Voulez-vous vous désabonner de cette liste de diffusion ?",
|
||||
"public.unsubTitle": "Se désabonner",
|
||||
"public.unsubbedInfo": "Vous vous êtes désabonné avec succès.",
|
||||
"public.unsubbedTitle": "Désabonné",
|
||||
"public.unsubbedInfo": "Vous vous êtes désabonné·e avec succès.",
|
||||
"public.unsubbedTitle": "Désabonné·e",
|
||||
"public.unsubscribeTitle": "Se désabonner de la liste de diffusion",
|
||||
"settings.duplicateMessengerName": "Nom de messagerie en double : {name}",
|
||||
"settings.errorEncoding": "Erreur lors du codage des paramètres : {error}",
|
||||
"settings.confirmRestart": "Assurez-vous que les campagnes actives soient en pause. Redémarrer ?",
|
||||
"settings.duplicateMessengerName": "Doublon du nom de messagerie : {name}",
|
||||
"settings.errorEncoding": "Erreur lors de l'encodage des paramètres : {error}",
|
||||
"settings.errorNoSMTP": "Au moins un bloc SMTP doit être activé",
|
||||
"settings.general.adminNotifEmails": "Emails de notification administrateur",
|
||||
"settings.general.adminNotifEmailsHelp": "Liste d'adresses email séparées par des virgules auxquelles les notifications administration telles que les mises à jour d'importation, la fin de la campagne, l'échec, etc. seront envoyées.",
|
||||
"settings.general.adminNotifEmails": "Emails pour les notifications admin",
|
||||
"settings.general.adminNotifEmailsHelp": "Liste d'adresses email (séparées par des virgules) auxquelles les notifications d'admin telles que les mises à jour d'importation, fins de campagnes, échecs, etc. seront envoyées.",
|
||||
"settings.general.checkUpdates": "Check for updates",
|
||||
"settings.general.checkUpdatesHelp": "Periodically check for new app releases and notify.",
|
||||
"settings.general.enablePublicSubPage": "Activer la page d'abonnement publique",
|
||||
"settings.general.enablePublicSubPageHelp": "Afficher une page d'abonnement publique avec toutes les listes publiques auxquelles les personnes peuvent s'abonner.",
|
||||
"settings.general.faviconURL": "URL du favicon",
|
||||
"settings.general.faviconURLHelp": "(Facultatif) URL complète du favicon statique visible par l'utilisateur, comme sur la page de désabonnement.",
|
||||
"settings.general.fromEmail": "Adresse email `Émetteur` par défaut",
|
||||
"settings.general.fromEmailHelp": "Adresse email `Émetteur` visible par défaut dans les emails de campagne sortants. Ce paramètre est modifiable pour chaque campagne.",
|
||||
"settings.general.fromEmail": "Adresse email `De :` par défaut",
|
||||
"settings.general.fromEmailHelp": "Adresse email `De :` à afficher par défaut dans les emails de campagne sortants. Ce paramètre est modifiable pour chaque campagne.",
|
||||
"settings.general.language": "Langue",
|
||||
"settings.general.logoURL": "URL du logo",
|
||||
"settings.general.logoURLHelp": "(Facultatif) URL complète du logo statique visible par l'utilisateur, comme sur la page de désabonnement.",
|
||||
"settings.general.name": "Général",
|
||||
"settings.general.rootURL": "URL racine",
|
||||
"settings.general.rootURLHelp": "URL publique de l'installation (pas de barre oblique finale).",
|
||||
"settings.invalidMessengerName": "Nom de messagerie non valide.",
|
||||
"settings.general.rootURLHelp": "URL publique de l'installation (sans slash final)",
|
||||
"settings.invalidMessengerName": "Nom de messagerie invalide",
|
||||
"settings.media.provider": "Fournisseur",
|
||||
"settings.media.s3.bucket": "Bucket",
|
||||
"settings.media.s3.bucketPath": "Chemin du bucket",
|
||||
"settings.media.s3.bucketPathHelp": "Chemin à l'intérieur du bucket pour télécharger les fichiers. La valeur par défaut est /",
|
||||
"settings.media.s3.bucketType": "Type de bucket",
|
||||
"settings.media.s3.bucket": "Compartiment",
|
||||
"settings.media.s3.bucketPath": "Chemin du compartiment",
|
||||
"settings.media.s3.bucketPathHelp": "Emplacement dans le compartiment pour la mise en ligne des fichiers. La valeur par défaut est /",
|
||||
"settings.media.s3.bucketType": "Type du compartiment",
|
||||
"settings.media.s3.bucketTypePrivate": "Privé",
|
||||
"settings.media.s3.bucketTypePublic": "Publique",
|
||||
"settings.media.s3.key": "AWS access key",
|
||||
"settings.media.s3.bucketTypePublic": "Public",
|
||||
"settings.media.s3.key": "Clé d'accès AWS",
|
||||
"settings.media.s3.region": "Région",
|
||||
"settings.media.s3.secret": "AWS access secret",
|
||||
"settings.media.s3.uploadExpiry": "Expiration du téléchargement",
|
||||
"settings.media.s3.uploadExpiryHelp": "(Facultatif) Spécifiez le TTL (en secondes) pour l'URL prédéfinie générée. Uniquement applicable pour les buckets privés (s, m, h, d pour les secondes, minutes, heures, jours).",
|
||||
"settings.media.title": "Téléchargements de médias",
|
||||
"settings.media.upload.path": "Chemin du téléchargement",
|
||||
"settings.media.upload.pathHelp": "Chemin vers le répertoire où les médias seront téléchargés.",
|
||||
"settings.media.upload.uri": "URI de téléchargement",
|
||||
"settings.media.upload.uriHelp": "URI de téléchargement qui sera visible du monde extérieur. Le média téléchargé dans le chemin du téléchargement sera accessible publiquement sous {root_url}, par exemple, https://listmonk.votresite.com/uploads.",
|
||||
"settings.messengers.maxConns": "Nb. connexions max.",
|
||||
"settings.messengers.maxConnsHelp": "Nombre maximum de connexions simultanées au serveur.",
|
||||
"settings.media.s3.secret": "Mot de passe d'accès AWS",
|
||||
"settings.media.s3.uploadExpiry": "Durée de validité",
|
||||
"settings.media.s3.uploadExpiryHelp": "(Facultatif) Spécifiez la durée de validité (en secondes) pour l'URL prédéfinie générée. Uniquement applicable pour les compartiments privés (s, m, h, d pour les secondes, minutes, heures, jours).",
|
||||
"settings.media.title": "Mise en ligne de fichiers",
|
||||
"settings.media.upload.path": "Emplacement d'envoi des fichiers",
|
||||
"settings.media.upload.pathHelp": "Chemin vers le répertoire où les médias seront mis en ligne",
|
||||
"settings.media.upload.uri": "URI d'envoi des fichiers",
|
||||
"settings.media.upload.uriHelp": "URI d'envoi des fichiers (qui sera visible du monde extérieur). Les médias stockés à cet emplacement seront accessible publiquement sous {root_url}, par exemple à l'adresse : https://listmonk.votresite.com/uploads",
|
||||
"settings.messengers.maxConns": "Nombre de connexions max.",
|
||||
"settings.messengers.maxConnsHelp": "Nombre maximum de connexions simultanées au serveur",
|
||||
"settings.messengers.messageDiscard": "Annuler les modifications ?",
|
||||
"settings.messengers.messageSaved": "Paramètres sauvegardés. Rechargement de l'application...",
|
||||
"settings.messengers.name": "Messagers",
|
||||
"settings.messengers.nameHelp": "Par exemple : my-sms. Alphanumérique / tiret.",
|
||||
"settings.messengers.messageSaved": "Paramètres sauvegardés. Redémarrage de l'application...",
|
||||
"settings.messengers.name": "Nom du service d'envoi de messages",
|
||||
"settings.messengers.nameHelp": "Par exemple : my-sms. Utilisez uniquement des caractères alphanumériques et des tirets.",
|
||||
"settings.messengers.password": "Mot de passe",
|
||||
"settings.messengers.retries": "Tentatives",
|
||||
"settings.messengers.retriesHelp": "Nombre de tentatives en cas d'échec d'un message.",
|
||||
"settings.messengers.skipTLSHelp": "Ignorez la vérification du nom d'hôte sur le certificat TLS.",
|
||||
"settings.messengers.retries": "Tentatives de renvoi",
|
||||
"settings.messengers.retriesHelp": "Nombre de tentatives de renvoi en cas d'échec",
|
||||
"settings.messengers.skipTLSHelp": "Ignorer la vérification du nom d'hôte sur le certificat TLS",
|
||||
"settings.messengers.timeout": "Délai d'inactivité",
|
||||
"settings.messengers.timeoutHelp": "Temps d'attente avant une nouvelle activité sur la connexion avant fermeture et suppression du pool (s pour seconde, m pour minute).",
|
||||
"settings.messengers.timeoutHelp": "Temps d'attente d'une nouvelle activité sur la connexion avant sa fermeture et suppression du pool (s pour seconde, m pour minute).",
|
||||
"settings.messengers.url": "URL",
|
||||
"settings.messengers.urlHelp": "URL racine du serveur Postback.",
|
||||
"settings.messengers.urlHelp": "URL racine du serveur Postback",
|
||||
"settings.messengers.username": "Nom d'utilisateur",
|
||||
"settings.needsRestart": "Certains paramètres ont été modifiés. Mettez toutes les campagnes actives en pause et redémarrez l'application.",
|
||||
"settings.performance.batchSize": "Taille du lot",
|
||||
"settings.performance.batchSizeHelp": "Le nombre d'abonnés à extraire de la base de données en une seule itération. Chaque itération extrait les abonnés de la base de données, leur envoie les messages, puis passe à l'itération suivante pour extraire le lot suivant. Idéalement cette valeur devrait être supérieure au débit maximum possible (Concurrence * Taux de message).",
|
||||
"settings.performance.concurrency": "Concurrence",
|
||||
"settings.performance.concurrencyHelp": "Nombre de worker (threads) concurrents maximum qui enverrons les messages simultanément.",
|
||||
"settings.performance.maxErrThreshold": "Seuil maximum d'erreur",
|
||||
"settings.performance.batchSizeHelp": "Le nombre d'abonné·es à extraire de la base de données en une seule itération. Chaque itération extrait les abonné·es de la base de données, leur envoie les messages, puis passe à l'itération suivante pour extraire le lot suivant. Idéalement cette valeur devrait être supérieure au débit maximum possible (Nb de threads * débit).",
|
||||
"settings.performance.concurrency": "Nombre de threads",
|
||||
"settings.performance.concurrencyHelp": "Nombre de workers (threads) concurrents maximum qui enverrons les messages simultanément.",
|
||||
"settings.performance.maxErrThreshold": "Seuil maximum d'erreurs",
|
||||
"settings.performance.maxErrThresholdHelp": "Le nombre d'erreurs (par exemple : délais d'expiration SMTP lors de l'envoi d'emails) qu'une campagne en cours d'exécution doit tolérer avant d'être suspendue pour une vérification ou une intervention manuelle. Réglez sur 0 pour ne jamais mettre en pause.",
|
||||
"settings.performance.messageRate": "Taux de message",
|
||||
"settings.performance.messageRateHelp": "Nombre maximum de messages à envoyer par worker en une seconde. Si concurrence = 10 et taux de message = 10, alors jusqu'à 10x10 = 100 messages peuvent être poussés chaque seconde. Ce paramètre ainsi que le paramètre concurrence devraient être modifié pour maintenir les messages sortants par seconde sous les limites de débit des serveurs de messages cibles, le cas échéant.",
|
||||
"settings.performance.name": "Performance",
|
||||
"settings.performance.slidingWindow": "Activer une limite par fenêtre glissante",
|
||||
"settings.performance.slidingWindowDuration": "Durée",
|
||||
"settings.performance.slidingWindowDurationHelp": "Durée de la période de la fenêtre glissante (m pour minute, h pour heure).",
|
||||
"settings.performance.slidingWindowHelp": "Limitez le nombre total de messages envoyés au cours d'une période donnée. Une fois cette limite atteinte, l'envoi des messages est suspendu jusqu'à ce que la fenêtre de temps soit passée.",
|
||||
"settings.performance.slidingWindowRate": "Nb. messages max.",
|
||||
"settings.performance.slidingWindowRateHelp": "Nombre maximum de messages à envoyer pendant la durée de la fenêtre.",
|
||||
"settings.privacy.allowBlocklist": "Autoriser la liste de blocage",
|
||||
"settings.privacy.allowBlocklistHelp": "Autoriser les abonnés à se désabonner de toutes les listes de diffusion et à se marquer comme étant bloqués ?",
|
||||
"settings.privacy.allowExport": "Autoriser l'exportation",
|
||||
"settings.privacy.allowExportHelp": "Autoriser les abonnés à exporter les données collectées à leur sujet ?",
|
||||
"settings.privacy.allowWipe": "Autoriser l'effacement",
|
||||
"settings.privacy.allowWipeHelp": "Autoriser les abonnés à supprimer leurs abonnements et toutes les autres données de la base de données. Les vues de campagne et les clics sur les liens sont également supprimés, tandis que le compteur global de vues et de nombre de clics restent inchangés (aucun abonné ne leur est associé) afin que les statistiques et les analyses ne soient pas affectées.",
|
||||
"settings.privacy.individualSubTracking": "Suivi individuel des abonnés",
|
||||
"settings.privacy.individualSubTrackingHelp": "Suivez les vues et les clics des campagnes par abonné. Lorsqu'elle est désactivée, la vue et le suivi des clics continuent sans être liés à des abonnés individuels.",
|
||||
"settings.privacy.listUnsubHeader": "Inclure l'en-tête `List-Unsubscribe`",
|
||||
"settings.privacy.listUnsubHeaderHelp": "Incluez des en-têtes de désabonnement qui permettre aux utilisateurs de se désabonner en un seul clic depuis leur client de messagerie.",
|
||||
"settings.performance.messageRate": "Débit de messages (par thread)",
|
||||
"settings.performance.messageRateHelp": "Nombre maximum de messages à envoyer par worker / thread en une seconde. Si concurrence = 10 et débit = 10, alors jusqu'à 10x10 = 100 messages peuvent être mis en file d'envoi chaque seconde. Réglez les deux paramètres afin que le débit total soit inférieur aux seuils fixés par les serveurs de messagerie cibles de vos abonné·es pour ne pas finir en spam.",
|
||||
"settings.performance.name": "Débits et performances",
|
||||
"settings.performance.slidingWindow": "Activer une limite d'envois par fenêtre glissante (max. X messages envoyés sur une durée donnée)",
|
||||
"settings.performance.slidingWindowDuration": "Durée de la fenêtre",
|
||||
"settings.performance.slidingWindowDurationHelp": "Durée de la fenêtre glissante (m pour minute, h pour heure).",
|
||||
"settings.performance.slidingWindowHelp": "Limitez le nombre total de messages envoyés au cours d'une période donnée. Une fois cette limite atteinte, l'envoi des messages est suspendu jusqu'à ce que la fenêtre de temps soit écoulée.",
|
||||
"settings.performance.slidingWindowRate": "Nb. de messages max",
|
||||
"settings.performance.slidingWindowRateHelp": "Nombre maximum de messages à envoyer sur cette fenêtre",
|
||||
"settings.privacy.allowBlocklist": "Autoriser les abonné·es à bloquer tout envoi",
|
||||
"settings.privacy.allowBlocklistHelp": "Autoriser les abonné·es à se désabonner de toutes les listes de diffusion et à se marquer comme étant bloqué·es ?",
|
||||
"settings.privacy.allowExport": "Autoriser l'export des données par les abonné·es",
|
||||
"settings.privacy.allowExportHelp": "Autoriser les abonné·es à exporter les données collectées à leur sujet ?",
|
||||
"settings.privacy.allowWipe": "Autoriser la suppression des données par les abonné·es",
|
||||
"settings.privacy.allowWipeHelp": "Autoriser les abonné·es à supprimer leurs abonnements et toutes les autres données de la base de données. Les vues de campagne et les clics sur les liens sont également supprimés, tandis que le compteur de vues et de nombre de clics globaux restent inchangés (aucun·e abonné·e ne leur est associé) afin que les statistiques et les analyses ne soient pas affectées.",
|
||||
"settings.privacy.individualSubTracking": "Suivi individuel des abonné·es (vérifiez si la légalislation l'autorise)",
|
||||
"settings.privacy.individualSubTrackingHelp": "Suivez les vues et les clics par abonné·e pour les campagnes (vérifiez si la légalislation en vigueur l'autorise). Si l'option est désactivée, le suivi des vues et des clics s'effectue de façon anonyme.",
|
||||
"settings.privacy.listUnsubHeader": "Inclure l'en-tête de désabonnement simplifié (via certaines messageries)",
|
||||
"settings.privacy.listUnsubHeaderHelp": "Inclure des en-têtes de désabonnement qui permettent aux utilisateurs de se désabonner en un seul clic depuis leur client de messagerie.",
|
||||
"settings.privacy.name": "Vie privée",
|
||||
"settings.restart": "Redémarrer",
|
||||
"settings.smtp.authProtocol": "Protocole d'authentification",
|
||||
"settings.smtp.customHeaders": "En-têtes personnalisés",
|
||||
"settings.smtp.customHeadersHelp": "Tableau facultatif d'en-têtes des emails à inclure dans tous les messages envoyés depuis ce serveur. Par exemple : [{\"X-Custom\": \"value\"}, {\"X-Custom2\": \"value\"}]",
|
||||
"settings.smtp.enabled": "Activée",
|
||||
"settings.smtp.customHeaders": "En-têtes personnalisées",
|
||||
"settings.smtp.customHeadersHelp": "Tableau facultatif d'en-têtes à inclure dans tous les emails envoyés depuis ce serveur. Par exemple : [{\"X-Custom\": \"value\"}, {\"X-Custom2\": \"value\"}]",
|
||||
"settings.smtp.enabled": "Activé",
|
||||
"settings.smtp.heloHost": "Nom d'hôte HELO",
|
||||
"settings.smtp.heloHostHelp": "Facultatif. Certains serveurs SMTP nécessitent un nom de domaine complet dans le nom d'hôte. Par défaut, HELLOs vient avec `localhost`. Définissez ce paramètre si un nom d'hôte personnalisé doit être utilisé.",
|
||||
"settings.smtp.heloHostHelp": "Facultatif. Certains serveurs SMTP nécessitent un nom de domaine complet dans le nom d'hôte. Par défaut, HELOs utilise `localhost`. Définissez ce paramètre si un nom d'hôte personnalisé doit être utilisé.",
|
||||
"settings.smtp.host": "Hôte",
|
||||
"settings.smtp.hostHelp": "Adresse hôte du serveur SMTP.",
|
||||
"settings.smtp.hostHelp": "Adresse hôte du serveur SMTP",
|
||||
"settings.smtp.idleTimeout": "Délai d'inactivité",
|
||||
"settings.smtp.idleTimeoutHelp": "Temps d'attente avant une nouvelle activité sur la connexion avant fermeture et suppression du pool (s pour seconde, m pour minute).",
|
||||
"settings.smtp.maxConns": "Nb. connexions max.",
|
||||
"settings.smtp.maxConnsHelp": "Nombre maximum de connexions simultanées au serveur SMTP.",
|
||||
"settings.smtp.idleTimeoutHelp": "Temps d'attente d'une nouvelle activité sur la connexion avant sa fermeture et suppression du pool (s pour seconde, m pour minute)",
|
||||
"settings.smtp.maxConns": "Nb. de connexions max.",
|
||||
"settings.smtp.maxConnsHelp": "Nombre maximum de connexions simultanées au serveur SMTP",
|
||||
"settings.smtp.name": "SMTP",
|
||||
"settings.smtp.password": "Mot de passe",
|
||||
"settings.smtp.passwordHelp": "Entrée pour modifier",
|
||||
"settings.smtp.passwordHelp": "Entrez un nouveau mot de passe si vous souhaitez le modifier",
|
||||
"settings.smtp.port": "Port",
|
||||
"settings.smtp.portHelp": "Port du serveur SMTP.",
|
||||
"settings.smtp.retries": "Tentatives",
|
||||
"settings.smtp.retriesHelp": "Nombre de tentatives en cas d'échec d'un message.",
|
||||
"settings.smtp.portHelp": "Port du serveur SMTP",
|
||||
"settings.smtp.retries": "Tentatives de renvoi",
|
||||
"settings.smtp.retriesHelp": "Nombre de tentatives de renvoi d'un message en cas d'échec",
|
||||
"settings.smtp.setCustomHeaders": "Définir des en-têtes personnalisés",
|
||||
"settings.smtp.skipTLS": "Ignorer la vérification TLS",
|
||||
"settings.smtp.skipTLSHelp": "Ignorez la vérification du nom d'hôte sur le certificat TLS.",
|
||||
"settings.smtp.skipTLSHelp": "Ignorer la vérification du nom d'hôte sur le certificat TLS",
|
||||
"settings.smtp.tls": "TLS",
|
||||
"settings.smtp.tlsHelp": "Activez STARTTLS.",
|
||||
"settings.smtp.tlsHelp": "Activer STARTTLS",
|
||||
"settings.smtp.username": "Nom d'utilisateur",
|
||||
"settings.smtp.waitTimeout": "Délai d'attente",
|
||||
"settings.smtp.waitTimeoutHelp": "Temps d'attente pour une nouvelle activité sur une connexion avant de sa fermeture et sa suppression du pool (s pour seconde, m pour minute).",
|
||||
"settings.smtp.waitTimeoutHelp": "Temps d'attente d'une nouvelle activité sur une connexion avant sa fermeture et sa suppression du pool (s pour seconde, m pour minute)",
|
||||
"settings.title": "Paramètres",
|
||||
"subscribers.advancedQuery": "Avancées",
|
||||
"subscribers.advancedQueryHelp": "Expression SQL partielle pour interroger les attributs de l'abonné",
|
||||
"settings.updateAvailable": "Une nouvelle version ({version}) est disponible.",
|
||||
"subscribers.advancedQuery": "Requête avancée",
|
||||
"subscribers.advancedQueryHelp": "Expression SQL partielle pour interroger les attributs de l'abonné·e",
|
||||
"subscribers.attribs": "Attributs",
|
||||
"subscribers.attribsHelp": "Les attributs sont définis comme une map JSON, par exemple :",
|
||||
"subscribers.blocklistedHelp": "Les abonnés bloqués ne recevront jamais d'emails.",
|
||||
"subscribers.confirmBlocklist": "Liste de blocage {num} abonné(s) ?",
|
||||
"subscribers.confirmDelete": "Supprimer {num} abonné(s) ?",
|
||||
"subscribers.confirmExport": "Exporter {num} abonné(s) ?",
|
||||
"subscribers.blocklistedHelp": "Les abonné·es bloqué·es ne recevront jamais d'emails.",
|
||||
"subscribers.confirmBlocklist": "Bloquer {num} abonné·e(s) ?",
|
||||
"subscribers.confirmDelete": "Supprimer {num} abonné·e(s) ?",
|
||||
"subscribers.confirmExport": "Exporter {num} abonné·e(s) ?",
|
||||
"subscribers.downloadData": "Télécharger les données",
|
||||
"subscribers.email": "Email",
|
||||
"subscribers.emailExists": "L'email existe déjà.",
|
||||
"subscribers.errorBlocklisting": "Erreur lors du blocage des abonnés : {error}",
|
||||
"subscribers.emailExists": "Cet email existe déjà.",
|
||||
"subscribers.errorBlocklisting": "Erreur lors du blocage des abonné·es : {error}",
|
||||
"subscribers.errorInvalidIDs": "Un ou plusieurs identifiants non valides fournis : {error}",
|
||||
"subscribers.errorNoIDs": "Aucune ID fournie.",
|
||||
"subscribers.errorNoListsGiven": "Aucune liste donnée.",
|
||||
"subscribers.errorPreparingQuery": "Erreur lors de la préparation de la requête d'abonné : {error}",
|
||||
"subscribers.errorSendingOptin": "Erreur lors de l'envoi de l'email opt-in.",
|
||||
"subscribers.export": "Exportation",
|
||||
"subscribers.invalidAction": "Action non valide.",
|
||||
"subscribers.invalidEmail": "Email invalide.",
|
||||
"subscribers.errorNoIDs": "Aucun identifiant fourni.",
|
||||
"subscribers.errorNoListsGiven": "Aucune liste attribuée.",
|
||||
"subscribers.errorPreparingQuery": "Erreur lors de la préparation de la requête d'abonné·e : {error}",
|
||||
"subscribers.errorSendingOptin": "Erreur lors de l'envoi de l'email d'opt-in.",
|
||||
"subscribers.export": "Export",
|
||||
"subscribers.invalidAction": "Cette action est invalide.",
|
||||
"subscribers.invalidEmail": "Cet email est invalide.",
|
||||
"subscribers.invalidJSON": "JSON non valide dans les attributs.",
|
||||
"subscribers.invalidName": "Nom incorrect.",
|
||||
"subscribers.invalidName": "Le nom entré présente une erreur.",
|
||||
"subscribers.listChangeApplied": "Modification de la liste effectuée.",
|
||||
"subscribers.lists": "Listes",
|
||||
"subscribers.listsHelp": "Les listes dont les abonnés se sont désabonnés ne peuvent pas être supprimées.",
|
||||
"subscribers.listsHelp": "Les listes dont les abonné·es se sont déjà désabonné·es ne peuvent pas être supprimées.",
|
||||
"subscribers.listsPlaceholder": "Listes auxquelles s'abonner",
|
||||
"subscribers.manageLists": "Gérer les listes",
|
||||
"subscribers.markUnsubscribed": "Marquer comme désabonné",
|
||||
"subscribers.newSubscriber": "Nouvel abonné",
|
||||
"subscribers.numSelected": "{num} abonné(s) sélectionné(s)",
|
||||
"subscribers.optinSubject": "Confirmer l'abonnement",
|
||||
"subscribers.markUnsubscribed": "Marquer comme désabonné·e",
|
||||
"subscribers.newSubscriber": "Nouvel·le abonné·e",
|
||||
"subscribers.numSelected": "{num} abonné·e(s) sélectionné·e(s)",
|
||||
"subscribers.optinSubject": "Confirmer votre abonnement",
|
||||
"subscribers.query": "Requête",
|
||||
"subscribers.queryPlaceholder": "Email ou nom",
|
||||
"subscribers.reset": "Réinitialiser",
|
||||
"subscribers.selectAll": "Sélectionner tout {num}",
|
||||
"subscribers.status.blocklisted": "Liste bloquée",
|
||||
"subscribers.status.confirmed": "Confirmé",
|
||||
"subscribers.status.enabled": "Activée",
|
||||
"subscribers.status.subscribed": "Abonné",
|
||||
"subscribers.status.unconfirmed": "Non confirmé",
|
||||
"subscribers.status.unsubscribed": "Désabonné",
|
||||
"subscribers.subscribersDeleted": "{num} abonné(s) supprimé(s)",
|
||||
"subscribers.status.blocklisted": "Bloqué·e",
|
||||
"subscribers.status.confirmed": "Confirmé·e",
|
||||
"subscribers.status.enabled": "Activé·e",
|
||||
"subscribers.status.subscribed": "Abonné·e",
|
||||
"subscribers.status.unconfirmed": "Non confirmé·e",
|
||||
"subscribers.status.unsubscribed": "Désabonné·e",
|
||||
"subscribers.subscribersDeleted": "{num} abonné·e(s) supprimé·e(s)",
|
||||
"templates.cantDeleteDefault": "Impossible de supprimer le modèle par défaut",
|
||||
"templates.default": "Défaut",
|
||||
"templates.dummyName": "Campagne de test",
|
||||
"templates.dummySubject": "Objet de la campagne de test",
|
||||
"templates.errorCompiling": "Erreur lors de la compilation du modèle : {error}",
|
||||
"templates.errorRendering": "Message d'erreur lors du rendu : {error}",
|
||||
"templates.fieldInvalidName": "Longueur du nom non valide.",
|
||||
"templates.fieldInvalidName": "Longueur du nom invalide.",
|
||||
"templates.makeDefault": "Définir par défaut",
|
||||
"templates.newTemplate": "Nouveau modèle",
|
||||
"templates.placeholderHelp": "L'espace réservé {placeholder} doit apparaître exactement une fois dans le modèle.",
|
||||
|
|
440
i18n/it.json
Normal file
440
i18n/it.json
Normal file
|
@ -0,0 +1,440 @@
|
|||
{
|
||||
"_.code": "it",
|
||||
"_.name": "Italiano (it)",
|
||||
"admin.errorMarshallingConfig": "Errore durante la lettura della configurazione: {error}",
|
||||
"campaigns.addAltText": "Aggiungere un messaggio sostitutivo in testo semplice",
|
||||
"campaigns.cantUpdate": "Impossibile aggiornare una campagna in corso o già effettuata.",
|
||||
"campaigns.clicks": "Clic",
|
||||
"campaigns.confirmDelete": "Cancellare {nome}",
|
||||
"campaigns.confirmSchedule": " Questa campagna inizierà automaticamente alla data e all'ora programmate. Programmare adesso?",
|
||||
"campaigns.confirmSwitchFormat": "Il contenuto può perdere la sua formattazione. Continuare?",
|
||||
"campaigns.content": "Contenuto",
|
||||
"campaigns.contentHelp": "Contenuto qui",
|
||||
"campaigns.continue": "Continuare",
|
||||
"campaigns.copyOf": "Copie di {name}",
|
||||
"campaigns.dateAndTime": "Data e ora",
|
||||
"campaigns.ended": "Finito",
|
||||
"campaigns.errorSendTest": "Errore durante il test di invio: {error}",
|
||||
"campaigns.fieldInvalidBody": "Errore durante la compilazione del contenuto della campagna: {error}",
|
||||
"campaigns.fieldInvalidFromEmail": "`Mittente` non valido.",
|
||||
"campaigns.fieldInvalidListIDs": "ID della lista non valido.",
|
||||
"campaigns.fieldInvalidMessenger": "Strumento di messaggeria sconosciuto {name}.",
|
||||
"campaigns.fieldInvalidName": "Lunghezza del nome non valida.",
|
||||
"campaigns.fieldInvalidSendAt": "La data programmata deve essere futura.",
|
||||
"campaigns.fieldInvalidSubject": "Lunghezza dell'oggetto non valida.",
|
||||
"campaigns.fromAddress": "Mittente",
|
||||
"campaigns.fromAddressPlaceholder": "Tuo nome <noreply@tuosito.com>",
|
||||
"campaigns.invalid": "Campagna non valida",
|
||||
"campaigns.markdown": "Markdown",
|
||||
"campaigns.needsSendAt": "È necessaria una data per programmare la campagna.",
|
||||
"campaigns.newCampaign": "Nuova campagna",
|
||||
"campaigns.noKnownSubsToTest": "Nessun iscritto conosciuto da testare.",
|
||||
"campaigns.noOptinLists": "Nessuna lista opt-in trovata per poter creare una campagna.",
|
||||
"campaigns.noSubs": "Non esiste alcun iscritto nelle liste selezionate per creare la campagna.",
|
||||
"campaigns.noSubsToTest": "Non c'è alcun iscritto a cui rivolgersi.",
|
||||
"campaigns.notFound": "Campagna introvabile.",
|
||||
"campaigns.onlyActiveCancel": "Solo le campagne attive possono essere annullate.",
|
||||
"campaigns.onlyActivePause": "Solo le campagne attive possono essere messe in pausa.",
|
||||
"campaigns.onlyDraftAsScheduled": "Solo le bozze delle campagne possono essere programmate.",
|
||||
"campaigns.onlyPausedDraft": "Solo le bozze e le campagne in pausa possono essere lanciate.",
|
||||
"campaigns.onlyScheduledAsDraft": "Solo le campagne pianificate possono essere registrate come bozze.",
|
||||
"campaigns.pause": "Pausa",
|
||||
"campaigns.plainText": "Testo semplice",
|
||||
"campaigns.preview": "Anteprima",
|
||||
"campaigns.progress": "Avanzamento",
|
||||
"campaigns.queryPlaceholder": "Nome o oggetto",
|
||||
"campaigns.rawHTML": "HTML semplice",
|
||||
"campaigns.removeAltText": "Cancellare il messaggio sostitutivo in testo semplice",
|
||||
"campaigns.richText": "Testo formattato",
|
||||
"campaigns.schedule": "Programmare la campagna",
|
||||
"campaigns.scheduled": "Programmata",
|
||||
"campaigns.send": "Inviare",
|
||||
"campaigns.sendLater": "Inviare più tardi",
|
||||
"campaigns.sendTest": "Inviare un messaggio di testo",
|
||||
"campaigns.sendTestHelp": "Per aggiungere più destinatari, premi Enter dopo aver aggiunto un indirizzo. Gli indirizzi devono appartenere a iscritti esistenti.",
|
||||
"campaigns.sendToLists": "Liste da inviare a",
|
||||
"campaigns.sent": "Inviato",
|
||||
"campaigns.start": "Lanciare la campagna",
|
||||
"campaigns.started": "\"{name}\" ha cominciato",
|
||||
"campaigns.startedAt": "Cominciato",
|
||||
"campaigns.stats": "Statistiche",
|
||||
"campaigns.status.cancelled": "Annullato",
|
||||
"campaigns.status.draft": "Bozza",
|
||||
"campaigns.status.finished": "Finito",
|
||||
"campaigns.status.paused": "In pausa",
|
||||
"campaigns.status.running": "In corso",
|
||||
"campaigns.status.scheduled": "Programmata",
|
||||
"campaigns.statusChanged": "\"{name}\" e {status}",
|
||||
"campaigns.subject": "Oggetto",
|
||||
"campaigns.testEmails": "Emails di prova",
|
||||
"campaigns.testSent": "Messaggio di prova inviato",
|
||||
"campaigns.timestamps": "Marcatura temporale ",
|
||||
"campaigns.views": "Visualizzazioni",
|
||||
"dashboard.campaignViews": "Visualizzazioni della campagna",
|
||||
"dashboard.linkClicks": "Clic sui link",
|
||||
"dashboard.messagesSent": "Messaggi inviati",
|
||||
"dashboard.orphanSubs": "Orfani",
|
||||
"email.data.info": "È stato aggiunto un file JSON contenente l'insieme dei tuoi dati salvati. Può essere visualizzato in un editore di testo.",
|
||||
"email.data.title": "I tuoi dati",
|
||||
"email.optin.confirmSub": "Confermare l'iscrizione",
|
||||
"email.optin.confirmSubHelp": "Conferma la tua iscrizione cliccando sul pulsante qui sotto.",
|
||||
"email.optin.confirmSubInfo": "Sei stato aggiunto alle liste seguenti:",
|
||||
"email.optin.confirmSubTitle": "Confermare l'iscrizione",
|
||||
"email.optin.confirmSubWelcome": "Buongiorno",
|
||||
"email.optin.privateList": "Lista privata",
|
||||
"email.status.campaignReason": "Ragione",
|
||||
"email.status.campaignSent": "Inviato",
|
||||
"email.status.campaignUpdateTitle": "Aggiornamento della campagna",
|
||||
"email.status.importFile": "File",
|
||||
"email.status.importRecords": "Salvataggi",
|
||||
"email.status.importTitle": "Importare l'aggiornamento",
|
||||
"email.status.status": "Stato",
|
||||
"email.unsub": "Cancella iscrizione",
|
||||
"email.unsubHelp": "Non desideri ricevere queste mail?",
|
||||
"forms.formHTML": "Formulario HTML",
|
||||
"forms.formHTMLHelp": "Utilizza il seguente codice HTML per visualizzare un formulario d'abbonamento su una pagina Web esterna. Il formulario deve avere il campo email e uno o più campi `l` (liste UUID). Il campo nome è facoltativo.",
|
||||
"forms.noPublicLists": "Non ci sono liste pubbliche per generare un formulario.",
|
||||
"forms.publicLists": "Liste pubbliche",
|
||||
"forms.publicSubPage": "Pagina di iscrizione pubblica",
|
||||
"forms.selectHelp": "Seleziona le liste da aggiungere al formulario.",
|
||||
"forms.title": "Formulari",
|
||||
"globals.buttons.add": "Aggiungi",
|
||||
"globals.buttons.addNew": "Aggiungi nuovo",
|
||||
"globals.buttons.cancel": "Annulla",
|
||||
"globals.buttons.clone": "Clona",
|
||||
"globals.buttons.close": "Chiudi",
|
||||
"globals.buttons.continue": "Continuare",
|
||||
"globals.buttons.delete": "Cancellare",
|
||||
"globals.buttons.edit": "Modifica",
|
||||
"globals.buttons.enabled": "Attivata",
|
||||
"globals.buttons.learnMore": "Per saperne di più",
|
||||
"globals.buttons.new": "Nuovo",
|
||||
"globals.buttons.ok": "Ok",
|
||||
"globals.buttons.remove": "Cancellare",
|
||||
"globals.buttons.save": "Salvare",
|
||||
"globals.buttons.saveChanges": "Salvare le modifiche",
|
||||
"globals.days.0": "dom",
|
||||
"globals.days.1": "lun",
|
||||
"globals.days.2": "mar",
|
||||
"globals.days.3": "mer",
|
||||
"globals.days.4": "gio",
|
||||
"globals.days.5": "ven",
|
||||
"globals.days.6": "sab",
|
||||
"globals.fields.createdAt": "Creato il ",
|
||||
"globals.fields.id": "ID",
|
||||
"globals.fields.name": "Nome",
|
||||
"globals.fields.status": "Stato",
|
||||
"globals.fields.type": "Tipo",
|
||||
"globals.fields.updatedAt": "Aggiornato il",
|
||||
"globals.fields.uuid": "UUID",
|
||||
"globals.messages.confirm": "Sei sicuro?",
|
||||
"globals.messages.created": "\"{name}\" creato",
|
||||
"globals.messages.deleted": "\"{name}\" cancellato",
|
||||
"globals.messages.emptyState": "Niente da visualizzare",
|
||||
"globals.messages.errorCreating": "Errore durante la creazione di {name}: {error}",
|
||||
"globals.messages.errorDeleting": "Errore durante la cancellazione di {name}: {error}",
|
||||
"globals.messages.errorFetching": "Errore durante il recupero di {name}: {error}",
|
||||
"globals.messages.errorUUID": "Errore durante la generazione dell'UUID: {error}",
|
||||
"globals.messages.errorUpdating": "Errore durante l'aggiornamento di {name}: {error}",
|
||||
"globals.messages.invalidID": "ID non valido",
|
||||
"globals.messages.invalidUUID": "UUID non valido",
|
||||
"globals.messages.notFound": "{name} introvabile",
|
||||
"globals.messages.passwordChange": "Inserisci un valore da modificare",
|
||||
"globals.messages.updated": "\"{name}\" aggiornato",
|
||||
"globals.months.1": "Gen",
|
||||
"globals.months.10": "Ott",
|
||||
"globals.months.11": "Nov",
|
||||
"globals.months.12": "Dic",
|
||||
"globals.months.2": "Feb",
|
||||
"globals.months.3": "Mar",
|
||||
"globals.months.4": "Apr",
|
||||
"globals.months.5": "Mag",
|
||||
"globals.months.6": "Giu",
|
||||
"globals.months.7": "Lug",
|
||||
"globals.months.8": "Ago",
|
||||
"globals.months.9": "Set",
|
||||
"globals.terms.campaign": "Campagna | Campagne",
|
||||
"globals.terms.campaigns": "Campagne",
|
||||
"globals.terms.dashboard": "Tabella di marcia",
|
||||
"globals.terms.list": "Lista | Liste",
|
||||
"globals.terms.lists": "Liste",
|
||||
"globals.terms.media": "Media | Media",
|
||||
"globals.terms.messenger": "Strumento di messaggeria | Strumenti di messaggeria",
|
||||
"globals.terms.messengers": "Strumento di messaggeria",
|
||||
"globals.terms.settings": "Parametri",
|
||||
"globals.terms.subscriber": "Iscritto | Iscritti",
|
||||
"globals.terms.subscribers": "Iscritti",
|
||||
"globals.terms.tag": "Etichetta | Etichette",
|
||||
"globals.terms.tags": "Etichette",
|
||||
"globals.terms.template": "Modello | Modelli",
|
||||
"globals.terms.templates": "Modelli",
|
||||
"import.alreadyRunning": "Un'importazione è già in corso. Aspetta che finisca o interrompila prima di riprovare.",
|
||||
"import.blocklist": "Lista degli indirizzi bloccati",
|
||||
"import.csvDelim": "Delimitatore CSV",
|
||||
"import.csvDelimHelp": "Il delimitatore predefinito è la virgola.",
|
||||
"import.csvExample": "Esempio di CSV semplice",
|
||||
"import.csvFile": "File CSV o ZIP",
|
||||
"import.csvFileHelp": "Clicca o trascina qui un file CSV o ZIP",
|
||||
"import.errorCopyingFile": "Errore durante la copia del file: {error}",
|
||||
"import.errorProcessingZIP": "Errore durante il trattamento del file ZIP: {error}",
|
||||
"import.errorStarting": "Errore durante l'avvio dell'importazione: {error}",
|
||||
"import.importDone": "Finito",
|
||||
"import.importStarted": "L'importazione è inziata",
|
||||
"import.instructions": "Istruzioni",
|
||||
"import.instructionsHelp": "Carica un file CSV o un file ZIP contenente un solo CSV per importare iscritti in massa. Il file CSV deve avere le seguenti intestazioni con i nomi delle colonne esatti. Gli attributi (facoltativi) devono essere delle stringhe JSON valide tra virgolette doppie.",
|
||||
"import.invalidDelim": "Il delimitatore deve essere un singolo carattere.",
|
||||
"import.invalidFile": "File non valido: {error}",
|
||||
"import.invalidMode": "Modalità non valida",
|
||||
"import.invalidParams": "Parametri non validi: {error}",
|
||||
"import.listSubHelp": "Liste a cui iscriversi.",
|
||||
"import.mode": "Modalità",
|
||||
"import.overwrite": "Sovrascrivere?",
|
||||
"import.overwriteHelp": "Sostituire il nome e gli attributi degli iscritti esistenti?",
|
||||
"import.recordsCount": "{num} / {total} salvataggi",
|
||||
"import.stopImport": "Interrompere l'importazione",
|
||||
"import.subscribe": "Iscriversi",
|
||||
"import.title": "Importare iscritti",
|
||||
"import.upload": "Caricare",
|
||||
"lists.confirmDelete": "Sei sicuro? Questo non cancella gli iscritti",
|
||||
"lists.confirmSub": "Confermare gli iscritti di {name}",
|
||||
"lists.invalidName": "Nome errato",
|
||||
"lists.newList": "Nuova lista",
|
||||
"lists.optin": "Iscrizione",
|
||||
"lists.optinHelp": "Opt-in invio doppio di una mail a l'iscritto richiedendo la sua conferma. Per le liste opt-in doppio, le campagne sono inviate solo agli iscritti che hanno confermato.",
|
||||
"lists.optinTo": "Attivare {name}",
|
||||
"lists.optins.double": "Opt-in doppio",
|
||||
"lists.optins.single": "Opt-in semplice",
|
||||
"lists.sendCampaign": "Inviare la campagna",
|
||||
"lists.sendOptinCampaign": "Inviare una campagna opt-in",
|
||||
"lists.type": "Tipo",
|
||||
"lists.typeHelp": "Le liste pubbliche sono libere d'accesso in abbonamento e i loro nomi sono visibili sulle pagine pubbliche come ad esempio la pagina della gestione degli abbonamenti.",
|
||||
"lists.types.private": "Privata",
|
||||
"lists.types.public": "Pubblico",
|
||||
"logs.title": "Giornali",
|
||||
"media.errorReadingFile": "Errore di lettura del file: {error}",
|
||||
"media.errorResizing": "Errore di ridimensionamento dell'immagine: {error}",
|
||||
"media.errorSavingThumbnail": "Errore durante il salvataggio della vignetta: {error}",
|
||||
"media.errorUploading": "Errore durante il caricamento del file: {error}",
|
||||
"media.invalidFile": "File non valido: {error}",
|
||||
"media.title": "Media",
|
||||
"media.unsupportedFileType": "Tipo di file non supportato ({type})",
|
||||
"media.upload": "Caricare",
|
||||
"media.uploadHelp": "Seleziona o trascina qui una o più immagini",
|
||||
"media.uploadImage": "Caricare l'immagine",
|
||||
"menu.allCampaigns": "Tutte le campagne",
|
||||
"menu.allLists": "Tutte le liste",
|
||||
"menu.allSubscribers": "Tutti gli iscritti",
|
||||
"menu.dashboard": "Tabella di marcia",
|
||||
"menu.forms": "Formulari",
|
||||
"menu.import": "Importare",
|
||||
"menu.logs": "Giornali",
|
||||
"menu.media": "Media",
|
||||
"menu.newCampaign": "Creare nuovo",
|
||||
"menu.settings": "Parametri",
|
||||
"public.campaignNotFound": "Lista di diffusione impossibile da trovare.",
|
||||
"public.confirmOptinSubTitle": "Confermare l'iscrizione",
|
||||
"public.confirmSub": "Confermare l'iscrizione",
|
||||
"public.confirmSubInfo": "Sei stato aggiunto alle liste seguenti:",
|
||||
"public.confirmSubTitle": "Confermare",
|
||||
"public.dataRemoved": "I tuoi abbonamenti e tutti i dati associati sono stati cancellati.",
|
||||
"public.dataRemovedTitle": "Dati cancellati",
|
||||
"public.dataSent": "I tuoi dati ti sono stati trasmessi via mail.",
|
||||
"public.dataSentTitle": "Dati trasmessi via mail",
|
||||
"public.errorFetchingCampaign": "Errore durante il recupero della mail.",
|
||||
"public.errorFetchingEmail": "Messaggio mail impossibile da trovare",
|
||||
"public.errorFetchingLists": "Errore durante il recupero delle liste. Per favore, riprova.",
|
||||
"public.errorProcessingRequest": "Errore durante la gestione della richiesta. Per favore, riprova.",
|
||||
"public.errorTitle": "Errore",
|
||||
"public.invalidFeature": "Questa funzione non è disponibile.",
|
||||
"public.invalidLink": "Link non valido",
|
||||
"public.noListsAvailable": "Nessuna lista disponibile per l'iscrizione.",
|
||||
"public.noListsSelected": "Nessuna lista valida selezionata per l'iscrizione.",
|
||||
"public.noSubInfo": "Non ci sono iscrizioni da confermare.",
|
||||
"public.noSubTitle": "Nessuna iscrizione",
|
||||
"public.notFoundTitle": "Non trovato",
|
||||
"public.privacyConfirmWipe": "Sei sicuro di voler cancellare in modo permanente tutti i tuoi dati d'iscrizione?",
|
||||
"public.privacyExport": "Esporta i tuoi dati",
|
||||
"public.privacyExportHelp": "Una copia dei tuoi dati ti sarà trasmessa via mail.",
|
||||
"public.privacyTitle": "Privacy e dati",
|
||||
"public.privacyWipe": "Cancella i tuoi dati",
|
||||
"public.privacyWipeHelp": "Cancella in modo permanente tutte le tue iscrizioni e relativi dari dal database.",
|
||||
"public.sub": "Iscriversi",
|
||||
"public.subConfirmed": "Iscrizione avvenuta con successo.",
|
||||
"public.subConfirmedTitle": "Confermato",
|
||||
"public.subName": "Nome (facoltativo)",
|
||||
"public.subNotFound": "Iscrizione impossibile da trovare.",
|
||||
"public.subOptinPending": "An e-mail has been sent to you to confirm your subscription(s).",
|
||||
"public.subPrivateList": "Lista privata",
|
||||
"public.subTitle": "Iscriversi",
|
||||
"public.unsub": "Cancella iscrizione",
|
||||
"public.unsubFull": "Cancella iscrizione anche per tutte le mail future.",
|
||||
"public.unsubHelp": "Vuoi cancellare l'iscrizione da questa lista di diffusione?",
|
||||
"public.unsubTitle": "Cancella iscrizione",
|
||||
"public.unsubbedInfo": "La cancellazione è avvenuta con successo.",
|
||||
"public.unsubbedTitle": "Iscrizione annullata",
|
||||
"public.unsubscribeTitle": "Cancella l'iscrizione dalla lista di diffusione",
|
||||
"settings.confirmRestart": "Ensure running campaigns are paused. Restart?",
|
||||
"settings.duplicateMessengerName": "Nome in messaggeria doppio: {name}",
|
||||
"settings.errorEncoding": "Errore durante la codifica dei parametri: {error}",
|
||||
"settings.errorNoSMTP": "Devi attivare almeno un blocco SMTP",
|
||||
"settings.general.adminNotifEmails": "Mail di notifica amministratore",
|
||||
"settings.general.adminNotifEmailsHelp": "Lista indirizzi mail separati da virgole ai quali saranno inviate notifiche di amministrazione come gli aggiornamenti di importazione, la fine della campagna, eventuali problemi ecc.",
|
||||
"settings.general.checkUpdates": "Check for updates",
|
||||
"settings.general.checkUpdatesHelp": "Periodically check for new app releases and notify.",
|
||||
"settings.general.enablePublicSubPage": "Attiva la pagina di iscrizione pubblica",
|
||||
"settings.general.enablePublicSubPageHelp": "Visualizza una pagina di iscrizione pubblica con tutte le liste pubbliche a cui è possibile iscriversi.",
|
||||
"settings.general.faviconURL": "URL della favicon",
|
||||
"settings.general.faviconURLHelp": "(Facoltativo) URL completo della favicon statica visibile dall'utente, come sulla pagina per annullare l'iscrizione.",
|
||||
"settings.general.fromEmail": "Indirizzo mail `Mittente` predefinito",
|
||||
"settings.general.fromEmailHelp": "Indirizzo mail `Mittente` nelle mail delle campagne uscenti visibile in modo predefinito. Questo parametro è modificabile per ogni campagna.",
|
||||
"settings.general.language": "Lingua",
|
||||
"settings.general.logoURL": "URL del logo",
|
||||
"settings.general.logoURLHelp": "(Facoltativo) URL completo del logo statico visibile dall'utente come sulla pagina per annullare l'iscrizione.",
|
||||
"settings.general.name": "Generale",
|
||||
"settings.general.rootURL": "Radice dell'URL",
|
||||
"settings.general.rootURLHelp": "URL pubblico dell'installazione (senza barra obliqua finale).",
|
||||
"settings.invalidMessengerName": "Nome di messaggeria non valido.",
|
||||
"settings.media.provider": "Fornitore",
|
||||
"settings.media.s3.bucket": "Bucket",
|
||||
"settings.media.s3.bucketPath": "Percorso del bucket",
|
||||
"settings.media.s3.bucketPathHelp": "Percorso all'interno del bucket per caricare i file. Il valore predefinito è /",
|
||||
"settings.media.s3.bucketType": "Tipo di bucket",
|
||||
"settings.media.s3.bucketTypePrivate": "Privato",
|
||||
"settings.media.s3.bucketTypePublic": "Pubblico",
|
||||
"settings.media.s3.key": "Chiave d'accesso AWS",
|
||||
"settings.media.s3.region": "Regione",
|
||||
"settings.media.s3.secret": "Accesso segreto AWS",
|
||||
"settings.media.s3.uploadExpiry": "Caricamento scaduto",
|
||||
"settings.media.s3.uploadExpiryHelp": "(Facoltativo) Specifica il TTL (in secondi) per l'URL predefinito generato. Applicabile solo per i buckets privati (s, m, h, d per i secondi, minuti, ore e giorni).",
|
||||
"settings.media.title": "Caricamento dei media",
|
||||
"settings.media.upload.path": "Percorso del caricamento",
|
||||
"settings.media.upload.pathHelp": "Percorso verso il repertorio dove i media saranno caricati.",
|
||||
"settings.media.upload.uri": "URI del caricamento",
|
||||
"settings.media.upload.uriHelp": "URI del caricamento che sarà visibile dal mondo esterno. Il media caricato nel percorso del caricamento sarà accessibile pubblicamente sotto {root_url}, per esempio: https://listmonk.tuosito.com/uploads.",
|
||||
"settings.messengers.maxConns": "Nb. connessioni max.",
|
||||
"settings.messengers.maxConnsHelp": "Numero massimo di connessioni simultanee al server.",
|
||||
"settings.messengers.messageDiscard": "Annullare le modifiche?",
|
||||
"settings.messengers.messageSaved": "Parametri salvati. Ricarica dell'applicazione...",
|
||||
"settings.messengers.name": "Strumento di messaggeria",
|
||||
"settings.messengers.nameHelp": "Per esempio: my-sms. Alfanumerico / trattino.",
|
||||
"settings.messengers.password": "Password",
|
||||
"settings.messengers.retries": "Tentativi",
|
||||
"settings.messengers.retriesHelp": "Numero di tentativi in caso di errore invio messaggio.",
|
||||
"settings.messengers.skipTLSHelp": "Ignora la verifica del nome dell'host sul certificato TLS.",
|
||||
"settings.messengers.timeout": "Periodo di inattività",
|
||||
"settings.messengers.timeoutHelp": "Tempo di attesa prima di una nuova attività sulla connessione prima della chiusura e cancellazione del pool (s per i secondi, m per i minuti).",
|
||||
"settings.messengers.url": "URL",
|
||||
"settings.messengers.urlHelp": "Radice URL del server Postback.",
|
||||
"settings.messengers.username": "Nome utente",
|
||||
"settings.needsRestart": "Settings changed. Pause all running campaigns and restart the app",
|
||||
"settings.performance.batchSize": "Dimensione del lotto",
|
||||
"settings.performance.batchSizeHelp": "Numero di iscritti da estrarre dal database in una sola iterazione. Ogni iterazione estrae gli iscritti dal database, invia loro i messaggi, poi passa all'iterazione seguente per estrarre il lotto successivo. Idealmente questo valore dovrebbe essere superiore alla velocità massima possibile (Concorrenza x Frequenza del messaggio).",
|
||||
"settings.performance.concurrency": "Concorrenza",
|
||||
"settings.performance.concurrencyHelp": "Numero di worker (threads) concorrenti massimo che invieranno i messaggi contemporaneamente.",
|
||||
"settings.performance.maxErrThreshold": "Soglia massima di errore",
|
||||
"settings.performance.maxErrThresholdHelp": "Numero di errori (esempio: SMTP scaduto durante l'invio delle mail) che una campagna in corso può tollerare prima di essere sospesa per verifica o intervento manuale. Imposta sur 0 per non andare mai in pausa.",
|
||||
"settings.performance.messageRate": "Frequenza del messaggio",
|
||||
"settings.performance.messageRateHelp": "Numero massimo di messaggi a inviare per worker in un secondo. Se concorrente = 10 e frequenza del messaggio = 10, allora fino a 10x10 = 100 messaggi possono essere emessi ogni secondo. Questo parametro, come il parametro concorrente, dovrebbe essere modificato per mantenere i messaggi uscenti ogni secondo al di sotto del limite della velocità dei server dei messaggi destinatari.",
|
||||
"settings.performance.name": "Performance",
|
||||
"settings.performance.slidingWindow": "Attiva un limite tramite finestra scorrevole",
|
||||
"settings.performance.slidingWindowDuration": "Durata",
|
||||
"settings.performance.slidingWindowDurationHelp": "Durata del periodo della finestra scorrevole (m per minuto, h per ora).",
|
||||
"settings.performance.slidingWindowHelp": "Limita il numero totale di messaggi inviati durante un dato periodo. Una volta raggiunto questo limite, l'invio dei messaggi è sospeso fino a che la finestra di tempo sia passata.",
|
||||
"settings.performance.slidingWindowRate": "Num. max messaggi.",
|
||||
"settings.performance.slidingWindowRateHelp": "Numero massimo di messaggi da inviare nella durata della finestra.",
|
||||
"settings.privacy.allowBlocklist": "Autorizza la lista di blocco",
|
||||
"settings.privacy.allowBlocklistHelp": "Autorizza gli iscritti a cancellare l'iscrizione da tutte le liste di diffusione e a segnalarsi come bloccati?",
|
||||
"settings.privacy.allowExport": "Autorizza l'esportazione",
|
||||
"settings.privacy.allowExportHelp": "Autorizzi gli iscritti a esportare i dati raccolti su di loro?",
|
||||
"settings.privacy.allowWipe": "Autorizza la cancellazione",
|
||||
"settings.privacy.allowWipeHelp": "Autorizza gli iscritti a cancellare le loro iscrizioni e tutti gli altri dati dal database. Le visualizzazioni della campagna e i clic sui link verranno anch'essi cancellati, mentre i contatori globali delle visualizzazioni e del numero di clic restano invariati (nessun iscritto vi è associato) in modo che le statistiche non siano compromesse.",
|
||||
"settings.privacy.individualSubTracking": "Follow-up individuale degli abbonati",
|
||||
"settings.privacy.individualSubTrackingHelp": "Monitora le visualizzazioni e i clic della campagna per iscritto. Quando è disabilitato, il follow-up delle visualizzazioni e dei clic, si effettua senza essere legato agli iscritti individuali.",
|
||||
"settings.privacy.listUnsubHeader": "Includere l'intestazione `List-Unsubscribe`",
|
||||
"settings.privacy.listUnsubHeaderHelp": "Includere intestazioni di annullamento dell'iscrizione che consentono agli utenti di annullare l'iscrizione con un clic dal proprio client di posta elettronica.",
|
||||
"settings.privacy.name": "Vita privata",
|
||||
"settings.restart": "Restart",
|
||||
"settings.smtp.authProtocol": "Protocollo di autenticazione",
|
||||
"settings.smtp.customHeaders": "Intestazioni personalizzate",
|
||||
"settings.smtp.customHeadersHelp": "Matrice facoltativa di intestazioni di posta elettronica da includere in tutti i messaggi inviati da questo server. Ad esempio: [{\"X-Custom\": \"value\"}, {\"X-Custom2\": \"value\"}]",
|
||||
"settings.smtp.enabled": "Attivata",
|
||||
"settings.smtp.heloHost": "Nome host HELO",
|
||||
"settings.smtp.heloHostHelp": "Facoltativo. Alcuni server SMTP richiedono un nome di dominio completo nel nome host. Per impostazione predefinita, HELLOs viene fornito con `localhost`. Impostare questo parametro se deve essere utilizzato un nome host personalizzato.",
|
||||
"settings.smtp.host": "Host",
|
||||
"settings.smtp.hostHelp": "Indirizzo host del server SMTP.",
|
||||
"settings.smtp.idleTimeout": "Periodo di inattività",
|
||||
"settings.smtp.idleTimeoutHelp": "Tempo di attesa prima di una nuova attività sulla connessione prima della chiusura e cancellazione del pool (s per i secondi, m per i minuti).",
|
||||
"settings.smtp.maxConns": "Nb. connessioni max.",
|
||||
"settings.smtp.maxConnsHelp": "Numero massimo di connessioni simultanee al server SMTP.",
|
||||
"settings.smtp.name": "SMTP",
|
||||
"settings.smtp.password": "Password",
|
||||
"settings.smtp.passwordHelp": "Entra per modificare",
|
||||
"settings.smtp.port": "Porto",
|
||||
"settings.smtp.portHelp": "Porta del server SMTP.",
|
||||
"settings.smtp.retries": "Tentativi",
|
||||
"settings.smtp.retriesHelp": "Numero di tentativi in caso di errore invio messaggio.",
|
||||
"settings.smtp.setCustomHeaders": "Definisci intestazioni personalizzate",
|
||||
"settings.smtp.skipTLS": "Ignora controllo TLS",
|
||||
"settings.smtp.skipTLSHelp": "Ignora la verifica del nome dell'host sul certificato TLS.",
|
||||
"settings.smtp.tls": "TLS",
|
||||
"settings.smtp.tlsHelp": "Attiva STARTTLS.",
|
||||
"settings.smtp.username": "Nome utente",
|
||||
"settings.smtp.waitTimeout": "Tempo d'attesa",
|
||||
"settings.smtp.waitTimeoutHelp": "Tempo di attesa per una nuova attività su una connessione prima che venga chiusa e rimossa dal pool (s per secondo, m per minuto).",
|
||||
"settings.title": "Parametri",
|
||||
"settings.updateAvailable": "A new update {version} is available.",
|
||||
"subscribers.advancedQuery": "Avanzate",
|
||||
"subscribers.advancedQueryHelp": "Espressione SQL parziale per interrogare gli attributi del sottoscrittore",
|
||||
"subscribers.attribs": "Attributi",
|
||||
"subscribers.attribsHelp": "Gli attributi sono definiti come una mappa JSON, ad esempio:",
|
||||
"subscribers.blocklistedHelp": "Gli abbonati bloccati non riceveranno mai e-mail.",
|
||||
"subscribers.confirmBlocklist": "Lista di blocco {num} iscritto(i)?",
|
||||
"subscribers.confirmDelete": "Elimina {num} iscrittoi(i)?",
|
||||
"subscribers.confirmExport": "Esporta {num} iscritto(i)?",
|
||||
"subscribers.downloadData": "Scarica i dati",
|
||||
"subscribers.email": "Email",
|
||||
"subscribers.emailExists": "Email già esistente.",
|
||||
"subscribers.errorBlocklisting": "Errore durante il blocco degli iscritti: {error}",
|
||||
"subscribers.errorInvalidIDs": "Una o più credenziali fornite non valide: {error}",
|
||||
"subscribers.errorNoIDs": "Nessun ID fornito.",
|
||||
"subscribers.errorNoListsGiven": "Nessuna lista fornita.",
|
||||
"subscribers.errorPreparingQuery": "Errore durante la preparazione della richiesta dell'iscritto: {error}",
|
||||
"subscribers.errorSendingOptin": "Errore durante l'invio dell'e-mail di attivazione.",
|
||||
"subscribers.export": "Esportazione",
|
||||
"subscribers.invalidAction": "Azione non valida.",
|
||||
"subscribers.invalidEmail": "E-mail non valida.",
|
||||
"subscribers.invalidJSON": "JSON non valido negli attributi.",
|
||||
"subscribers.invalidName": "Nome errato.",
|
||||
"subscribers.listChangeApplied": "Modifica della lista eseguita.",
|
||||
"subscribers.lists": "Liste",
|
||||
"subscribers.listsHelp": "Le liste i cui iscritti hanno annullato l'iscrizione non possono essere eliminate.",
|
||||
"subscribers.listsPlaceholder": "Liste a cui iscriversi",
|
||||
"subscribers.manageLists": "Gestisci liste",
|
||||
"subscribers.markUnsubscribed": "Segna come non iscritto",
|
||||
"subscribers.newSubscriber": "Nuovo iscritto",
|
||||
"subscribers.numSelected": "{num} iscritto(i) selezionato(i)",
|
||||
"subscribers.optinSubject": "Confermare l'iscrizione",
|
||||
"subscribers.query": "Richiesta",
|
||||
"subscribers.queryPlaceholder": "Email o nome",
|
||||
"subscribers.reset": "Ripristina",
|
||||
"subscribers.selectAll": "Seleziona tutto {num}",
|
||||
"subscribers.status.blocklisted": "Lista bloccata",
|
||||
"subscribers.status.confirmed": "Confermato",
|
||||
"subscribers.status.enabled": "Attivata",
|
||||
"subscribers.status.subscribed": "Iscritto",
|
||||
"subscribers.status.unconfirmed": "Non confermato",
|
||||
"subscribers.status.unsubscribed": "Iscrizione annullata",
|
||||
"subscribers.subscribersDeleted": "{num} iscritto(i) eliminato(i)",
|
||||
"templates.cantDeleteDefault": "Impossibile eliminare il modello predefinito",
|
||||
"templates.default": "Predefinito",
|
||||
"templates.dummyName": "Campagna di prova",
|
||||
"templates.dummySubject": "Oggetto della campagna di prova",
|
||||
"templates.errorCompiling": "Errore durante la compilazione del modello: {error}",
|
||||
"templates.errorRendering": "Messaggio di errore durante il rendering: {errore}",
|
||||
"templates.fieldInvalidName": "Lunghezza del nome non valida.",
|
||||
"templates.makeDefault": "Definisci per impostazione predefinita",
|
||||
"templates.newTemplate": "Nuovo modello",
|
||||
"templates.placeholderHelp": "Il segnaposto {placeholder} deve apparire esattamente una volta nel modello.",
|
||||
"templates.preview": "Anteprima",
|
||||
"templates.rawHTML": "HTML semplice"
|
||||
}
|
26
i18n/ml.json
26
i18n/ml.json
|
@ -2,6 +2,7 @@
|
|||
"_.code": "ml",
|
||||
"_.name": "മലയാളം (ml)",
|
||||
"admin.errorMarshallingConfig": "അഭ്യർത്ഥന ക്രമീകരിയ്ക്കുന്നതിൽ പരാജയപ്പെട്ടു: {error}",
|
||||
"campaigns.addAltText": "Add alternate plain text message",
|
||||
"campaigns.cantUpdate": "ഇപ്പോൾ നടന്നുകൊണ്ടിരിയ്ക്കുന്നതോ, അവസാനിച്ചതോ ആയ ക്യാമ്പേയ്ൻ പുതുക്കാനാകില്ല.",
|
||||
"campaigns.clicks": "ക്ലീക്കുകൾ",
|
||||
"campaigns.confirmDelete": "{name} നീക്കം ചെയ്യുക",
|
||||
|
@ -24,6 +25,7 @@
|
|||
"campaigns.fromAddress": "പ്രേക്ഷകൻ",
|
||||
"campaigns.fromAddressPlaceholder": "നിങ്ങളുടെ പേര് <noreply@yoursite.com>",
|
||||
"campaigns.invalid": "ക്യാമ്പേയ്ൻ അസാധുവാണ്",
|
||||
"campaigns.markdown": "Markdown",
|
||||
"campaigns.needsSendAt": "ക്യാമ്പേയ്ന് `send_at` തിയതി മുൻകൂട്ടി നിശ്ചയിക്കേണ്ടതുണ്ട്.",
|
||||
"campaigns.newCampaign": "പുതിയ ക്യാമ്പേയ്ൻ",
|
||||
"campaigns.noKnownSubsToTest": "ടെസ്റ്റ് ചെയ്യാൻ, വരിക്കാരുടെ പട്ടിക ശൂന്യമാണ്.",
|
||||
|
@ -42,6 +44,7 @@
|
|||
"campaigns.progress": "പുരോഗതി",
|
||||
"campaigns.queryPlaceholder": "പേരോ വിഷയമോ",
|
||||
"campaigns.rawHTML": "അസംസ്കൃത എച്. ടി. എം. എൽ",
|
||||
"campaigns.removeAltText": "Remove alternate plain text message",
|
||||
"campaigns.richText": "റിച്ച് ടെക്സ്റ്റ്",
|
||||
"campaigns.schedule": "ക്യാമ്പേയ്ൻ ആസൂത്രണം ചെയ്യുക",
|
||||
"campaigns.scheduled": "ആസൂത്രണം ചെയ്തു",
|
||||
|
@ -90,7 +93,9 @@
|
|||
"email.unsubHelp": "ഈ-മെയിലുകൾ ഇനി സ്വീകരിക്കേണ്ടതില്ലേ?",
|
||||
"forms.formHTML": "എച്. ടി. എം. എൽ ഫോം",
|
||||
"forms.formHTMLHelp": "മറ്റൊരു വെബ് പേജിൽ സബ്സ്ക്രിപ്ഷൻ ഫോം കാണിയ്ക്കുന്നതിന് താഴെക്കൊടുത്തിരിക്കുന്ന എച്. ടി. എം. എൽ ഉപയോഗിക്കുക.",
|
||||
"forms.noPublicLists": "There are no public lists to generate a forms.",
|
||||
"forms.publicLists": "പൊതു ലിസ്റ്റുകൾ",
|
||||
"forms.publicSubPage": "Public subscription page",
|
||||
"forms.selectHelp": "ഫോമിലേയ്ക്ക് ചേർക്കേണ്ട ലിസ്റ്റുകൾ.",
|
||||
"forms.title": "ഫോമുകൾ",
|
||||
"globals.buttons.add": "ചേർക്കുക",
|
||||
|
@ -108,13 +113,13 @@
|
|||
"globals.buttons.remove": "നീക്കം ചെയ്യുക",
|
||||
"globals.buttons.save": "സൂക്ഷിക്കുക",
|
||||
"globals.buttons.saveChanges": "മാറ്റങ്ങൾ സൂക്ഷിക്കുക",
|
||||
"globals.days.0": "ഞായർ",
|
||||
"globals.days.1": "തിങ്കൾ",
|
||||
"globals.days.2": "ചൊവ്വ",
|
||||
"globals.days.3": "ബുധൻ",
|
||||
"globals.days.4": "വ്യാഴം",
|
||||
"globals.days.5": "വെള്ളി",
|
||||
"globals.days.6": "ശനി",
|
||||
"globals.days.7": "ഞായർ",
|
||||
"globals.fields.createdAt": "നിർമ്മിച്ചത്",
|
||||
"globals.fields.id": "ഐഡി",
|
||||
"globals.fields.name": "പേര്",
|
||||
|
@ -242,6 +247,8 @@
|
|||
"public.errorTitle": "എറർ",
|
||||
"public.invalidFeature": "ഈ ഫീച്ചർ ലഭ്യമല്ല",
|
||||
"public.invalidLink": "കണ്ണി അസാധുവാണ്",
|
||||
"public.noListsAvailable": "No lists available to subscribe.",
|
||||
"public.noListsSelected": "No valid lists selected to subscribe.",
|
||||
"public.noSubInfo": "സ്ഥിരീകരിക്കാനായി വരിക്കാരനാകാനുള്ള അഭ്യർത്ഥനകളൊന്നുമില്ല",
|
||||
"public.noSubTitle": "വരിക്കാരാരുമില്ല",
|
||||
"public.notFoundTitle": "കണ്ടെത്തിയില്ല",
|
||||
|
@ -251,10 +258,14 @@
|
|||
"public.privacyTitle": "സ്വകാര്യതയും വിവരങ്ങളും",
|
||||
"public.privacyWipe": "നിങ്ങളുടെ വിവരങ്ങൾ എന്നന്നേയ്ക്കുമായി ഇല്ലാതാക്കുക",
|
||||
"public.privacyWipeHelp": "താങ്കൾ വരിക്കാരനായിരിക്കുന്നതും അനുബന്ധ വിവരങ്ങളും ഡേറ്റാബേസിൽ നിന്നും എന്നത്തേയ്ക്കുമായി നീക്കം ചെയ്യുക.",
|
||||
"public.sub": "Subscribe",
|
||||
"public.subConfirmed": "വരിക്കാരനായി",
|
||||
"public.subConfirmedTitle": "സ്ഥിരീകരിച്ചു",
|
||||
"public.subName": "Name (optional)",
|
||||
"public.subNotFound": "വരിക്കാരനെ കണ്ടത്തിയില്ല.",
|
||||
"public.subOptinPending": "An e-mail has been sent to you to confirm your subscription(s).",
|
||||
"public.subPrivateList": "സ്വകാര്യ ലിസ്റ്റ്",
|
||||
"public.subTitle": "Subscribe",
|
||||
"public.unsub": "വരിക്കാരനല്ലാതാകുക",
|
||||
"public.unsubFull": "ഭാവിയിലുള്ള ഇ-മെയിലുകളിൽനിന്നും ഒഴിവാകുക.",
|
||||
"public.unsubHelp": "ഇനിമേൽ ഈ ലിസ്റ്റിന്റെ വരിക്കാരനാകേണ്ട എന്നുറപ്പാണോ?",
|
||||
|
@ -262,11 +273,16 @@
|
|||
"public.unsubbedInfo": "നിങ്ങൾ വരിക്കാരനല്ലാതായി",
|
||||
"public.unsubbedTitle": "വരിക്കാരനല്ലാതാകുക",
|
||||
"public.unsubscribeTitle": "മെയിലിങ് ലിസ്റ്റിന്റെ വരിക്കാരനല്ലാതാകുക",
|
||||
"settings.confirmRestart": "Ensure running campaigns are paused. Restart?",
|
||||
"settings.duplicateMessengerName": "ഒരേ പേരിൽ ഒന്നിലധികം സന്ദശവാഹകർ: {name}",
|
||||
"settings.errorEncoding": "ക്രമീകരണം എൻകോഡ് ചെയ്യുന്നതിൽ തടസം നേരിട്ടു: {error}",
|
||||
"settings.errorNoSMTP": "കുറഞ്ഞപക്ഷം ഒരു എസ്. എം. ടീ. പീ ബ്ലൊക്കെങ്കിലും പ്രവർത്തനക്ഷമയിരിക്കണം",
|
||||
"settings.general.adminNotifEmails": "കാര്യനിര്വ്വാഹകർക്കുള്ള അറിയിപ്പ് ഇ-മെയിലുകൾ",
|
||||
"settings.general.adminNotifEmailsHelp": "ഇംപോർട്ട് ചെയ്തതിലുള്ള വിവരങ്ങൾ, ക്യാമ്പേയ്ൻ പൂർത്തീകരണം, പ്രശ്നങ്ങൾ എന്നിങ്ങനെയുള്ള പ്രധാനപ്പെട്ട കാര്യനിര്വ്വാഹകർക്കുള്ള അറിയിപ്പിനായുള്ള കോമാ ഉപയോഗിച്ച് വേർതിരിച്ച ഇ-മെയിൽ വിലാസങ്ങൾ.",
|
||||
"settings.general.checkUpdates": "Check for updates",
|
||||
"settings.general.checkUpdatesHelp": "Periodically check for new app releases and notify.",
|
||||
"settings.general.enablePublicSubPage": "Enable public subscription page",
|
||||
"settings.general.enablePublicSubPageHelp": "Show a public subscription page with all the public lists for people to subscribe.",
|
||||
"settings.general.faviconURL": "ഫാവ് ഐക്കൺ യൂ. ആർ. എൽ",
|
||||
"settings.general.faviconURLHelp": "(ഐച്ഛികം) വരിക്കാരനല്ലാതാകാനുള്ള പേജുപോലുള്ള പൊതുവായ പേജുകളിൽ കാണിക്കുന്നതിനുവേണ്ടിയുള്ള ഫാവ് ഐക്കണിന്റെ പൂർണ്ണ വെബ് വിലാസം.",
|
||||
"settings.general.fromEmail": "സ്ഥിരസ്ഥിതി `from` ഇ-മെയിൽ",
|
||||
|
@ -286,7 +302,7 @@
|
|||
"settings.media.s3.bucketTypePrivate": "സ്വകാര്യമായ",
|
||||
"settings.media.s3.bucketTypePublic": "പൊതുവായ",
|
||||
"settings.media.s3.key": "AWS പ്രവേശന വാക്യം",
|
||||
"settings.media.s3.region": "മേഘല",
|
||||
"settings.media.s3.region": "മേഖല",
|
||||
"settings.media.s3.secret": "AWS പ്രവേശന രഹസ്യം",
|
||||
"settings.media.s3.uploadExpiry": "അപ്ലോഡിന്റെ കാലാവധി",
|
||||
"settings.media.s3.uploadExpiryHelp": "(ഐച്ഛികം) മുൻകൂട്ടി നിർമ്മിക്കുന്ന യൂ. ആർ. എല്ലിനുള്ള സെക്കന്റിലുള്ള TTL വ്യക്തമാക്കുക . സ്വകാര്യ ബക്കറ്റുകൾക്ക് മാത്രമേ ബാധകമാകൂ (s, m, h, d എന്നിവ യഥാക്രമം സെക്കന്റ്, മിനുട്ട്, മണിക്കൂർ, ദിവസങ്ങൾ എന്നിവയെ സൂചിപ്പിക്കുന്നു).",
|
||||
|
@ -299,7 +315,7 @@
|
|||
"settings.messengers.maxConnsHelp": "എസ്. എം. ടീ. പി സേർവ്വറിലേയ്ക്കുള്ള പരമാവധി സമാന്തര കണക്ഷനുകൾ.",
|
||||
"settings.messengers.messageDiscard": "മാറ്റങ്ങൾ നിരസിക്കട്ടെ?",
|
||||
"settings.messengers.messageSaved": "ക്രമീകരണങ്ങൾ സംരക്ഷിച്ചു. ആപ്പ് പുനരാരംഭിക്കുന്നു ...",
|
||||
"settings.messengers.name": "സന്തേശ വാഹകർ",
|
||||
"settings.messengers.name": "സന്ദേശ വാഹകർ",
|
||||
"settings.messengers.nameHelp": "ഉദാഹരണം: എന്റെ-ലിസ്റ്റ്. അക്കങ്ങളും അക്ഷരങ്ങളും / ഡാഷും.",
|
||||
"settings.messengers.password": "രഹസ്യ വാക്ക്",
|
||||
"settings.messengers.retries": "പുനഃശ്രമങ്ങൾ",
|
||||
|
@ -310,6 +326,7 @@
|
|||
"settings.messengers.url": "യൂ. ആർ. എൽ",
|
||||
"settings.messengers.urlHelp": "പോസ്റ്റ്ബാക്ക് സേർവറിന്റെ റൂട്ട് യൂ. ആർ. എൽ.",
|
||||
"settings.messengers.username": "ഉപഭോക്ത്ര നാമം",
|
||||
"settings.needsRestart": "Settings changed. Pause all running campaigns and restart the app",
|
||||
"settings.performance.batchSize": "ബാച്ചിന്റെ വലിപ്പം",
|
||||
"settings.performance.batchSizeHelp": "ഒരാവർത്തനത്തിൽ എത്ര വരിക്കാരെ ഡാറ്റാബേസിൽ നിന്നും എടുക്കണം. ഓരോ തവണയും വരിക്കാരെ ഡാറ്റാബേസിൽ നിന്നും എടുക്കുകയും അടുത്ത ആവർത്തനത്തിൽ അടുത്ത ബാച്ചിനെ എടുക്കുകയും അങ്ങനെ തുടരുകയും ചെയ്യും. ഈ മൂല്യം പരമാവധി ത്രൂപുട്ടിനേക്കാളും (concurrency * message_rate) കൂടുതലാകുന്നതാണ് നല്ലത്.",
|
||||
"settings.performance.concurrency": "കൺകറൻസി",
|
||||
|
@ -336,6 +353,7 @@
|
|||
"settings.privacy.listUnsubHeader": "`List-Unsubscribe` തലക്കെട്ട് കൂട്ടിച്ചേർക്കുക",
|
||||
"settings.privacy.listUnsubHeaderHelp": "ഒറ്റ ക്ലിക്കിലൂടെ വരിക്കാനല്ലാതാക്കാൻ ഇ-മെയിൽ ക്ലൈന്റിൽ വരിക്കാരനല്ലാതാക്കാനുള്ള തലക്കെട്ട് കൂട്ടിച്ചേർക്കുക.",
|
||||
"settings.privacy.name": "സ്വകാര്യത",
|
||||
"settings.restart": "Restart",
|
||||
"settings.smtp.authProtocol": "പ്രാമാണീകരണ പ്രോട്ടോക്കോൾ",
|
||||
"settings.smtp.customHeaders": "ഇഷ്ടാനുസൃത തലക്കെട്ടുകൾ",
|
||||
"settings.smtp.customHeadersHelp": "ഈ സേർവറിൽ നിന്നും അയക്കുന്ന എല്ലാ ഈ-മെയിലിലും ഉണ്ടാകേണ്ട ഇഷ്ടാനുസൃത തലക്കെട്ടുകൾ. ഉദാഹരണം: [{\"X-Custom\": \"value\"}, {\"X-Custom2\": \"value\"}]",
|
||||
|
@ -364,6 +382,7 @@
|
|||
"settings.smtp.waitTimeout": "കാത്തുനിൽക്കുന്നതിനുള്ള സമയപരിധി",
|
||||
"settings.smtp.waitTimeoutHelp": "പൂളിൽ നിന്നും കണക്ഷൻ വിച്ഛേദിയ്ക്കുന്നതിനുമുമ്പ് പുതിയ പ്രവർത്തനത്തിനായി കാത്തുനിൽക്കുന്നതിനുള്ള സമയപരിധി(s സെക്കന്റിന്, m മിനുട്ടിന്).",
|
||||
"settings.title": "ക്രമീകരണങ്ങൾ",
|
||||
"settings.updateAvailable": "A new update {version} is available.",
|
||||
"subscribers.advancedQuery": "വിപുലമായത്",
|
||||
"subscribers.advancedQueryHelp": "വരിക്കാരുടെ വിവരങ്ങൾ മനസിലാക്കുന്നതിനായുള്ള ഭാഗികമായ SQL പ്രയേഗം",
|
||||
"subscribers.attribs": "ആട്രിബ്യൂട്ടുകൾ",
|
||||
|
@ -400,6 +419,7 @@
|
|||
"subscribers.reset": "പുനഃസജ്ജമാക്കുക",
|
||||
"subscribers.selectAll": "{num} എല്ലാം തിരഞ്ഞടുക്കുക",
|
||||
"subscribers.status.blocklisted": "തടയുന്ന പട്ടികയിൽ ചേർത്തു",
|
||||
"subscribers.status.confirmed": "Confirmed",
|
||||
"subscribers.status.enabled": "പ്രവർത്തനക്ഷമാക്കി",
|
||||
"subscribers.status.subscribed": "വരിക്കാരനായി",
|
||||
"subscribers.status.unconfirmed": "തീർച്ചപ്പെടുത്താത്തത്",
|
||||
|
|
440
i18n/pl.json
Normal file
440
i18n/pl.json
Normal file
|
@ -0,0 +1,440 @@
|
|||
{
|
||||
"_.code": "pl",
|
||||
"_.name": "Polski (pl)",
|
||||
"admin.errorMarshallingConfig": "Błąd przerabiania konfiguracji: {error}",
|
||||
"campaigns.addAltText": "Dodaj alternatywną wiadomość jako plain text",
|
||||
"campaigns.cantUpdate": "Nie można aktualizować aktywnej ani zakończonej kampanii",
|
||||
"campaigns.clicks": "Kliknięć",
|
||||
"campaigns.confirmDelete": "Usuń {name}",
|
||||
"campaigns.confirmSchedule": "Ta kampania rozpocznie się automatyczne i zadanej dacie czasie. Czy zaplanować teraz?",
|
||||
"campaigns.confirmSwitchFormat": "Treść może utracić formatowanie. Kontynuować?",
|
||||
"campaigns.content": "Zgoda",
|
||||
"campaigns.contentHelp": "Zgoda tutaj",
|
||||
"campaigns.continue": "Kontynuuj",
|
||||
"campaigns.copyOf": "Kopia {name}",
|
||||
"campaigns.dateAndTime": "Data i czas",
|
||||
"campaigns.ended": "Zakończona",
|
||||
"campaigns.errorSendTest": "Błąd wysyłania testu: {error}",
|
||||
"campaigns.fieldInvalidBody": "Błąd kompilacji treści kampanii: {error}",
|
||||
"campaigns.fieldInvalidFromEmail": "Nieprawidłowy `from_email`.",
|
||||
"campaigns.fieldInvalidListIDs": "Nieprawidłowa lista identyfikatorów (IDs)",
|
||||
"campaigns.fieldInvalidMessenger": "Nieznany komunikator {name}.",
|
||||
"campaigns.fieldInvalidName": "Nieprawidłowa długość dla nazwy,",
|
||||
"campaigns.fieldInvalidSendAt": "Zaplanowana data powinna być w przyszłości,",
|
||||
"campaigns.fieldInvalidSubject": "Nieprawidłowa długość tytułu",
|
||||
"campaigns.fromAddress": "Adres od",
|
||||
"campaigns.fromAddressPlaceholder": "Twoja Nazwa <noreply@yoursite.com>",
|
||||
"campaigns.invalid": "Nieprawidłowa kampania",
|
||||
"campaigns.markdown": "Markdown",
|
||||
"campaigns.needsSendAt": "Kampania wymaga daty w celu zaplanowania.",
|
||||
"campaigns.newCampaign": "Nowa kampania",
|
||||
"campaigns.noKnownSubsToTest": "Brak znanych subskrybentów do testów.",
|
||||
"campaigns.noOptinLists": "Nie znaleziono list typu opt-in do stworzenia kampanii.",
|
||||
"campaigns.noSubs": "Nie ma subskrybentów w wybranej liście w celu stworzenia kampanii.",
|
||||
"campaigns.noSubsToTest": "Brak subskrybentów do wyboru.",
|
||||
"campaigns.notFound": "Kampania nieznaleziona.",
|
||||
"campaigns.onlyActiveCancel": "Tylko aktywne kampanie mogą być anulowane.",
|
||||
"campaigns.onlyActivePause": "Tylko aktywne kampanie mogą być pauzowane.",
|
||||
"campaigns.onlyDraftAsScheduled": "Tylko szkice kampanii mogą być planowane.",
|
||||
"campaigns.onlyPausedDraft": "Tylko kampanie pauzowane i szkice mogą być startowane.",
|
||||
"campaigns.onlyScheduledAsDraft": "Tylko planowane kampanie mogą być zapisane jako szkic.",
|
||||
"campaigns.pause": "Pauza",
|
||||
"campaigns.plainText": "Plain text",
|
||||
"campaigns.preview": "Podgląd",
|
||||
"campaigns.progress": "Postęp",
|
||||
"campaigns.queryPlaceholder": "Nazwa lub temat",
|
||||
"campaigns.rawHTML": "Raw HTML",
|
||||
"campaigns.removeAltText": "Usuń alternatywną treść typu plain text",
|
||||
"campaigns.richText": "Wzbogacony format tekstowy (Rich text)",
|
||||
"campaigns.schedule": "Zaplanuj kampanię",
|
||||
"campaigns.scheduled": "Zaplanowana",
|
||||
"campaigns.send": "Wyślij",
|
||||
"campaigns.sendLater": "Wyślij później",
|
||||
"campaigns.sendTest": "Wyślij wiadomość testową",
|
||||
"campaigns.sendTestHelp": "Naciśnij Enter po wypisaniu adresu w celu dodania kolejnych odbiorców. Adresy muszą należeć do istniejących subskrybentów.",
|
||||
"campaigns.sendToLists": "Listy do których wysłać",
|
||||
"campaigns.sent": "Wysłana",
|
||||
"campaigns.start": "Wystartuj kampanię",
|
||||
"campaigns.started": "\"{name}\" wystartowana",
|
||||
"campaigns.startedAt": "Wystartowana",
|
||||
"campaigns.stats": "Statystyki",
|
||||
"campaigns.status.cancelled": "Anulowana",
|
||||
"campaigns.status.draft": "Szkic",
|
||||
"campaigns.status.finished": "Zakończona",
|
||||
"campaigns.status.paused": "Spauzowana",
|
||||
"campaigns.status.running": "W trakcie",
|
||||
"campaigns.status.scheduled": "Zaplanowana",
|
||||
"campaigns.statusChanged": "\"{name}\" jest {status}",
|
||||
"campaigns.subject": "Temat",
|
||||
"campaigns.testEmails": "E-maile",
|
||||
"campaigns.testSent": "Wiadomość testowa wysłana",
|
||||
"campaigns.timestamps": "Sygnatury czasowe",
|
||||
"campaigns.views": "Wyświetlenia",
|
||||
"dashboard.campaignViews": "Wyświetlenia kampanii",
|
||||
"dashboard.linkClicks": "Kliknięcia linków",
|
||||
"dashboard.messagesSent": "Wiadomości wysłane ",
|
||||
"dashboard.orphanSubs": "Porzucone",
|
||||
"email.data.info": "Kopia wszystkich zarejestrowanych danych o Tobie jest dołączona jako plik w formacie JSON. Może zostać otworzona w edytorze tekstu.",
|
||||
"email.data.title": "Twoje dane",
|
||||
"email.optin.confirmSub": "Potwierdź subskrypcję",
|
||||
"email.optin.confirmSubHelp": "Potwierdź subskrypcję naciskając przycisk poniżej.",
|
||||
"email.optin.confirmSubInfo": "Zostałeś dodany(a) do następujących list:",
|
||||
"email.optin.confirmSubTitle": "Potwierdź subskrypcję",
|
||||
"email.optin.confirmSubWelcome": "Cześć",
|
||||
"email.optin.privateList": "Lista prywatna",
|
||||
"email.status.campaignReason": "Powód",
|
||||
"email.status.campaignSent": "Wysłane",
|
||||
"email.status.campaignUpdateTitle": "Aktualizacja kampanii",
|
||||
"email.status.importFile": "Plik",
|
||||
"email.status.importRecords": "Rekordy",
|
||||
"email.status.importTitle": "Importuj aktualizacjię",
|
||||
"email.status.status": "Status",
|
||||
"email.unsub": "Odsubskrybuj",
|
||||
"email.unsubHelp": "Nie chcesz otrzymywać tych maili?",
|
||||
"forms.formHTML": "Formularz HTML",
|
||||
"forms.formHTMLHelp": "Użyj następującego kodu HTML w celu wyświetlenia formularza na zewnętrznej stronie. Formularz powinien mieć pole z adresem email i jedno lub więcej pól z `l` (UUID listy). Pole z nazwą jest opcjonalne.",
|
||||
"forms.noPublicLists": "Nie ma publicznych list do wygenerowania formularza.",
|
||||
"forms.publicLists": "Publiczne listy",
|
||||
"forms.publicSubPage": "Publiczna strona subskrypcji",
|
||||
"forms.selectHelp": "Wybierz listy do dodania do formularza",
|
||||
"forms.title": "Formularze",
|
||||
"globals.buttons.add": "Dodanj",
|
||||
"globals.buttons.addNew": "Dodaj nowe",
|
||||
"globals.buttons.cancel": "Anuluj",
|
||||
"globals.buttons.clone": "Klonuj",
|
||||
"globals.buttons.close": "Zamknij",
|
||||
"globals.buttons.continue": "Kontynuuj",
|
||||
"globals.buttons.delete": "Usuń",
|
||||
"globals.buttons.edit": "Edytuj",
|
||||
"globals.buttons.enabled": "Włączone",
|
||||
"globals.buttons.learnMore": "Dowiedz się więcej",
|
||||
"globals.buttons.new": "Nowe",
|
||||
"globals.buttons.ok": "Ok",
|
||||
"globals.buttons.remove": "Usuń",
|
||||
"globals.buttons.save": "Zapisz",
|
||||
"globals.buttons.saveChanges": "Zapisz zmiany",
|
||||
"globals.days.0": "Nie",
|
||||
"globals.days.1": "Pon",
|
||||
"globals.days.2": "Wt",
|
||||
"globals.days.3": "Śr",
|
||||
"globals.days.4": "Czw",
|
||||
"globals.days.5": "Pt",
|
||||
"globals.days.6": "Sob",
|
||||
"globals.fields.createdAt": "Utworzone",
|
||||
"globals.fields.id": "ID",
|
||||
"globals.fields.name": "Nazwa",
|
||||
"globals.fields.status": "Status",
|
||||
"globals.fields.type": "Typ",
|
||||
"globals.fields.updatedAt": "Zaktualizowano",
|
||||
"globals.fields.uuid": "UUID",
|
||||
"globals.messages.confirm": "Na pewno?",
|
||||
"globals.messages.created": "\"{name}\" utworzono",
|
||||
"globals.messages.deleted": "\"{name}\" usunięto",
|
||||
"globals.messages.emptyState": "Nic tutaj nie ma",
|
||||
"globals.messages.errorCreating": "Błąd podczas tworzenia {name}: {error}",
|
||||
"globals.messages.errorDeleting": "Błąd podczas usuwania {name}: {error}",
|
||||
"globals.messages.errorFetching": "Błąd podczas pobierania {name}: {error}",
|
||||
"globals.messages.errorUUID": "Błąd podczas generowania UUID: {error}",
|
||||
"globals.messages.errorUpdating": "Błąd podczas aktualizacji {name}: {error}",
|
||||
"globals.messages.invalidID": "Nieprawidłowy iD",
|
||||
"globals.messages.invalidUUID": "Nieprawidłowy UUID",
|
||||
"globals.messages.notFound": "{name} nie znaleziono",
|
||||
"globals.messages.passwordChange": "Podaj wartość do zmiany",
|
||||
"globals.messages.updated": "\"{name}\" zaktualizowano",
|
||||
"globals.months.1": "Sty",
|
||||
"globals.months.10": "Paź",
|
||||
"globals.months.11": "Lis",
|
||||
"globals.months.12": "Gru",
|
||||
"globals.months.2": "Lut",
|
||||
"globals.months.3": "Mar",
|
||||
"globals.months.4": "Kwie",
|
||||
"globals.months.5": "Maj",
|
||||
"globals.months.6": "Czer",
|
||||
"globals.months.7": "Lip",
|
||||
"globals.months.8": "Sie",
|
||||
"globals.months.9": "Wrz",
|
||||
"globals.terms.campaign": "Kampania | Kampanie",
|
||||
"globals.terms.campaigns": "Kampanie",
|
||||
"globals.terms.dashboard": "Przegląd",
|
||||
"globals.terms.list": "Lista | Listy",
|
||||
"globals.terms.lists": "Listy",
|
||||
"globals.terms.media": "Media | Media",
|
||||
"globals.terms.messenger": "Komunikator | Komunikatory",
|
||||
"globals.terms.messengers": "Komunikatory",
|
||||
"globals.terms.settings": "Ustawienia",
|
||||
"globals.terms.subscriber": "Sybskrypcja | Sybskrypcje",
|
||||
"globals.terms.subscribers": "Sybskrypcje",
|
||||
"globals.terms.tag": "Tag | Tagi",
|
||||
"globals.terms.tags": "Tagi",
|
||||
"globals.terms.template": "Szablon | Szablony",
|
||||
"globals.terms.templates": "Szablony",
|
||||
"import.alreadyRunning": "Importowanie jest już uruchomione. Poczekaj, aż się zakończy, albo zatrzymaj je przed ponowną próbą.",
|
||||
"import.blocklist": "Lista zablokowanych",
|
||||
"import.csvDelim": "Separator CSV",
|
||||
"import.csvDelimHelp": "Domyślnym separatorem jest przecinek.",
|
||||
"import.csvExample": "Przykładowy \"surowy\" CSV.",
|
||||
"import.csvFile": "Plik CSV lub ZIP",
|
||||
"import.csvFileHelp": "Naciśnij lub przerzuć plik CSV lub ZIP w to miejsce.",
|
||||
"import.errorCopyingFile": "Błąd kopiowania pliku: {error}",
|
||||
"import.errorProcessingZIP": "Błąd procesowania pliku ZIP: {error}",
|
||||
"import.errorStarting": "Błąd rozpoczynania importu: {error}",
|
||||
"import.importDone": "Zrobione",
|
||||
"import.importStarted": "Import rozpoczęty",
|
||||
"import.instructions": "Instrukcje",
|
||||
"import.instructionsHelp": "Wrzuć plik CSV lub ZIP z pojedynczym plikiem CSV w celu masowego importowania subskybentów. Plik CSV powinien posiadać wskazane nagłówki kolumn z dokładnie tymi nazwami. Atrybuty (opcjonalne) powinny być zapisane w poprawnym formacje JSON z podwójnie escapowanymi cudzysłowami.",
|
||||
"import.invalidDelim": "Separator powinien być pojedynczym znakiem.",
|
||||
"import.invalidFile": "Nieprawidłowy plik: {error}",
|
||||
"import.invalidMode": "Nieprawidłowy tryp",
|
||||
"import.invalidParams": "Nieprawidłowe parametry: {error}",
|
||||
"import.listSubHelp": "Listy do subskrybowania.",
|
||||
"import.mode": "Tryb",
|
||||
"import.overwrite": "Nadpisać?",
|
||||
"import.overwriteHelp": "Nadpisać nazwy i atrybuty istniejących subskrybentów?",
|
||||
"import.recordsCount": "{num} / {total} rekordów",
|
||||
"import.stopImport": "Zatrzymaj import",
|
||||
"import.subscribe": "Subskrypcje",
|
||||
"import.title": "Importuj subskrypcje",
|
||||
"import.upload": "Wyślij",
|
||||
"lists.confirmDelete": "Jesteś pewny(a)? To nie usunie subskrybcji.",
|
||||
"lists.confirmSub": "Potwierdź subskrypcję dla {name}",
|
||||
"lists.invalidName": "Nieprawidłowa nazwa",
|
||||
"lists.newList": "Nowa lista",
|
||||
"lists.optin": "Opt-in",
|
||||
"lists.optinHelp": "Podwójny opt-in wysyła e-mail do subskrybenta z zapytaniem o potwierdzenie. W listach z podwójnym opt-in kampanie są wysyłane tylko do potwierdzonych subskrybentów.",
|
||||
"lists.optinTo": "Opt-in do {name}",
|
||||
"lists.optins.double": "Podwójny opt-in",
|
||||
"lists.optins.single": "Pojedynczy opt-in",
|
||||
"lists.sendCampaign": "Wyślij kampanię",
|
||||
"lists.sendOptinCampaign": "Wyślij kampanię opt-in",
|
||||
"lists.type": "Typ",
|
||||
"lists.typeHelp": "Publiczne listy są otwarte do świata i każdy może się zapisać. Nazwy są widoczne np. na stronie do zarządzania subskrypcją.",
|
||||
"lists.types.private": "Prywatna",
|
||||
"lists.types.public": "Publiczna",
|
||||
"logs.title": "Logi",
|
||||
"media.errorReadingFile": "Błąd odczytu pliku: {error}",
|
||||
"media.errorResizing": "Błąd zmiany rozmiaru obrazu: {error}",
|
||||
"media.errorSavingThumbnail": "Błąd zapisywania miniaturki: {error}",
|
||||
"media.errorUploading": "Błąd wgrywania pliku: {error}",
|
||||
"media.invalidFile": "Nieprawidłowy plik: {error}",
|
||||
"media.title": "Media",
|
||||
"media.unsupportedFileType": "Niewspierany typ pliku ({type})",
|
||||
"media.upload": "Wysyłanie",
|
||||
"media.uploadHelp": "Kliknij lub przeciągnij jeden lub więcej plików tutaj",
|
||||
"media.uploadImage": "Wyślij obraz",
|
||||
"menu.allCampaigns": "Wszystkie kampanie",
|
||||
"menu.allLists": "Wszystkie listy",
|
||||
"menu.allSubscribers": "Wszyscy subskrybenci",
|
||||
"menu.dashboard": "Przegląd",
|
||||
"menu.forms": "Formularze",
|
||||
"menu.import": "Import",
|
||||
"menu.logs": "Logi",
|
||||
"menu.media": "Media",
|
||||
"menu.newCampaign": "Utwórz nową",
|
||||
"menu.settings": "Ustawienia",
|
||||
"public.campaignNotFound": "Wiadomość email nie została znaleziona.",
|
||||
"public.confirmOptinSubTitle": "Potwierdź subskrypcję",
|
||||
"public.confirmSub": "Potwierdź subskrypcję",
|
||||
"public.confirmSubInfo": "Zostałeś(aś) dodany(a) do następujących listy:",
|
||||
"public.confirmSubTitle": "Potwierdź",
|
||||
"public.dataRemoved": "Twoja subskrypcja i wszystkie powiązane dane została usunięta.",
|
||||
"public.dataRemovedTitle": "Dane usunięte",
|
||||
"public.dataSent": "Twoje dane został przesłane do Ciebie mailem w formie załącznika.",
|
||||
"public.dataSentTitle": "Dane przesłanie mailem",
|
||||
"public.errorFetchingCampaign": "Błąd pobierania wiadomości email.",
|
||||
"public.errorFetchingEmail": "Wiadomość email nie została znaleziona",
|
||||
"public.errorFetchingLists": "Błąd pobierania list. Spróbuj ponownie.",
|
||||
"public.errorProcessingRequest": "Błąd przetwarzania żądania. Spróbuj ponownie.",
|
||||
"public.errorTitle": "Błąd",
|
||||
"public.invalidFeature": "Ta funkcjonalność jest niedostępna.",
|
||||
"public.invalidLink": "Nieprawidłowy liny.",
|
||||
"public.noListsAvailable": "Brak list do subkskrybowania.",
|
||||
"public.noListsSelected": "Brak prawidłowych list wybranych do subskrybowania.",
|
||||
"public.noSubInfo": "Brak subskrypcji do potwierdzenia.",
|
||||
"public.noSubTitle": "Brak subskrypcji ",
|
||||
"public.notFoundTitle": "Nie znaleziono",
|
||||
"public.privacyConfirmWipe": "Czy jesteś pewny(a), że chcesz usunąć wszystkie swoje dane?",
|
||||
"public.privacyExport": "Eksportuj swoje dane",
|
||||
"public.privacyExportHelp": "Kopia twoich danych zostanie przesłana do ciebie mailem.",
|
||||
"public.privacyTitle": "Prywatność i dane",
|
||||
"public.privacyWipe": "Usuń swoje dane",
|
||||
"public.privacyWipeHelp": "Usuń wszystkie swoje subskrypcje i dane z nimi związanie permanentnie z bazy danych.",
|
||||
"public.sub": "Subsrkybuj",
|
||||
"public.subConfirmed": "Pomyślnie zasubskrybowano.",
|
||||
"public.subConfirmedTitle": "Potwierdzono",
|
||||
"public.subName": "Nazwa (optional)",
|
||||
"public.subNotFound": "Subskrypcja nie została znaleziona",
|
||||
"public.subOptinPending": "Została wysłana wiadomość w celu potwierdzenia subskrypcji.",
|
||||
"public.subPrivateList": "Lista prywatna",
|
||||
"public.subTitle": "Subskrybuj",
|
||||
"public.unsub": "Odsubskrybuj",
|
||||
"public.unsubFull": "Również odsubskrybuj od wszystkich przyszłych maili.",
|
||||
"public.unsubHelp": "Czy chcesz się wypisać z tej listy mailowej?",
|
||||
"public.unsubTitle": "Wypisz się",
|
||||
"public.unsubbedInfo": "Pomyślnie odsubskrybowano",
|
||||
"public.unsubbedTitle": "Odsubskrybowano",
|
||||
"public.unsubscribeTitle": "Wypisz się z listy mailingowej",
|
||||
"settings.confirmRestart": "Upewnij się, że uruchomione kampanie są zapauzowane. Zrestartować?",
|
||||
"settings.duplicateMessengerName": "Powtórzona nazwa komunikatora: {name}",
|
||||
"settings.errorEncoding": "Błąd szyfrowania ustawień: {error}",
|
||||
"settings.errorNoSMTP": "Co najmniej jeden blok SMTP powinien być aktywowany",
|
||||
"settings.general.adminNotifEmails": "Adres email do powiadomień admina",
|
||||
"settings.general.adminNotifEmailsHelp": "Lista maili oddzielona przecinkami do adminów, którym przesyłać informacje o importach, zakończonych kampaniach, błędach itd. ",
|
||||
"settings.general.checkUpdates": "Check for updates",
|
||||
"settings.general.checkUpdatesHelp": "Periodically check for new app releases and notify.",
|
||||
"settings.general.enablePublicSubPage": "Włącz publiczną stronę subskrypcji",
|
||||
"settings.general.enablePublicSubPageHelp": "Pokaż publiczną stronę do zapisu na subskrypcje publicznych list.",
|
||||
"settings.general.faviconURL": "URL Favicony",
|
||||
"settings.general.faviconURLHelp": "(Opcjonalnie) pełny URL do statycznej favicony. Będzie używana na takich stronach jak np strona do wypisania się ze subskrypcji.",
|
||||
"settings.general.fromEmail": "Domyślny email `od`",
|
||||
"settings.general.fromEmailHelp": "Domyślny email `od` do pokazania w wychodzących kampaniach emailowych. Może zostać zmienione w kampanii.",
|
||||
"settings.general.language": "Język",
|
||||
"settings.general.logoURL": "URL loga",
|
||||
"settings.general.logoURLHelp": "(Opcjonalne) pełny URL do statycznego loga. Będzie używana na takich stronach jak np strona do wypisania się ze subskrypcji.",
|
||||
"settings.general.name": "Ogólne",
|
||||
"settings.general.rootURL": "Bazowy URL",
|
||||
"settings.general.rootURLHelp": "Publiczny URL instalacji (bez slasha na końcu)",
|
||||
"settings.invalidMessengerName": "Nieprawidłowa nazwa komunikatora.",
|
||||
"settings.media.provider": "Dostawca",
|
||||
"settings.media.s3.bucket": "Komora (Bucket)",
|
||||
"settings.media.s3.bucketPath": "Ścieżka komory (Bucket path)",
|
||||
"settings.media.s3.bucketPathHelp": "Ścieżka w komorze do której wrzucać pliki. Domyślna to /",
|
||||
"settings.media.s3.bucketType": "Typ komory (Bucket type)",
|
||||
"settings.media.s3.bucketTypePrivate": "Prywatny",
|
||||
"settings.media.s3.bucketTypePublic": "Publiczny",
|
||||
"settings.media.s3.key": "Klucz dostępu AWS",
|
||||
"settings.media.s3.region": "Region",
|
||||
"settings.media.s3.secret": "Sekret dostępu AWS",
|
||||
"settings.media.s3.uploadExpiry": "Wygaśnięcie przesyłania",
|
||||
"settings.media.s3.uploadExpiryHelp": "(Opcjonalne) Zdefiniuj TTL (w sekundach) dla wygenerowanego podpisanego URL. Tylko dla prywatnych komór (bucketów) (s, m, h, d dla sekund, minut, godzin, dni).",
|
||||
"settings.media.title": "Wysyłka mediów",
|
||||
"settings.media.upload.path": "Ścieżka do wysyłki",
|
||||
"settings.media.upload.pathHelp": "Ścieżka do folderu do którego media będą wrzucane.",
|
||||
"settings.media.upload.uri": "URI wysyłki",
|
||||
"settings.media.upload.uriHelp": "URI do wysyłki jest widoczna dla świata zewnętrznego. Wrzucone media do upload_path będą publicznie dostępne pod {root_url} np https://listmonk.yoursite.com/uploads.",
|
||||
"settings.messengers.maxConns": "Maksymalna liczba połąćzeń",
|
||||
"settings.messengers.maxConnsHelp": "Maksymalna liczba jednoczesnych połączeń do serwera.",
|
||||
"settings.messengers.messageDiscard": "Odrzucić zmiany?",
|
||||
"settings.messengers.messageSaved": "Ustawienia zapisane. Przeładowuję aplikację...",
|
||||
"settings.messengers.name": "Komunikatory",
|
||||
"settings.messengers.nameHelp": "np: my-sms. Alfanumeryczne / myślnik.",
|
||||
"settings.messengers.password": "Hasło",
|
||||
"settings.messengers.retries": "Ponowne próby",
|
||||
"settings.messengers.retriesHelp": "Liczba ponownych prób przed niepowodzeniem.",
|
||||
"settings.messengers.skipTLSHelp": "Pomiń sprawdzanie nazwy hosta w certyfikacie TLS.",
|
||||
"settings.messengers.timeout": "Czas bezczynności",
|
||||
"settings.messengers.timeoutHelp": "Czas czekania na nową aktywność na połączeniu przed jej zamknięciem i usunięciem z puli (s dla sekud, m dla minut)",
|
||||
"settings.messengers.url": "URL",
|
||||
"settings.messengers.urlHelp": "Bazowy URL serwera Postback.",
|
||||
"settings.messengers.username": "Nazwa użytkownika",
|
||||
"settings.needsRestart": "Ustawienia zmienione. Zatrzymaj wszystkie aktywne kampanie i uruchom ponownie aplikację",
|
||||
"settings.performance.batchSize": "Rozmiar paczki",
|
||||
"settings.performance.batchSizeHelp": "Liczba subskrybentów do pobrania z bazy danych przy jednej iteracji. Każda iteracja pobiera subskrybentów z bazy danych, wysyła do nich wiadomości, a następnie przechodzi do następnej iteracji. W idealnym przypadku powinno to być większe niż maksymalna przepustowość (liczba wątków * prędkość wysyłania wiadomości)",
|
||||
"settings.performance.concurrency": "Wielowątkowość",
|
||||
"settings.performance.concurrencyHelp": "Maksymalna liczba jednoczesnych workerów (wątków), która będzie wysyłała wiadomości jednocześnie.",
|
||||
"settings.performance.maxErrThreshold": "Maksymalny prób błędu",
|
||||
"settings.performance.maxErrThresholdHelp": "Liczba błędów (np: SMTP timeout), która będzie tolerowana przez aktywną kampanię. Po jej przekroczeniu zostanie zatrzymana w celu sprawdzenia przyczyny. Ustaw 0, żeby nigdy nie przerywać.",
|
||||
"settings.performance.messageRate": "Prędkość wysyłania wiadomości",
|
||||
"settings.performance.messageRateHelp": "Maximum number of messages to be sent out per second per worker in a second. If concurrency = 10 and message_rate = 10, then up to 10x10=100 messages may be pushed out every second. This, along with concurrency, should be tweaked to keep the net messages going out per second under the target message servers rate limits if any.",
|
||||
"settings.performance.name": "Wydajność",
|
||||
"settings.performance.slidingWindow": "Włącz limit dla okna czasowego",
|
||||
"settings.performance.slidingWindowDuration": "Czas trwania",
|
||||
"settings.performance.slidingWindowDurationHelp": "Czas trwania okna czasowego (m dla minut, h dla godzin).",
|
||||
"settings.performance.slidingWindowHelp": "Ustaw ograniczenie dla wiadomości, które są wysyłane w danym okresie czasu. Po osiągnięciu limitu wiadomości zostaną wstrzymane, aż okno czasowe stanie się znowu dostępne.",
|
||||
"settings.performance.slidingWindowRate": "Maksymalna liczba wiadomości",
|
||||
"settings.performance.slidingWindowRateHelp": "Maksymalna liczba wiadomości podczas okna czasowego.",
|
||||
"settings.privacy.allowBlocklist": "Zezwól na blokowanie",
|
||||
"settings.privacy.allowBlocklistHelp": "Czy zezwolić subskrybentom na wypisywanie się z wszystkich list mailowych i oznaczenie siebie jako zablokowanych?",
|
||||
"settings.privacy.allowExport": "Zezwól na eksportowanie danych",
|
||||
"settings.privacy.allowExportHelp": "Czy zezwolić subskrybentom na eksportowanie danych zebranych o nich?",
|
||||
"settings.privacy.allowWipe": "Zezwól na czyszczenie danych",
|
||||
"settings.privacy.allowWipeHelp": "Czy zezwolić subskrybentom na usuwanie ich samych razem z wszystkimi ich danymi? Wyświetlenia i liczba kliknięć zostaną zachowane, ale zostaną z nich usunięte informacje kto wykonał tę akcję.",
|
||||
"settings.privacy.individualSubTracking": "Śledzenie indywidualnych subskrybentów",
|
||||
"settings.privacy.individualSubTrackingHelp": "Śledź dane wyświetleń i kliknięć na poziomie pojedynczego subskrybenta. Jeśli wyłączone dane będą nadal zbierane, ale niepowiązane ze subskrybentami.",
|
||||
"settings.privacy.listUnsubHeader": "Dodawaj nagłówek `List-Unsubscribe`",
|
||||
"settings.privacy.listUnsubHeaderHelp": "Dodaj nagłówki do wypisania się z subskrypcji. Niektóre programy pocztowe umożliwiają wypisanie się jednym kliknięciem.",
|
||||
"settings.privacy.name": "Prywatność",
|
||||
"settings.restart": "Restart",
|
||||
"settings.smtp.authProtocol": "Protokół autoryzacji",
|
||||
"settings.smtp.customHeaders": "Niestandardowe nagłówki",
|
||||
"settings.smtp.customHeadersHelp": "Opcjonalna lista nagłówków do zamieszczania w wiadomościach we wszystkich wiadomościach wysłanych z tego serwera. np: [{\"X-Custom\": \"value\"}, {\"X-Custom2\": \"value\"}]",
|
||||
"settings.smtp.enabled": "Włączone",
|
||||
"settings.smtp.heloHost": "Nazwa hosta HELO",
|
||||
"settings.smtp.heloHostHelp": "Opcjonalne. Niektóre serwery SMTP wymagają FQDN w nazwie hosta. Domyślnie HELLO korzystają z `localhost`. Ustaw jeśli inny host powinien zostać użyty.",
|
||||
"settings.smtp.host": "Host",
|
||||
"settings.smtp.hostHelp": "Adres serwera SMTP.",
|
||||
"settings.smtp.idleTimeout": "Czas bezczynności",
|
||||
"settings.smtp.idleTimeoutHelp": "Czas czekania na nową aktywność na połączeniu przed jej zamknięciem i usunięciem z puli (s dla sekud, m dla minut).",
|
||||
"settings.smtp.maxConns": "Maksymalna liczba połączeń",
|
||||
"settings.smtp.maxConnsHelp": "Maksymalna liczba jednoczesnych połączeń do serwera SMTP.",
|
||||
"settings.smtp.name": "SMTP",
|
||||
"settings.smtp.password": "Hasło",
|
||||
"settings.smtp.passwordHelp": "Wpisz w celu zmiany",
|
||||
"settings.smtp.port": "Port",
|
||||
"settings.smtp.portHelp": "Port serwera SMTP.",
|
||||
"settings.smtp.retries": "Ponowne próby",
|
||||
"settings.smtp.retriesHelp": "Liczba ponownych prób przy niepowodzeniu",
|
||||
"settings.smtp.setCustomHeaders": "Ustaw niestandardowe nagłówki",
|
||||
"settings.smtp.skipTLS": "Pomiń weryfikację TLS",
|
||||
"settings.smtp.skipTLSHelp": "Pomiń sprawdzanie nazwy hosta dla certyfikatu TLS.",
|
||||
"settings.smtp.tls": "TLS",
|
||||
"settings.smtp.tlsHelp": "Włącz STARTTLS.",
|
||||
"settings.smtp.username": "Nazwa użytkownika",
|
||||
"settings.smtp.waitTimeout": "Czas oczekiwania",
|
||||
"settings.smtp.waitTimeoutHelp": "Czas czekania na nową aktywność na połączeniu przed jej zamknięciem i usunięciem z puli (s dla sekud, m dla minut).",
|
||||
"settings.title": "Ustawienia",
|
||||
"settings.updateAvailable": "Nowa wersja {version} jest dostępna.",
|
||||
"subscribers.advancedQuery": "Zaawansowane",
|
||||
"subscribers.advancedQueryHelp": "Częściowe zapytania SQL w celu pobrania atrybutów subsrybentów",
|
||||
"subscribers.attribs": "Atrybuty",
|
||||
"subscribers.attribsHelp": "Atrybuty są definiowane jako mapa w JSON, np:",
|
||||
"subscribers.blocklistedHelp": "Zablokowani subskrybenci nigdy nie dostaną żadnego emaila.",
|
||||
"subscribers.confirmBlocklist": "Czy zablokować {num} subskrybentów?",
|
||||
"subscribers.confirmDelete": "Usunąć {num} subskrybentów?",
|
||||
"subscribers.confirmExport": "Wyeksportować {num} subskrybentów?",
|
||||
"subscribers.downloadData": "Pobierz dane",
|
||||
"subscribers.email": "Email",
|
||||
"subscribers.emailExists": "Email już istnieje.",
|
||||
"subscribers.errorBlocklisting": "Błąd blokowania subskrybentów: {error}",
|
||||
"subscribers.errorInvalidIDs": "Podano jeden lub więcej nieprawidłowy ID: {error}",
|
||||
"subscribers.errorNoIDs": "Nie podano identyfikatorów.",
|
||||
"subscribers.errorNoListsGiven": "Nie podano list.",
|
||||
"subscribers.errorPreparingQuery": "Błąd przygotowywania zapytania o subskrypcje: {error}",
|
||||
"subscribers.errorSendingOptin": "Błąd wysyłania maila opt-in.",
|
||||
"subscribers.export": "Eksport",
|
||||
"subscribers.invalidAction": "Nieprawidłowa akcja.",
|
||||
"subscribers.invalidEmail": "Nieprawidłowy email.",
|
||||
"subscribers.invalidJSON": "Nieprawidłowy JSON w atrybutach.",
|
||||
"subscribers.invalidName": "Nieprawidłowa nazwa.",
|
||||
"subscribers.listChangeApplied": "Zmiana listy wykonana.",
|
||||
"subscribers.lists": "Listy",
|
||||
"subscribers.listsHelp": "Listy z których subskrybenci się wypisali nie mogą zostać usunięte.",
|
||||
"subscribers.listsPlaceholder": "Listy do subskrypcji",
|
||||
"subscribers.manageLists": "Zarządzaj listami",
|
||||
"subscribers.markUnsubscribed": "Oznacz jako odsubskrybowanych",
|
||||
"subscribers.newSubscriber": "Nowy subskrybent",
|
||||
"subscribers.numSelected": "Wybrano {num} subskrypcji",
|
||||
"subscribers.optinSubject": "Potwierdź subskrypcję",
|
||||
"subscribers.query": "Zapytanie",
|
||||
"subscribers.queryPlaceholder": "E-mail lub nazwa",
|
||||
"subscribers.reset": "Resetuj",
|
||||
"subscribers.selectAll": "Wybierz wszystkich {num}",
|
||||
"subscribers.status.blocklisted": "Zablokowany",
|
||||
"subscribers.status.confirmed": "Potwierdzony",
|
||||
"subscribers.status.enabled": "Aktywny",
|
||||
"subscribers.status.subscribed": "Subskrybuje",
|
||||
"subscribers.status.unconfirmed": "Niepotwierdzony",
|
||||
"subscribers.status.unsubscribed": "Odsubskrybowany",
|
||||
"subscribers.subscribersDeleted": "Usunięto {num} subskrybentów",
|
||||
"templates.cantDeleteDefault": "Nie można usunąć domyślnego szablonu",
|
||||
"templates.default": "Domyślny",
|
||||
"templates.dummyName": "Fikcyjna kampania",
|
||||
"templates.dummySubject": "Temat fikcyjnej kampanii",
|
||||
"templates.errorCompiling": "Błąd kompilacji szablonu: {error}",
|
||||
"templates.errorRendering": "Błąd renderowania wiadomości: {error}",
|
||||
"templates.fieldInvalidName": "Nieprawidłowa długość dla nazwy.",
|
||||
"templates.makeDefault": "Ustaw jako domślny",
|
||||
"templates.newTemplate": "Nowy szablon",
|
||||
"templates.placeholderHelp": "Symbol zastępczy {placeholder} powinien występować dokładnie raz w szablonie.",
|
||||
"templates.preview": "Podgląd",
|
||||
"templates.rawHTML": "Surowy HTML"
|
||||
}
|
440
i18n/pt-BR.json
Normal file
440
i18n/pt-BR.json
Normal file
|
@ -0,0 +1,440 @@
|
|||
{
|
||||
"_.code": "pt-BR",
|
||||
"_.name": "Português Brasileiro (pt-BR)",
|
||||
"admin.errorMarshallingConfig": "Erro ao ler as configurações: {error}",
|
||||
"campaigns.addAltText": "Adicionar mensagem alternativa em texto simples",
|
||||
"campaigns.cantUpdate": "Não é possível atualizar uma campanha em execução ou finalizada.",
|
||||
"campaigns.clicks": "Cliques",
|
||||
"campaigns.confirmDelete": "Excluir {name}",
|
||||
"campaigns.confirmSchedule": "A campanha irá começar automaticamente na data e hora agendadas. Agendar agora?",
|
||||
"campaigns.confirmSwitchFormat": "O conteúdo pode perder a formatação. Continuar?",
|
||||
"campaigns.content": "Conteúdo",
|
||||
"campaigns.contentHelp": "Conteúdo aqui",
|
||||
"campaigns.continue": "Continuar",
|
||||
"campaigns.copyOf": "Cópia de {name}",
|
||||
"campaigns.dateAndTime": "Data e hora",
|
||||
"campaigns.ended": "Finalizada",
|
||||
"campaigns.errorSendTest": "Erro ao enviar o teste: {error}",
|
||||
"campaigns.fieldInvalidBody": "Erro ao compilar corpo da campanha: {error}",
|
||||
"campaigns.fieldInvalidFromEmail": "`from_email` inválido.",
|
||||
"campaigns.fieldInvalidListIDs": "Lista de IDs inválida.",
|
||||
"campaigns.fieldInvalidMessenger": "Mensageiro {name} desconhecido.",
|
||||
"campaigns.fieldInvalidName": "Quantidade de caracteres inválida para o nome.",
|
||||
"campaigns.fieldInvalidSendAt": "A data agendada deve ser no futuro.",
|
||||
"campaigns.fieldInvalidSubject": "Quantidade de caracteres inválida para o assunto.",
|
||||
"campaigns.fromAddress": "Endereço do remetente",
|
||||
"campaigns.fromAddressPlaceholder": "Seu Nome <noreply@yoursite.com>",
|
||||
"campaigns.invalid": "Campanha inválida",
|
||||
"campaigns.markdown": "Markdown",
|
||||
"campaigns.needsSendAt": "A campanha precisa de uma data para ser programada.",
|
||||
"campaigns.newCampaign": "Nova campanha",
|
||||
"campaigns.noKnownSubsToTest": "Nenhum assinante conhecido para testar.",
|
||||
"campaigns.noOptinLists": "Nenhuma lista opt-in encontrada para criar campanha.",
|
||||
"campaigns.noSubs": "Não há assinantes nas listas selecionadas para criar a campanha.",
|
||||
"campaigns.noSubsToTest": "Não há nenhum assinantes pra enviar.",
|
||||
"campaigns.notFound": "Campanha não encontrada.",
|
||||
"campaigns.onlyActiveCancel": "Apenas campanhas ativas podem ser canceladas.",
|
||||
"campaigns.onlyActivePause": "Apenas campanhas ativas podem ser pausadas.",
|
||||
"campaigns.onlyDraftAsScheduled": "Apenas campanhas em rascunho podem ser agendadas.",
|
||||
"campaigns.onlyPausedDraft": "Apenas campanhas pausadas e em rascunhos podem ser iniciadas.",
|
||||
"campaigns.onlyScheduledAsDraft": "Apenas campanhas agendadas podem ser salvas como rascunhos.",
|
||||
"campaigns.pause": "Pausar",
|
||||
"campaigns.plainText": "Texto simples",
|
||||
"campaigns.preview": "Pré-visualizar",
|
||||
"campaigns.progress": "Progresso",
|
||||
"campaigns.queryPlaceholder": "Nome ou assunto",
|
||||
"campaigns.rawHTML": "Código HTML",
|
||||
"campaigns.removeAltText": "Remover mensagem alternativa em texto simples",
|
||||
"campaigns.richText": "Texto com formatação",
|
||||
"campaigns.schedule": "Agendar campanha",
|
||||
"campaigns.scheduled": "Agendada",
|
||||
"campaigns.send": "Enviar",
|
||||
"campaigns.sendLater": "Enviar mais tarde",
|
||||
"campaigns.sendTest": "Enviar mensagem de teste",
|
||||
"campaigns.sendTestHelp": "Pressione a tecla enter depois de digitar um endereço para adicionar vários destinatários. Os endereços devem pertencer a membros existentes.",
|
||||
"campaigns.sendToLists": "Listas para enviar para",
|
||||
"campaigns.sent": "Enviada",
|
||||
"campaigns.start": "Iniciar campanha",
|
||||
"campaigns.started": "Campanha \"{name}\" iniciada",
|
||||
"campaigns.startedAt": "Iniciada",
|
||||
"campaigns.stats": "Estatísticas",
|
||||
"campaigns.status.cancelled": "Cancelada",
|
||||
"campaigns.status.draft": "Rascunho",
|
||||
"campaigns.status.finished": "Finalizada",
|
||||
"campaigns.status.paused": "Pausada",
|
||||
"campaigns.status.running": "Executando",
|
||||
"campaigns.status.scheduled": "Agendado",
|
||||
"campaigns.statusChanged": "O status da campanha \"{name}\" é {status}",
|
||||
"campaigns.subject": "Assunto",
|
||||
"campaigns.testEmails": "E-mails",
|
||||
"campaigns.testSent": "Mensagem de teste enviada",
|
||||
"campaigns.timestamps": "Data e hora",
|
||||
"campaigns.views": "Visualizações",
|
||||
"dashboard.campaignViews": "Visualizações da campanha",
|
||||
"dashboard.linkClicks": "Links clicados",
|
||||
"dashboard.messagesSent": "Mensagens enviadas",
|
||||
"dashboard.orphanSubs": "Órfãos",
|
||||
"email.data.info": "Uma cópia de todos os dados associados a você está anexado em um arquivo JSON. Ele pode ser ler o conteúdo em um editor de texto.",
|
||||
"email.data.title": "Seus dados",
|
||||
"email.optin.confirmSub": "Confirmar a assinatura",
|
||||
"email.optin.confirmSubHelp": "Confirme sua assinatura clicando no botão abaixo.",
|
||||
"email.optin.confirmSubInfo": "Você foi adicionado às seguintes listas:",
|
||||
"email.optin.confirmSubTitle": "Confirmar a assinatura",
|
||||
"email.optin.confirmSubWelcome": "Olá",
|
||||
"email.optin.privateList": "Lista privada",
|
||||
"email.status.campaignReason": "Motivo",
|
||||
"email.status.campaignSent": "Enviada",
|
||||
"email.status.campaignUpdateTitle": "Atualizar a campanha",
|
||||
"email.status.importFile": "Arquivo",
|
||||
"email.status.importRecords": "Registros",
|
||||
"email.status.importTitle": "Importar atualização",
|
||||
"email.status.status": "Status",
|
||||
"email.unsub": "Cancelar assinatura",
|
||||
"email.unsubHelp": "Não quer mais receber estes e-mails?",
|
||||
"forms.formHTML": "Formulário HTML",
|
||||
"forms.formHTMLHelp": "Use este HTML para inserir um formulário de inscrição em uma página externa. O formulário deve ter o campo de e-mail e um ou mais campos `l` (lista UUID). O campo nome é opcional.",
|
||||
"forms.noPublicLists": "Não há nenhuma lista pública para gerar um formulário.",
|
||||
"forms.publicLists": "Listas públicas",
|
||||
"forms.publicSubPage": "Página pública de assinatura",
|
||||
"forms.selectHelp": "Selecione listas para adicionar ao formulário.",
|
||||
"forms.title": "Formulários",
|
||||
"globals.buttons.add": "Adicionar",
|
||||
"globals.buttons.addNew": "Adicionar novo",
|
||||
"globals.buttons.cancel": "Cancelar",
|
||||
"globals.buttons.clone": "Clonar",
|
||||
"globals.buttons.close": "Fechar",
|
||||
"globals.buttons.continue": "Continuar",
|
||||
"globals.buttons.delete": "Excluir",
|
||||
"globals.buttons.edit": "Editar",
|
||||
"globals.buttons.enabled": "Habilitado",
|
||||
"globals.buttons.learnMore": "Saiba mais",
|
||||
"globals.buttons.new": "Novo",
|
||||
"globals.buttons.ok": "Ok",
|
||||
"globals.buttons.remove": "Excluir",
|
||||
"globals.buttons.save": "Salvar",
|
||||
"globals.buttons.saveChanges": "Salvar alterações",
|
||||
"globals.days.0": "Dom",
|
||||
"globals.days.1": "Seg",
|
||||
"globals.days.2": "Ter",
|
||||
"globals.days.3": "Qua",
|
||||
"globals.days.4": "Qui",
|
||||
"globals.days.5": "Sex",
|
||||
"globals.days.6": "Sáb",
|
||||
"globals.fields.createdAt": "Criado",
|
||||
"globals.fields.id": "ID",
|
||||
"globals.fields.name": "Nome",
|
||||
"globals.fields.status": "Status",
|
||||
"globals.fields.type": "Tipo",
|
||||
"globals.fields.updatedAt": "Atualizado",
|
||||
"globals.fields.uuid": "UUID",
|
||||
"globals.messages.confirm": "Tem certeza?",
|
||||
"globals.messages.created": "\"{name}\" criado",
|
||||
"globals.messages.deleted": "\"{name}\" excluído",
|
||||
"globals.messages.emptyState": "Nada por aqui",
|
||||
"globals.messages.errorCreating": "Erro ao criar {name}: {error}",
|
||||
"globals.messages.errorDeleting": "Erro ao excluir {name}: {error}",
|
||||
"globals.messages.errorFetching": "Erro ao obter {name}: {error}",
|
||||
"globals.messages.errorUUID": "Erro ao gerar UUID: {error}",
|
||||
"globals.messages.errorUpdating": "Erro ao atualizar {name}: {error}",
|
||||
"globals.messages.invalidID": "ID inválido",
|
||||
"globals.messages.invalidUUID": "UUID inválido",
|
||||
"globals.messages.notFound": "{name} não encontrado",
|
||||
"globals.messages.passwordChange": "Digite um valor para alterar",
|
||||
"globals.messages.updated": "\"{name}\"atualizado",
|
||||
"globals.months.1": "Jan",
|
||||
"globals.months.10": "Out",
|
||||
"globals.months.11": "Nov",
|
||||
"globals.months.12": "Dez",
|
||||
"globals.months.2": "Fev",
|
||||
"globals.months.3": "Mar",
|
||||
"globals.months.4": "Abr",
|
||||
"globals.months.5": "Mai",
|
||||
"globals.months.6": "Jun",
|
||||
"globals.months.7": "Jul",
|
||||
"globals.months.8": "Ago",
|
||||
"globals.months.9": "Set",
|
||||
"globals.terms.campaign": "Campanha | Campanhas",
|
||||
"globals.terms.campaigns": "Campanhas",
|
||||
"globals.terms.dashboard": "Painel",
|
||||
"globals.terms.list": "Lista | Listas",
|
||||
"globals.terms.lists": "Listas",
|
||||
"globals.terms.media": "Mídia | Mídias",
|
||||
"globals.terms.messenger": "Mensageiro | Mensageiros",
|
||||
"globals.terms.messengers": "Mensageiros",
|
||||
"globals.terms.settings": "Configurações",
|
||||
"globals.terms.subscriber": "Assinante | Assinantes",
|
||||
"globals.terms.subscribers": "Assinantes",
|
||||
"globals.terms.tag": "Tag | Tags",
|
||||
"globals.terms.tags": "Tags",
|
||||
"globals.terms.template": "Modelo | Modelos",
|
||||
"globals.terms.templates": "Modelos",
|
||||
"import.alreadyRunning": "Uma importação já está em execução. Aguarde até que termine ou pare-a antes de tentar novamente.",
|
||||
"import.blocklist": "Lista de bloqueio",
|
||||
"import.csvDelim": "Delimitador CSV",
|
||||
"import.csvDelimHelp": "Delimitador padrão é vírgula.",
|
||||
"import.csvExample": "Exemplo de CSV bruto",
|
||||
"import.csvFile": "Arquivo CSV ou ZIP",
|
||||
"import.csvFileHelp": "Clique ou arraste um arquivo CSV ou ZIP aqui",
|
||||
"import.errorCopyingFile": "Erro ao copiar arquivo: {error}",
|
||||
"import.errorProcessingZIP": "Erro ao processar o arquivo ZIP: {error}",
|
||||
"import.errorStarting": "Erro ao iniciar importação: {error}",
|
||||
"import.importDone": "Finalizada",
|
||||
"import.importStarted": "Importação iniciada",
|
||||
"import.instructions": "Instruções",
|
||||
"import.instructionsHelp": "Envie um arquivo CSV ou um arquivo ZIP contendo um único arquivo CSV para a importação de assinantes lote. O arquivo CSV deve ter os seguintes cabeçalhos com os nomes exatos das colunas. Os atributos (opcional) devem ser uma string JSON válida com aspas duplas.",
|
||||
"import.invalidDelim": "O delimitador deve ser um único caractere.",
|
||||
"import.invalidFile": "Arquivo inválido: {error}",
|
||||
"import.invalidMode": "Modo inválido",
|
||||
"import.invalidParams": "Parâmetros inválidos: {error}",
|
||||
"import.listSubHelp": "Listas para inscrever.",
|
||||
"import.mode": "Modo",
|
||||
"import.overwrite": "Sobrescrever?",
|
||||
"import.overwriteHelp": "Sobrescrever nome e atributos de inscritos existentes?",
|
||||
"import.recordsCount": "{num} / {total} registros",
|
||||
"import.stopImport": "Parar importação",
|
||||
"import.subscribe": "Inscrever",
|
||||
"import.title": "Importar inscritos",
|
||||
"import.upload": "Enviar arquivo",
|
||||
"lists.confirmDelete": "Você tem certeza? Isso não exclui inscritos.",
|
||||
"lists.confirmSub": "Confirmar assinatura(s) para {name}",
|
||||
"lists.invalidName": "Nome inválido",
|
||||
"lists.newList": "Nova lista",
|
||||
"lists.optin": "Confirmação da inscrição",
|
||||
"lists.optinHelp": "A inscrição com confirmação envia um e-mail para o inscrito pedindo que ele confirme a inscrição. Nas listas com inscrição com confirmação, as campanhas são enviadas apenas para inscritos que confirmaram a inscrição.",
|
||||
"lists.optinTo": "Inscrição com confirmação para {name}",
|
||||
"lists.optins.double": "Inscrição com confirmação",
|
||||
"lists.optins.single": "Inscrição simples",
|
||||
"lists.sendCampaign": "Enviar campanha",
|
||||
"lists.sendOptinCampaign": "Enviada campanha de confirmação de inscrição",
|
||||
"lists.type": "Tipo",
|
||||
"lists.typeHelp": "Listas públicas estão abertas ao mundo para se inscrever e seus nomes podem aparecer em páginas públicas, como na página de gerenciamento de inscrições.",
|
||||
"lists.types.private": "Privada",
|
||||
"lists.types.public": "Pública",
|
||||
"logs.title": "Logs",
|
||||
"media.errorReadingFile": "Erro ao ler arquivo: {error}",
|
||||
"media.errorResizing": "Erro ao redimensionar imagem: {error}",
|
||||
"media.errorSavingThumbnail": "Erro ao salvar miniatura: {error}",
|
||||
"media.errorUploading": "Erro ao enviar o arquivo: {error}",
|
||||
"media.invalidFile": "Arquivo inválido: {error}",
|
||||
"media.title": "Mídia",
|
||||
"media.unsupportedFileType": "Tipo de arquivo não suportado ({type})",
|
||||
"media.upload": "Enviar arquivo",
|
||||
"media.uploadHelp": "Clique ou arraste uma ou mais imagens aqui",
|
||||
"media.uploadImage": "Enviar Imagem",
|
||||
"menu.allCampaigns": "Todas as campanhas",
|
||||
"menu.allLists": "Todas as listas",
|
||||
"menu.allSubscribers": "Todos os inscritos",
|
||||
"menu.dashboard": "Painel",
|
||||
"menu.forms": "Formulários",
|
||||
"menu.import": "Importação",
|
||||
"menu.logs": "Logs",
|
||||
"menu.media": "Mídia",
|
||||
"menu.newCampaign": "Criar nova",
|
||||
"menu.settings": "Configurações",
|
||||
"public.campaignNotFound": "A mensagem do e-mail não foi encontrada.",
|
||||
"public.confirmOptinSubTitle": "Confirmar a assinatura",
|
||||
"public.confirmSub": "Confirmar a assinatura",
|
||||
"public.confirmSubInfo": "Você foi adicionado às seguintes listas:",
|
||||
"public.confirmSubTitle": "Confirmar",
|
||||
"public.dataRemoved": "Suas assinaturas e todos os dados associados foram removidos.",
|
||||
"public.dataRemovedTitle": "Dados removidos",
|
||||
"public.dataSent": "Seus dados foram enviados em anexo para seu e-mail.",
|
||||
"public.dataSentTitle": "Dados enviados para seu e-mail",
|
||||
"public.errorFetchingCampaign": "Erro ao obter a mensagem do e-mail.",
|
||||
"public.errorFetchingEmail": "Mensagem do e-mail não encontrada",
|
||||
"public.errorFetchingLists": "Erro ao obter as listas. Por favor, tente novamente.",
|
||||
"public.errorProcessingRequest": "Erro ao processar a solicitação. Por favor, tente novamente.",
|
||||
"public.errorTitle": "Erro",
|
||||
"public.invalidFeature": "Este recurso não está disponível.",
|
||||
"public.invalidLink": "Link inválido",
|
||||
"public.noListsAvailable": "Não há listas disponíveis para se inscrever.",
|
||||
"public.noListsSelected": "Não foram selecionadas listas válidas para inscrever.",
|
||||
"public.noSubInfo": "Não há nenhuma inscrição para confirmar.",
|
||||
"public.noSubTitle": "Sem inscrições",
|
||||
"public.notFoundTitle": "Não Encontrado",
|
||||
"public.privacyConfirmWipe": "Você tem certeza que deseja excluir todos os seus dados de assinatura permanentemente?",
|
||||
"public.privacyExport": "Exportar seus dados",
|
||||
"public.privacyExportHelp": "Uma cópia de seus dados será enviado por e-mail para você.",
|
||||
"public.privacyTitle": "Privacidade e dados",
|
||||
"public.privacyWipe": "Limpe seus dados",
|
||||
"public.privacyWipeHelp": "Excluir todas as suas assinaturas e dados relacionados do banco de dados permanentemente.",
|
||||
"public.sub": "Inscrever-se",
|
||||
"public.subConfirmed": "Inscrito com sucesso.",
|
||||
"public.subConfirmedTitle": "Confirmado",
|
||||
"public.subName": "Nome (opcional)",
|
||||
"public.subNotFound": "Inscrição não encontrada.",
|
||||
"public.subOptinPending": "Um e-mail foi enviado a você para confirmar sua(s) inscrição(ões).",
|
||||
"public.subPrivateList": "Lista privada",
|
||||
"public.subTitle": "Cancelar a inscrição",
|
||||
"public.unsub": "Cancelar a inscrição",
|
||||
"public.unsubFull": "Também cancelar a inscrição de todos os e-mails futuros.",
|
||||
"public.unsubHelp": "Deseja cancelar a inscrição desta lista de e-mail?",
|
||||
"public.unsubTitle": "Cancelar inscrição",
|
||||
"public.unsubbedInfo": "Você cancelou a inscrição com sucesso.",
|
||||
"public.unsubbedTitle": "Inscrição cancelada",
|
||||
"public.unsubscribeTitle": "Cancelar inscrição na lista de e-mails",
|
||||
"settings.confirmRestart": "Certifique-se de que as campanhas em execução estão pausadas. Reiniciar?",
|
||||
"settings.duplicateMessengerName": "Nome duplicado do mensageiro: {name}",
|
||||
"settings.errorEncoding": "Erro ao codificar as configurações: {error}",
|
||||
"settings.errorNoSMTP": "Pelo menos um bloco SMTP deve estar habilitado",
|
||||
"settings.general.adminNotifEmails": "E-mails de notificação de administrador",
|
||||
"settings.general.adminNotifEmailsHelp": "Lista de e-mails separados por vírgula para os quais as notificações de administração, como atualizações de importação, conclusão da campanha, falha, etc. devem ser enviadas.",
|
||||
"settings.general.checkUpdates": "Check for updates",
|
||||
"settings.general.checkUpdatesHelp": "Periodically check for new app releases and notify.",
|
||||
"settings.general.enablePublicSubPage": "Habilitar a página pública de inscrição",
|
||||
"settings.general.enablePublicSubPageHelp": "Habilitar a página pública de inscrição com todas as listas públicas para as pessoas se inscreverem.",
|
||||
"settings.general.faviconURL": "URL do Favicon",
|
||||
"settings.general.faviconURLHelp": "(Opcional) URL completo do favicon estático para ser visualizado pelo usuário, como a página de cancelamento de inscrição.",
|
||||
"settings.general.fromEmail": "E-mail `de` padrão",
|
||||
"settings.general.fromEmailHelp": "E-mail `de` padrão é usada nas mensagens de e-mails enviadas. Isso pode ser alterado por campanha.",
|
||||
"settings.general.language": "Idioma",
|
||||
"settings.general.logoURL": "URL do logotipo",
|
||||
"settings.general.logoURLHelp": "(Opcional) URL completo do logotipo estático para ser visualizado pelo usuário, como a página de cancelamento de inscrição.",
|
||||
"settings.general.name": "Geral",
|
||||
"settings.general.rootURL": "URL base",
|
||||
"settings.general.rootURLHelp": "URL público da instalação (sem barra final).",
|
||||
"settings.invalidMessengerName": "Nome de mensageiro inválido.",
|
||||
"settings.media.provider": "Provedor",
|
||||
"settings.media.s3.bucket": "Bucket",
|
||||
"settings.media.s3.bucketPath": "Caminho do bucket",
|
||||
"settings.media.s3.bucketPathHelp": "Caminho dentro do bucket para enviar os arquivos. O padrão é /",
|
||||
"settings.media.s3.bucketType": "Tipo de bucket",
|
||||
"settings.media.s3.bucketTypePrivate": "Privado",
|
||||
"settings.media.s3.bucketTypePublic": "Público",
|
||||
"settings.media.s3.key": "Chave de acesso AWS",
|
||||
"settings.media.s3.region": "Região",
|
||||
"settings.media.s3.secret": "Segredo de acesso AWS",
|
||||
"settings.media.s3.uploadExpiry": "Expiração do arquivo enviado",
|
||||
"settings.media.s3.uploadExpiryHelp": "(Opcional) Especificar TTL (em segundos) para a URL pré-assinada gerada. Apenas aplicável para buckets privados (s, m, h, d para segundos, minutos, horas e dias).",
|
||||
"settings.media.title": "Envios de mídias",
|
||||
"settings.media.upload.path": "Caminho de envio",
|
||||
"settings.media.upload.pathHelp": "Caminho para o diretório onde a mídia será enviado.",
|
||||
"settings.media.upload.uri": "URI de envio",
|
||||
"settings.media.upload.uriHelp": "URI de envio que é visível ao mundo exterior. Todas as mídias enviadas para o upload_path será publicamente acessível em {root_url}, por exemplo, https://listmonk.exemplo.com.br/uploads.",
|
||||
"settings.messengers.maxConns": "Máx. conexões",
|
||||
"settings.messengers.maxConnsHelp": "Máximo de conexões simultâneas para o servidor.",
|
||||
"settings.messengers.messageDiscard": "Descartar alterações?",
|
||||
"settings.messengers.messageSaved": "Configurações salvas. Recarregando o aplicativo...",
|
||||
"settings.messengers.name": "Mensageiros",
|
||||
"settings.messengers.nameHelp": "ex: meu-sms. Alfanuméricos / traço.",
|
||||
"settings.messengers.password": "Senha",
|
||||
"settings.messengers.retries": "Tentativas",
|
||||
"settings.messengers.retriesHelp": "Número de tentativas quando uma mensagem falhar.",
|
||||
"settings.messengers.skipTLSHelp": "Pular verificação de hostname sobre o certificado TLS.",
|
||||
"settings.messengers.timeout": "Tempo de espera limite",
|
||||
"settings.messengers.timeoutHelp": "Tempo para esperar por uma nova atividade em uma conexão antes de fechá-la e removê-la do pool (s parar segundo, m para minuto).",
|
||||
"settings.messengers.url": "URL",
|
||||
"settings.messengers.urlHelp": "URL base do servidor Postback.",
|
||||
"settings.messengers.username": "Usuário",
|
||||
"settings.needsRestart": "Configurações alteradas. Pause todas as campanhas em execução e reiniciar o aplicativo",
|
||||
"settings.performance.batchSize": "Tamanho do lote",
|
||||
"settings.performance.batchSizeHelp": "O número de inscritos para puxar do banco de dados em uma única iteração. Cada iteração puxa assinantes da base de dados, envia mensagens para eles, e então passa para a próxima iteração para puxar o próximo lote. O ideal é que isso seja mais alto do que o máximo possível de transferência (concorrência * taxa de mensagem).",
|
||||
"settings.performance.concurrency": "Concorrência",
|
||||
"settings.performance.concurrencyHelp": "Máximo de trabalhador simultâneo (threads) que tentará enviar mensagens simultaneamente.",
|
||||
"settings.performance.maxErrThreshold": "Limite máximo de erros",
|
||||
"settings.performance.maxErrThresholdHelp": "O número de erros (por exemplo: tempo limite SMTP ao enviar e-mail) uma campanha em curso deve tolerar antes de ser pausada para investigação manual ou intervenção. Marque 0 para nunca pausar.",
|
||||
"settings.performance.messageRate": "Taxa de mensagens",
|
||||
"settings.performance.messageRateHelp": "Número máximo de mensagens a serem enviadas por segundo por trabalhador em um segundo. Se a concorrência = 10 e taxa de mensagem = 10, então até 10x10=100 mensagens podem ser enviadas a cada segundo. Isto, juntamente com a concorrência, deve ser ajustado para manter as mensagens saindo da rede por segundo abaixo dos limites de taxa dos servidores de mensagens de destino, se houver.",
|
||||
"settings.performance.name": "Performance",
|
||||
"settings.performance.slidingWindow": "Habilitar limite da janela deslizante",
|
||||
"settings.performance.slidingWindowDuration": "Duração",
|
||||
"settings.performance.slidingWindowDurationHelp": "Duração do período da janela deslizante (m para minuto, h para hora).",
|
||||
"settings.performance.slidingWindowHelp": "Limitar o número total de mensagens enviadas em determinado período. Ao atingir este limite, as mensagens são impedidas de ser enviadas até ao fim da janela temporária.",
|
||||
"settings.performance.slidingWindowRate": "Max. mensagens",
|
||||
"settings.performance.slidingWindowRateHelp": "Número máximo de mensagens a serem enviadas dentro da duração da janela.",
|
||||
"settings.privacy.allowBlocklist": "Permitir lista de bloqueio",
|
||||
"settings.privacy.allowBlocklistHelp": "Permitir que os inscritos cancelem a inscrição de todas as listas de e-mails e se marquem como bloqueados?",
|
||||
"settings.privacy.allowExport": "Permitir exportação",
|
||||
"settings.privacy.allowExportHelp": "Permitir que os assinantes exportem os dados coletados neles?",
|
||||
"settings.privacy.allowWipe": "Permitir limpeza",
|
||||
"settings.privacy.allowWipeHelp": "Permitir que os assinantes se excluam incluindo suas inscrições e todos os outros dados da base de dados. Visualizações da campanha e cliques de links também são removidos enquanto o total de visualizações e cliques permanecem (com nenhum inscrito associado a eles) para que as estatísticas e análises não sejam afetadas.",
|
||||
"settings.privacy.individualSubTracking": "Rastreamento individual de inscrito",
|
||||
"settings.privacy.individualSubTrackingHelp": "Rastrear visualizações e cliques de cada inscrito. Quando desativado, o rastreio da visualizações e clique continuar sem estar associado a nenhuma inscrição.",
|
||||
"settings.privacy.listUnsubHeader": "Incluir cabeçalho `List-Unsubscribe`",
|
||||
"settings.privacy.listUnsubHeaderHelp": "Incluir cabeçalhos de desinscrição que permitem aos clientes de e-mail cancelem a inscrição em um único clique.",
|
||||
"settings.privacy.name": "Privacidade",
|
||||
"settings.restart": "Reiniciar",
|
||||
"settings.smtp.authProtocol": "Protocolo Autenticação",
|
||||
"settings.smtp.customHeaders": "Cabeçalhos personalizados",
|
||||
"settings.smtp.customHeadersHelp": "Array opcional de cabeçalhos de e-mail para incluir em todas as mensagens enviadas a partir deste servidor. por exemplo: [{\"X-Custom\": \"value\"}, {\"X-Custom2\": \"value\"}]",
|
||||
"settings.smtp.enabled": "Habilitado",
|
||||
"settings.smtp.heloHost": "Nome do host HELO",
|
||||
"settings.smtp.heloHostHelp": "Opcional. Alguns servidores SMTP exigem um FQDN no nome do host. Por padrão, os HELLOs vão com 'localhost'. Defina isto se um nome de host personalizado deve ser usado.",
|
||||
"settings.smtp.host": "Host",
|
||||
"settings.smtp.hostHelp": "Endereço do servidor SMTP.",
|
||||
"settings.smtp.idleTimeout": "Tempo limite ocioso",
|
||||
"settings.smtp.idleTimeoutHelp": "Tempo para esperar por uma nova atividade em uma conexão antes de fechá-la e removê-la do pool (s parar segundo, m para minuto).",
|
||||
"settings.smtp.maxConns": "Máx. Conexões",
|
||||
"settings.smtp.maxConnsHelp": "Número máximo de conexões simultâneas ao servidor SMTP.",
|
||||
"settings.smtp.name": "SMTP",
|
||||
"settings.smtp.password": "Senha",
|
||||
"settings.smtp.passwordHelp": "Digite para alterar",
|
||||
"settings.smtp.port": "Porta",
|
||||
"settings.smtp.portHelp": "Porta do servidor SMTP.",
|
||||
"settings.smtp.retries": "Tentativas",
|
||||
"settings.smtp.retriesHelp": "Número de tentativas quando uma mensagem falhar.",
|
||||
"settings.smtp.setCustomHeaders": "Definir cabeçalhos personalizados",
|
||||
"settings.smtp.skipTLS": "Pular verificação de TLS",
|
||||
"settings.smtp.skipTLSHelp": "Pular verificação de hostname sobre o certificado TLS.",
|
||||
"settings.smtp.tls": "TLS",
|
||||
"settings.smtp.tlsHelp": "Habilitar STARTTLS.",
|
||||
"settings.smtp.username": "Usuário",
|
||||
"settings.smtp.waitTimeout": "Tempo limite de espera",
|
||||
"settings.smtp.waitTimeoutHelp": "Tempo para esperar por uma nova atividade em uma conexão antes de fechá-la e removê-la do pool (s parar segundo, m para minuto).",
|
||||
"settings.title": "Configurações",
|
||||
"settings.updateAvailable": "Atualização: a nova versão {version} já está disponível.",
|
||||
"subscribers.advancedQuery": "Avançado",
|
||||
"subscribers.advancedQueryHelp": "Expressão de SQL parcial para consultar atributos dos inscritos",
|
||||
"subscribers.attribs": "Atributos",
|
||||
"subscribers.attribsHelp": "Atributos são definidos como um mapa JSON, por exemplo:",
|
||||
"subscribers.blocklistedHelp": "Inscritos bloqueados nunca receberão quaisquer e-mails.",
|
||||
"subscribers.confirmBlocklist": "Bloquear {num} inscrito(s)?",
|
||||
"subscribers.confirmDelete": "Excluir {num} inscrito(s)?",
|
||||
"subscribers.confirmExport": "Exportar {num} inscrito(s)?",
|
||||
"subscribers.downloadData": "Baixar dados",
|
||||
"subscribers.email": "E-mail",
|
||||
"subscribers.emailExists": "E-mail já existe.",
|
||||
"subscribers.errorBlocklisting": "Erro ao bloquear inscritos: {error}",
|
||||
"subscribers.errorInvalidIDs": "Um ou mais IDs inválidos: {error}",
|
||||
"subscribers.errorNoIDs": "Nenhum ID informado.",
|
||||
"subscribers.errorNoListsGiven": "Nenhuma lista informada.",
|
||||
"subscribers.errorPreparingQuery": "Erro ao preparar consulta de inscritos: {error}",
|
||||
"subscribers.errorSendingOptin": "Erro ao enviar e-mail de confirmação de inscrição.",
|
||||
"subscribers.export": "Exportar",
|
||||
"subscribers.invalidAction": "Ação inválida.",
|
||||
"subscribers.invalidEmail": "E-mail inválido.",
|
||||
"subscribers.invalidJSON": "JSON inválido nos atributos.",
|
||||
"subscribers.invalidName": "Nome inválido.",
|
||||
"subscribers.listChangeApplied": "Alterações na lista aplicadas.",
|
||||
"subscribers.lists": "Listas",
|
||||
"subscribers.listsHelp": "Listas das quais os inscritos cancelaram a inscrição por eles mesmos não podem ser removidos.",
|
||||
"subscribers.listsPlaceholder": "Listas para inscrever",
|
||||
"subscribers.manageLists": "Gerenciar listas",
|
||||
"subscribers.markUnsubscribed": "Marcar como inscrição cancelada",
|
||||
"subscribers.newSubscriber": "Novo inscrito",
|
||||
"subscribers.numSelected": "{num} inscrito(s) selecionado(s)",
|
||||
"subscribers.optinSubject": "Confirmar a inscrição",
|
||||
"subscribers.query": "Consulta",
|
||||
"subscribers.queryPlaceholder": "E-mail ou nome",
|
||||
"subscribers.reset": "Redefinir",
|
||||
"subscribers.selectAll": "Selecionar todos {num}",
|
||||
"subscribers.status.blocklisted": "Lista de bloqueados",
|
||||
"subscribers.status.confirmed": "Confirmado",
|
||||
"subscribers.status.enabled": "Habilitado",
|
||||
"subscribers.status.subscribed": "Inscrito",
|
||||
"subscribers.status.unconfirmed": "Não confirmado",
|
||||
"subscribers.status.unsubscribed": "Inscrição cancelada",
|
||||
"subscribers.subscribersDeleted": "{num} inscrito(s) excluído(s)",
|
||||
"templates.cantDeleteDefault": "Não é possível excluir o modelo padrão",
|
||||
"templates.default": "Padrão",
|
||||
"templates.dummyName": "Campanha fictícia",
|
||||
"templates.dummySubject": "Assunto da campanha fictícia",
|
||||
"templates.errorCompiling": "Erro ao compilar modelo: {error}",
|
||||
"templates.errorRendering": "Erro ao renderizar mensagem: {error}",
|
||||
"templates.fieldInvalidName": "Comprimento inválido para o nome.",
|
||||
"templates.makeDefault": "Definir como padrão",
|
||||
"templates.newTemplate": "Novo modelo",
|
||||
"templates.placeholderHelp": "O palavra reservada {placeholder} deve aparecer exatamente uma vez no modelo.",
|
||||
"templates.preview": "Pré-visualizar",
|
||||
"templates.rawHTML": "Código HTML"
|
||||
}
|
11
i18n/pt.json
11
i18n/pt.json
|
@ -25,6 +25,7 @@
|
|||
"campaigns.fromAddress": "Endereço do Remetente",
|
||||
"campaigns.fromAddressPlaceholder": "O Teu Nome <noreply@oteusite.com>",
|
||||
"campaigns.invalid": "Campanha inválida",
|
||||
"campaigns.markdown": "Markdown",
|
||||
"campaigns.needsSendAt": "A campanha necessita de uma data para ser agendada.",
|
||||
"campaigns.newCampaign": "Nova campanha",
|
||||
"campaigns.noKnownSubsToTest": "Não existem subscritores para testar.",
|
||||
|
@ -92,6 +93,7 @@
|
|||
"email.unsubHelp": "Não quer receber estes e-mails?",
|
||||
"forms.formHTML": "Formulário HTML",
|
||||
"forms.formHTMLHelp": "Usa o seguinte código HTML para mostrar um formulário de subscrição numa página externa. O formulário deve ter um campo de email e um ou mais campos `l` (UUID de listas). O campo de nome é opcional.",
|
||||
"forms.noPublicLists": "There are no public lists to generate a forms.",
|
||||
"forms.publicLists": "Listas públicas",
|
||||
"forms.publicSubPage": "Página pública de subscrição",
|
||||
"forms.selectHelp": "Seleciona listas para adicionar ao formulário.",
|
||||
|
@ -111,13 +113,13 @@
|
|||
"globals.buttons.remove": "Remover",
|
||||
"globals.buttons.save": "Guardar",
|
||||
"globals.buttons.saveChanges": "Guardar alterações",
|
||||
"globals.days.0": "Dom",
|
||||
"globals.days.1": "Seg",
|
||||
"globals.days.2": "Ter",
|
||||
"globals.days.3": "Qua",
|
||||
"globals.days.4": "Qui",
|
||||
"globals.days.5": "Sex",
|
||||
"globals.days.6": "Sáb",
|
||||
"globals.days.7": "Dom",
|
||||
"globals.fields.createdAt": "Criado a",
|
||||
"globals.fields.id": "ID",
|
||||
"globals.fields.name": "Nome",
|
||||
|
@ -261,6 +263,7 @@
|
|||
"public.subConfirmedTitle": "Confirmado",
|
||||
"public.subName": "Nome (opcional)",
|
||||
"public.subNotFound": "Subscrição não encontrada.",
|
||||
"public.subOptinPending": "An e-mail has been sent to you to confirm your subscription(s).",
|
||||
"public.subPrivateList": "Lista privada",
|
||||
"public.subTitle": "Subscrever",
|
||||
"public.unsub": "Cancelar subscrição",
|
||||
|
@ -270,11 +273,14 @@
|
|||
"public.unsubbedInfo": "A sua subscrição foi cancelada com sucesso.",
|
||||
"public.unsubbedTitle": "Subscrição cancelada",
|
||||
"public.unsubscribeTitle": "Cancelar subscrição da lista de emails",
|
||||
"settings.confirmRestart": "Ensure running campaigns are paused. Restart?",
|
||||
"settings.duplicateMessengerName": "Nome duplicado do mensageiro: {name}",
|
||||
"settings.errorEncoding": "Erro de definições de codificação: {error}",
|
||||
"settings.errorNoSMTP": "Pelo menos um bloco SMTP deve estar ativo",
|
||||
"settings.general.adminNotifEmails": "Emails de notificação de administração",
|
||||
"settings.general.adminNotifEmailsHelp": "Lista separada por vírgulas dos endereços de email para os quais devem ser enviadas notificações de administração como updates importantes, conclusão de campanhas, falhas, etc.",
|
||||
"settings.general.checkUpdates": "Check for updates",
|
||||
"settings.general.checkUpdatesHelp": "Periodically check for new app releases and notify.",
|
||||
"settings.general.enablePublicSubPage": "Ativar página de subscrição pública",
|
||||
"settings.general.enablePublicSubPageHelp": "Mostrar uma página de subscrição pública com todas as listas públicas para as pessoas se subscreverem.",
|
||||
"settings.general.faviconURL": "URL do Favicon",
|
||||
|
@ -320,6 +326,7 @@
|
|||
"settings.messengers.url": "URL",
|
||||
"settings.messengers.urlHelp": "URL base do servidor Postback.",
|
||||
"settings.messengers.username": "Nome de utilizador",
|
||||
"settings.needsRestart": "Settings changed. Pause all running campaigns and restart the app",
|
||||
"settings.performance.batchSize": "Tamanho do lote",
|
||||
"settings.performance.batchSizeHelp": "O número de subscritores para ir buscar à base de dados numa só iteração. Cada iteração vai buscar subscritores à base de dados, envia-lhe mensagens, e depois segue para a nova iteração para ir buscar o lote seguinte. Isto deve idealmente ser maior do que a máxima taxa de transferência alcançável (simultaneidade * taxa de mensagens).",
|
||||
"settings.performance.concurrency": "Simultaneidade",
|
||||
|
@ -346,6 +353,7 @@
|
|||
"settings.privacy.listUnsubHeader": "Incluir header `List-Unsubscribe`",
|
||||
"settings.privacy.listUnsubHeaderHelp": "Incluir headers de cancelamento de subscrição que permite aos clientes de email permitir ao utilizadores cancelar a subscrição num único clique.",
|
||||
"settings.privacy.name": "Privacidade",
|
||||
"settings.restart": "Restart",
|
||||
"settings.smtp.authProtocol": "Protocolo Autenticação",
|
||||
"settings.smtp.customHeaders": "Headers customizados",
|
||||
"settings.smtp.customHeadersHelp": "Array opcional de headers de email a incluir em todas as mensagens enviadas deste servidor. eg: [{\"X-Custom\": \"value\"}, {\"X-Custom2\": \"value\"}]",
|
||||
|
@ -374,6 +382,7 @@
|
|||
"settings.smtp.waitTimeout": "Tempo limite de espera",
|
||||
"settings.smtp.waitTimeoutHelp": "Tempo a esperar por nova atividade numa conexão antes de a fechar e removê-la da pool (s para segundo, m para minuto).",
|
||||
"settings.title": "Definições",
|
||||
"settings.updateAvailable": "A new update {version} is available.",
|
||||
"subscribers.advancedQuery": "Avançado",
|
||||
"subscribers.advancedQueryHelp": "Expressão SQL parcial para consultar atributos de subscritores",
|
||||
"subscribers.attribs": "Atributos",
|
||||
|
|
440
i18n/ru.json
Normal file
440
i18n/ru.json
Normal file
|
@ -0,0 +1,440 @@
|
|||
{
|
||||
"_.code": "ru",
|
||||
"_.name": "Русский (ru)",
|
||||
"admin.errorMarshallingConfig": "Ошибка преобразования конфига: {error}",
|
||||
"campaigns.addAltText": "Добавить альтернативное простое текстовое сообщение",
|
||||
"campaigns.cantUpdate": "Не возможно обновить запущенную или завершённую компанию.",
|
||||
"campaigns.clicks": "Клики",
|
||||
"campaigns.confirmDelete": "Удалить {name}",
|
||||
"campaigns.confirmSchedule": "Эта компания будет автоматически запущена в запланированное время. Запланировать сейчас?",
|
||||
"campaigns.confirmSwitchFormat": "Содержимое может потерять форматирование. Продолжить?",
|
||||
"campaigns.content": "Содержимое",
|
||||
"campaigns.contentHelp": "Содержимое",
|
||||
"campaigns.continue": "Продолжить",
|
||||
"campaigns.copyOf": "Копия {name}",
|
||||
"campaigns.dateAndTime": "Дата и время",
|
||||
"campaigns.ended": "Окончено",
|
||||
"campaigns.errorSendTest": "Ошибка отправки теста: {error}",
|
||||
"campaigns.fieldInvalidBody": "Ошибка сборки тела компании: {error}",
|
||||
"campaigns.fieldInvalidFromEmail": "Неверный `from_email`.",
|
||||
"campaigns.fieldInvalidListIDs": "Неверные ID списков.",
|
||||
"campaigns.fieldInvalidMessenger": "Неизвестный мессенджер {name}.",
|
||||
"campaigns.fieldInvalidName": "Неверная длина имени.",
|
||||
"campaigns.fieldInvalidSendAt": "Запланированная дата должна быть позже текущей.",
|
||||
"campaigns.fieldInvalidSubject": "Неверная длина темы.",
|
||||
"campaigns.fromAddress": "Адрес отправителя",
|
||||
"campaigns.fromAddressPlaceholder": "Ваше имя <noreply@yoursite.com>",
|
||||
"campaigns.invalid": "Неверная компания",
|
||||
"campaigns.markdown": "Разметка",
|
||||
"campaigns.needsSendAt": "Для планирования компании необходима дата.",
|
||||
"campaigns.newCampaign": "Новая компания",
|
||||
"campaigns.noKnownSubsToTest": "Для теста нет известных подписчиков.",
|
||||
"campaigns.noOptinLists": "Не найдено списков с подтверждением подписки для создания кампании .",
|
||||
"campaigns.noSubs": "В выбранных списках нет подписчиков для создания кампании.",
|
||||
"campaigns.noSubsToTest": "Нед подписциков для цели.",
|
||||
"campaigns.notFound": "Компания не найдена.",
|
||||
"campaigns.onlyActiveCancel": "Только активные компании могут быть отменены.",
|
||||
"campaigns.onlyActivePause": "Только активные компании могут быть приостановлены.",
|
||||
"campaigns.onlyDraftAsScheduled": "Можно запланировать только черновики кампаний.",
|
||||
"campaigns.onlyPausedDraft": "Можно запускать только приостановленные кампании и черновики.",
|
||||
"campaigns.onlyScheduledAsDraft": "Только запланированные кампании можно сохранить как черновики.",
|
||||
"campaigns.pause": "Приостановить",
|
||||
"campaigns.plainText": "Простой текст",
|
||||
"campaigns.preview": "Предпросмотр",
|
||||
"campaigns.progress": "Прогресс",
|
||||
"campaigns.queryPlaceholder": "Имя темы",
|
||||
"campaigns.rawHTML": "Необработанный HTML",
|
||||
"campaigns.removeAltText": "Удалить альтернативное простое текстовое сообщение",
|
||||
"campaigns.richText": "Форматированный текст",
|
||||
"campaigns.schedule": "Запланировать компанию",
|
||||
"campaigns.scheduled": "Запланированные",
|
||||
"campaigns.send": "Отправить",
|
||||
"campaigns.sendLater": "Отправить позже",
|
||||
"campaigns.sendTest": "Отправить тестовое сообщение",
|
||||
"campaigns.sendTestHelp": "Нажмите Enter после ввода адреса, чтобы добавить нескольких получателей. Адреса должны принадлежать существующим подписчикам.",
|
||||
"campaigns.sendToLists": "Списки для отправки",
|
||||
"campaigns.sent": "Отправленные",
|
||||
"campaigns.start": "Запустить компанию",
|
||||
"campaigns.started": "\"{name}\" запущена",
|
||||
"campaigns.startedAt": "Запущенные",
|
||||
"campaigns.stats": "Статистика",
|
||||
"campaigns.status.cancelled": "Отменённые",
|
||||
"campaigns.status.draft": "Черновик",
|
||||
"campaigns.status.finished": "Завершена",
|
||||
"campaigns.status.paused": "Приостановлена",
|
||||
"campaigns.status.running": "Запущена",
|
||||
"campaigns.status.scheduled": "Запланирована",
|
||||
"campaigns.statusChanged": "\"{name}\" {status}",
|
||||
"campaigns.subject": "Тема",
|
||||
"campaigns.testEmails": "E-mails",
|
||||
"campaigns.testSent": "Тестовое сообщение отправлено",
|
||||
"campaigns.timestamps": "Метки времени",
|
||||
"campaigns.views": "Просмотры",
|
||||
"dashboard.campaignViews": "Просмотров компании",
|
||||
"dashboard.linkClicks": "Кликов по ссылкам",
|
||||
"dashboard.messagesSent": "Отправлено сообщений",
|
||||
"dashboard.orphanSubs": "Подписчиков не в писках",
|
||||
"email.data.info": "Копия всех записанных на вас данных прилагается в виде файла в формате JSON. Его можно просмотреть в текстовом редакторе.",
|
||||
"email.data.title": "Ваши данные",
|
||||
"email.optin.confirmSub": "Подтвердить подписку",
|
||||
"email.optin.confirmSubHelp": "Подтвердите подписку нажатием кнопки ниже.",
|
||||
"email.optin.confirmSubInfo": "Вы были добавлены в следующие листы:",
|
||||
"email.optin.confirmSubTitle": "Подтверждение подписки",
|
||||
"email.optin.confirmSubWelcome": "Привет",
|
||||
"email.optin.privateList": "Приватный список",
|
||||
"email.status.campaignReason": "Причина",
|
||||
"email.status.campaignSent": "Отправлена",
|
||||
"email.status.campaignUpdateTitle": "Обновление компании",
|
||||
"email.status.importFile": "Файл",
|
||||
"email.status.importRecords": "Записи",
|
||||
"email.status.importTitle": "Import update",
|
||||
"email.status.status": "Статус",
|
||||
"email.unsub": "Отписаться",
|
||||
"email.unsubHelp": "Не хотите получать эти письма?",
|
||||
"forms.formHTML": "Форма HTML",
|
||||
"forms.formHTMLHelp": "Используйте следующий HTML-код, чтобы показать форму подписки на внешней веб-странице. Форма должна иметь поле электронной почты и одно или несколько полей `l` (список UUID). Поле имени необязательно.",
|
||||
"forms.noPublicLists": "Для генерации формы нет публичных списков.",
|
||||
"forms.publicLists": "Публичные списки",
|
||||
"forms.publicSubPage": "Публичная страница подписки",
|
||||
"forms.selectHelp": "Выберите списки для добаления в форму.",
|
||||
"forms.title": "Формы",
|
||||
"globals.buttons.add": "Добавить",
|
||||
"globals.buttons.addNew": "Добавить",
|
||||
"globals.buttons.cancel": "Отменить",
|
||||
"globals.buttons.clone": "Клонировать",
|
||||
"globals.buttons.close": "Закрыть",
|
||||
"globals.buttons.continue": "Продолжить",
|
||||
"globals.buttons.delete": "Удалить",
|
||||
"globals.buttons.edit": "Изменить",
|
||||
"globals.buttons.enabled": "Включено",
|
||||
"globals.buttons.learnMore": "Подпробней",
|
||||
"globals.buttons.new": "Новое",
|
||||
"globals.buttons.ok": "Ok",
|
||||
"globals.buttons.remove": "Удалить",
|
||||
"globals.buttons.save": "Сохранить",
|
||||
"globals.buttons.saveChanges": "Сохранить изменения",
|
||||
"globals.days.0": "Вс",
|
||||
"globals.days.1": "Пн",
|
||||
"globals.days.2": "Вт",
|
||||
"globals.days.3": "Ср",
|
||||
"globals.days.4": "Чт",
|
||||
"globals.days.5": "Пт",
|
||||
"globals.days.6": "Сб",
|
||||
"globals.fields.createdAt": "Создано",
|
||||
"globals.fields.id": "ID",
|
||||
"globals.fields.name": "Имя",
|
||||
"globals.fields.status": "Статус",
|
||||
"globals.fields.type": "Тип",
|
||||
"globals.fields.updatedAt": "Обновлено",
|
||||
"globals.fields.uuid": "UUID",
|
||||
"globals.messages.confirm": "Уверены?",
|
||||
"globals.messages.created": "\"{name}\" создано",
|
||||
"globals.messages.deleted": "\"{name}\" удалено",
|
||||
"globals.messages.emptyState": "Ничего нет",
|
||||
"globals.messages.errorCreating": "Ошибка создания {name}: {error}",
|
||||
"globals.messages.errorDeleting": "Ошибка удаления {name}: {error}",
|
||||
"globals.messages.errorFetching": "Ошибка получения {name}: {error}",
|
||||
"globals.messages.errorUUID": "Ошибка генерации UUID: {error}",
|
||||
"globals.messages.errorUpdating": "Ошибка обновления {name}: {error}",
|
||||
"globals.messages.invalidID": "Неверный ID",
|
||||
"globals.messages.invalidUUID": "Неверный UUID",
|
||||
"globals.messages.notFound": "{name} не найдено",
|
||||
"globals.messages.passwordChange": "Введите значение для изменения",
|
||||
"globals.messages.updated": "\"{name}\" обновлено",
|
||||
"globals.months.1": "Янв",
|
||||
"globals.months.10": "Окт",
|
||||
"globals.months.11": "Ноя",
|
||||
"globals.months.12": "Дек",
|
||||
"globals.months.2": "Фев",
|
||||
"globals.months.3": "Мар",
|
||||
"globals.months.4": "Апр",
|
||||
"globals.months.5": "Май",
|
||||
"globals.months.6": "Июн",
|
||||
"globals.months.7": "Июл",
|
||||
"globals.months.8": "Авг",
|
||||
"globals.months.9": "Сен",
|
||||
"globals.terms.campaign": "Компания | Компании",
|
||||
"globals.terms.campaigns": "Компании",
|
||||
"globals.terms.dashboard": "Панель",
|
||||
"globals.terms.list": "Список | Списки",
|
||||
"globals.terms.lists": "Списки",
|
||||
"globals.terms.media": "Медиа | Медиа",
|
||||
"globals.terms.messenger": "Мессенджер | Мессенджеры",
|
||||
"globals.terms.messengers": "Мессенджеры",
|
||||
"globals.terms.settings": "Параметры",
|
||||
"globals.terms.subscriber": "Подписчик | Подписчики",
|
||||
"globals.terms.subscribers": "Подписчики",
|
||||
"globals.terms.tag": "Тег | Теги",
|
||||
"globals.terms.tags": "Теги",
|
||||
"globals.terms.template": "Шаблон | Шаблоны",
|
||||
"globals.terms.templates": "Шаблоны",
|
||||
"import.alreadyRunning": "Импорт уже выполняется. Подождите, пока он закончит, или остановите его, прежде чем пытаться снова. ",
|
||||
"import.blocklist": "Список блокировки",
|
||||
"import.csvDelim": "Разделитель CSV",
|
||||
"import.csvDelimHelp": "Разделитель по умолчанию - запятая.",
|
||||
"import.csvExample": "Пример необработанного CSV",
|
||||
"import.csvFile": "Файл CSV или ZIP",
|
||||
"import.csvFileHelp": "Кликните или перетащите сюда файл CSV или ZIP",
|
||||
"import.errorCopyingFile": "Ошибка копирования файла: {error}",
|
||||
"import.errorProcessingZIP": "Ошибка обработки файла ZIP: {error}",
|
||||
"import.errorStarting": "Ошибка запуска импорта: {error}",
|
||||
"import.importDone": "Готово",
|
||||
"import.importStarted": "Импорт запущен",
|
||||
"import.instructions": "Инструкции",
|
||||
"import.instructionsHelp": "Загрузите CSV-файл или ZIP-файл с одним CSV-файлом для массового импорта подписчиков. Файл CSV должен иметь следующие заголовки с точными названиями столбцов. Атрибуты (необязательно) должны быть допустимой строкой JSON с двойными кавычками.",
|
||||
"import.invalidDelim": "Разделителем должен быть один символ.",
|
||||
"import.invalidFile": "Неверный файл: {error}",
|
||||
"import.invalidMode": "Неверный режим",
|
||||
"import.invalidParams": "Неверные параметры: {error}",
|
||||
"import.listSubHelp": "Списки для подписки.",
|
||||
"import.mode": "Режим",
|
||||
"import.overwrite": "Перезаписать?",
|
||||
"import.overwriteHelp": "Перезаписать имя или атрибуты существующих подписчиков?",
|
||||
"import.recordsCount": "{num} / {total} записей",
|
||||
"import.stopImport": "Остановить импорт",
|
||||
"import.subscribe": "Подписаться",
|
||||
"import.title": "Импорт подписчиков",
|
||||
"import.upload": "Выгрузить",
|
||||
"lists.confirmDelete": "Уверены? Это не удалит подписчиков.",
|
||||
"lists.confirmSub": "Подтвердить подписку(и) на {name}",
|
||||
"lists.invalidName": "Неверное имя",
|
||||
"lists.newList": "Новый список",
|
||||
"lists.optin": "Подтверждение",
|
||||
"lists.optinHelp": "\"Двойное подтверждение\" отправляет подписчику электронное письмо с запросом подтверждения. Для списков с двойным подтверждением кампании отправляются только подтвержденным подписчикам",
|
||||
"lists.optinTo": "Подтвердить подписку на {name}",
|
||||
"lists.optins.double": "Двойное подтверждение",
|
||||
"lists.optins.single": "Одиночное подтверждение",
|
||||
"lists.sendCampaign": "Отправить компанию",
|
||||
"lists.sendOptinCampaign": "Отправить компанию с подтверждением подписки",
|
||||
"lists.type": "Тип",
|
||||
"lists.typeHelp": "Публичные списки открыты для всех, и их имена могут появляться на общедоступных страницах, таких как страница управления подпиской.",
|
||||
"lists.types.private": "Приватный",
|
||||
"lists.types.public": "Публичный",
|
||||
"logs.title": "Логи",
|
||||
"media.errorReadingFile": "Ошибка чтения файла: {error}",
|
||||
"media.errorResizing": "Ошибка изменения размера изображения: {error}",
|
||||
"media.errorSavingThumbnail": "Ошибка сохранения миниатюры: {error}",
|
||||
"media.errorUploading": "Ошибка выгрузки файла: {error}",
|
||||
"media.invalidFile": "Неверный файл: {error}",
|
||||
"media.title": "Медиа",
|
||||
"media.unsupportedFileType": "Неподдерживаемый тип файла ({type})",
|
||||
"media.upload": "Выгрузить",
|
||||
"media.uploadHelp": "Кликните или перетащите сюда одно или более изображений",
|
||||
"media.uploadImage": "Выгрузить изображение",
|
||||
"menu.allCampaigns": "Все компании",
|
||||
"menu.allLists": "Все списки",
|
||||
"menu.allSubscribers": "Все подписчики",
|
||||
"menu.dashboard": "Панель",
|
||||
"menu.forms": "Формы",
|
||||
"menu.import": "Импорт",
|
||||
"menu.logs": "Логи",
|
||||
"menu.media": "Медиа",
|
||||
"menu.newCampaign": "Создать новую",
|
||||
"menu.settings": "Параметры",
|
||||
"public.campaignNotFound": "Письмо не было найдено.",
|
||||
"public.confirmOptinSubTitle": "Подтверждение подписки",
|
||||
"public.confirmSub": "Подтвердить подписку",
|
||||
"public.confirmSubInfo": "Вы были добавлены в следующие списки:",
|
||||
"public.confirmSubTitle": "Подверждение",
|
||||
"public.dataRemoved": "Ваши подписки и все данные были удалены.",
|
||||
"public.dataRemovedTitle": "Данные удалены",
|
||||
"public.dataSent": "Ваши данные были отправлены Вам письмом с вложением.",
|
||||
"public.dataSentTitle": "Данные отправлены письмом",
|
||||
"public.errorFetchingCampaign": "Ошибка получения письма.",
|
||||
"public.errorFetchingEmail": "Письмо не найдено",
|
||||
"public.errorFetchingLists": "Ошибка получения списков. Пожалуйста, повторите.",
|
||||
"public.errorProcessingRequest": "Ошибка обработки запроса. Пожалуйста, повторите.",
|
||||
"public.errorTitle": "Ошибка",
|
||||
"public.invalidFeature": "Эта функция недоступна.",
|
||||
"public.invalidLink": "Неверная ссылка",
|
||||
"public.noListsAvailable": "Нет доступных списков для подписки.",
|
||||
"public.noListsSelected": "Для подписки не выбраны действительные списки.",
|
||||
"public.noSubInfo": "Нет подписок для подтверждения.",
|
||||
"public.noSubTitle": "Нет подписок",
|
||||
"public.notFoundTitle": "Не найдено",
|
||||
"public.privacyConfirmWipe": "Вы уверены, что хотите навсегда удалить все данные о подписке?",
|
||||
"public.privacyExport": "Экспортировать Ваши данные",
|
||||
"public.privacyExportHelp": "Копия Ваших данных будет отправлена Вам письмом",
|
||||
"public.privacyTitle": "Конфиденциальность и данные",
|
||||
"public.privacyWipe": "Стереть Ваши данные",
|
||||
"public.privacyWipeHelp": "Удалит все Ваши подписки и связанные данные из базы данных без возможности восстановления. ",
|
||||
"public.sub": "Подписаться",
|
||||
"public.subConfirmed": "Успешно подписано.",
|
||||
"public.subConfirmedTitle": "Подтверждено",
|
||||
"public.subName": "Имя (необязательно)",
|
||||
"public.subNotFound": "Подписка не найдена.",
|
||||
"public.subOptinPending": "Для подтверждения подписки(ок) Вам было отправлено письмо.",
|
||||
"public.subPrivateList": "Приватный список",
|
||||
"public.subTitle": "Подписаться",
|
||||
"public.unsub": "Отписаться",
|
||||
"public.unsubFull": "Также отписаться от всех будущих писем.",
|
||||
"public.unsubHelp": "Хотите отписаться от этих списков рассылки?",
|
||||
"public.unsubTitle": "Отписаться",
|
||||
"public.unsubbedInfo": "Вы были отписаны.",
|
||||
"public.unsubbedTitle": "Отписано",
|
||||
"public.unsubscribeTitle": "Отписаться от списков рассылки",
|
||||
"settings.confirmRestart": "Убедитесь, что запущенные кампании приостановлены. Запустить снова?",
|
||||
"settings.duplicateMessengerName": "Повторяющееся имя мессенджера: {name}",
|
||||
"settings.errorEncoding": "Error encoding settings: {error}",
|
||||
"settings.errorNoSMTP": "Должен быть включён минимум один блок SMTP",
|
||||
"settings.general.adminNotifEmails": "Письма с уведомлениями для администратора",
|
||||
"settings.general.adminNotifEmailsHelp": "Список адресов электронной почты, разделенных запятыми, на которые следует отправлять уведомления администратора, такие как обновления импорта, завершение кампании, сбой и т.д. ",
|
||||
"settings.general.checkUpdates": "Check for updates",
|
||||
"settings.general.checkUpdatesHelp": "Periodically check for new app releases and notify.",
|
||||
"settings.general.enablePublicSubPage": "Включить публичную страницу подписки",
|
||||
"settings.general.enablePublicSubPageHelp": "Показать страницу общедоступной подписки со всеми общедоступными списками, на которые можно подписаться.",
|
||||
"settings.general.faviconURL": "Favicon URL",
|
||||
"settings.general.faviconURLHelp": "(Необязательно) полный URL на favicon, который будет отображён, например, на странице отписки",
|
||||
"settings.general.fromEmail": "Адрес`from` по умолчанию",
|
||||
"settings.general.fromEmailHelp": "Адрес `from` по умолчанию для отображения в исходящих письмах компании. Можно изменить для каждой компании.",
|
||||
"settings.general.language": "Язык",
|
||||
"settings.general.logoURL": "URL логотипа",
|
||||
"settings.general.logoURLHelp": "(Необязательно) полный URL на логотип, который будет отображён, например, на странице отписки.",
|
||||
"settings.general.name": "Основное",
|
||||
"settings.general.rootURL": "Базовый URL",
|
||||
"settings.general.rootURLHelp": "Публичный URL текущего портала (без конечного слэша).",
|
||||
"settings.invalidMessengerName": "Неверное имя мессенджера.",
|
||||
"settings.media.provider": "Провайдер",
|
||||
"settings.media.s3.bucket": "Bucket",
|
||||
"settings.media.s3.bucketPath": "Путь bucket",
|
||||
"settings.media.s3.bucketPathHelp": "Путь внутри bucket для выгрузки файлов. По умолчанию - /",
|
||||
"settings.media.s3.bucketType": "Тип bucket",
|
||||
"settings.media.s3.bucketTypePrivate": "Приватный",
|
||||
"settings.media.s3.bucketTypePublic": "Публичный",
|
||||
"settings.media.s3.key": "Ключ доступа AWS",
|
||||
"settings.media.s3.region": "Регион",
|
||||
"settings.media.s3.secret": "Секретаня фраза AWS",
|
||||
"settings.media.s3.uploadExpiry": "Срок жизни выгрузки",
|
||||
"settings.media.s3.uploadExpiryHelp": "(Необязательно) Укажите TTL (в секундах) сгенерированного подписанного URL. Применимо только для приватных bucket (s, m, h, d соответствует секундам, минутам, часам и дням).",
|
||||
"settings.media.title": "Выгрузки медиа",
|
||||
"settings.media.upload.path": "Путь для выгрузок",
|
||||
"settings.media.upload.pathHelp": "Путь до каталога, куда будут выгружаться медиа-файлы.",
|
||||
"settings.media.upload.uri": "URI выгрузок",
|
||||
"settings.media.upload.uriHelp": "URI выгрузок, который будет видим снаружи. Медиа-файлы, выгруженные в upload_path, будут доступны публично через {root_url}, например, https://listmonk.yoursite.com/uploads.",
|
||||
"settings.messengers.maxConns": "Максимальное число соединений",
|
||||
"settings.messengers.maxConnsHelp": "Максимальное число одновременных соединений к серверу.",
|
||||
"settings.messengers.messageDiscard": "Отказаться от изменений?",
|
||||
"settings.messengers.messageSaved": "Параметры сохранены. Перезагружаем приложение...",
|
||||
"settings.messengers.name": "Мессенджеры",
|
||||
"settings.messengers.nameHelp": "Напр.: my-sms. Цифры буквы / тире.",
|
||||
"settings.messengers.password": "Пароль",
|
||||
"settings.messengers.retries": "Повторные попытки",
|
||||
"settings.messengers.retriesHelp": "Число повторных попыток после ошибки отправки сообщения.",
|
||||
"settings.messengers.skipTLSHelp": "Не проверять мя хоста в сертификате TLS.",
|
||||
"settings.messengers.timeout": "Таймаут простоя",
|
||||
"settings.messengers.timeoutHelp": "Время ожидания новой активности в соединении перед тем, как закрыть и удалить его из пула (s, m соотвественно секунды и минуты)",
|
||||
"settings.messengers.url": "URL",
|
||||
"settings.messengers.urlHelp": "Базовый URL сервера постбэк.",
|
||||
"settings.messengers.username": "Имя пользователя",
|
||||
"settings.needsRestart": "Параметры изменены. Приостановите все запущенные компании и перезапустите приложение",
|
||||
"settings.performance.batchSize": "Размер партии",
|
||||
"settings.performance.batchSizeHelp": "Количество подписчиков, которые нужно извлечь из базы данных за одну итерацию. Каждая итерация извлекает подписчиков из базы данных, отправляет им сообщения, а затем переходит к следующей итерации, чтобы получить следующую партию. В идеале это должно быть выше максимально достижимой пропускной способности (concurrency * message_rate). ",
|
||||
"settings.performance.concurrency": "Параллельное выполнение",
|
||||
"settings.performance.concurrencyHelp": "Максимальное число одновременно работающих процессов, которые будут пытаться одновременно отправить сообщения.",
|
||||
"settings.performance.maxErrThreshold": "Порог максимального числа ошибок",
|
||||
"settings.performance.maxErrThresholdHelp": "Число ошибок (например, таймауты SMTP во время отправки писем), после которого запущенная компания должна быть приостановлена для изучения или вмешательства.",
|
||||
"settings.performance.messageRate": "Скорость сообщений",
|
||||
"settings.performance.messageRateHelp": "Максимальное количество сообщений, отправляемых одним рабочим процессом в секунду. Если concurrency = 10 и message_rate = 10, то до 10x10 = 100 сообщений могут выталкиваться каждую секунду. Этот параметр, наряду с параллельным выполнением, следует настроить так, чтобы количество отправляемых сообщений в секунду не вышло за рамки ограничений скорости (если таковые имеются) целевых серверов SMTP.",
|
||||
"settings.performance.name": "Производительность",
|
||||
"settings.performance.slidingWindow": "Включить ограничение скользящего окна",
|
||||
"settings.performance.slidingWindowDuration": "Длительность",
|
||||
"settings.performance.slidingWindowDurationHelp": "Длительность периода скользящего окна (m, h соотвественно минуты и часы)",
|
||||
"settings.performance.slidingWindowHelp": "Ограничить количество сообщений, которые будут отправлены в указанный период. По достижении этого ограничения, сообщения будут задержаны до очистки временного окна.",
|
||||
"settings.performance.slidingWindowRate": "Максимальное количество сообщений",
|
||||
"settings.performance.slidingWindowRateHelp": "Максимальное количество сообщений, которые будут отправлены в течение временного окна.",
|
||||
"settings.privacy.allowBlocklist": "Разрешить блокировку",
|
||||
"settings.privacy.allowBlocklistHelp": "Позволить подписчикам отписываться от всех списков рассылки и помечать себя заблокированными?",
|
||||
"settings.privacy.allowExport": "Разрешить экспорт",
|
||||
"settings.privacy.allowExportHelp": "Разрешить подписчикам экспортировать собранные на них данные?",
|
||||
"settings.privacy.allowWipe": "Разрешить удаление",
|
||||
"settings.privacy.allowWipeHelp": "Разрешить подписчикам удалять себя (включая их подписки и иные данные) из базы данных. Просмотры кампании и клики по ссылкам также удаляются, в то время как просмотры и счетчики кликов остаются (без привязанного к ним подписчика), так что это не влияет на статистику и аналитику.",
|
||||
"settings.privacy.individualSubTracking": "Отслеживание каждого подписчика",
|
||||
"settings.privacy.individualSubTrackingHelp": "Отслеживать просмотры и клики на уровне каждого подписчика. Если отключено, просмотры и клики отслеживаются без привязки к конкретным подписчикам.",
|
||||
"settings.privacy.listUnsubHeader": "Включать заголовок `List-Unsubscribe`",
|
||||
"settings.privacy.listUnsubHeaderHelp": "Включать заголовок отписки",
|
||||
"settings.privacy.name": "Конфиденциальност",
|
||||
"settings.restart": "Перезапустить",
|
||||
"settings.smtp.authProtocol": "Протокол авторизации",
|
||||
"settings.smtp.customHeaders": "Настраиваемые заголовки",
|
||||
"settings.smtp.customHeadersHelp": "Необязательный массив заголовков e-mail, которые будут включены во все письма, отправляемые с этого сервера. Например: [{\"X-Custom\": \"значение\"}, {\"X-Custom2\": \"значение\"}]",
|
||||
"settings.smtp.enabled": "Включено",
|
||||
"settings.smtp.heloHost": "Имя хоста HELO",
|
||||
"settings.smtp.heloHostHelp": "Необязательно. Некоторые серверы SMTP требуют FQDN в имени хоста. По умолчанию команды HELO идут с `localhost`. Укажите, если должно использоваться собственное имя хоста.",
|
||||
"settings.smtp.host": "Хост",
|
||||
"settings.smtp.hostHelp": "Адрес сервера SMTP.",
|
||||
"settings.smtp.idleTimeout": "Таймаут простоя",
|
||||
"settings.smtp.idleTimeoutHelp": "Время ожидания новой активности в соединении перед тем, как закрыть и удалить его из пула (s, m соотвественно секунды и минуты).",
|
||||
"settings.smtp.maxConns": "Максимальное количество соединений",
|
||||
"settings.smtp.maxConnsHelp": "Максимальное количество одновременных соединений к серверу SMTP.",
|
||||
"settings.smtp.name": "SMTP",
|
||||
"settings.smtp.password": "Пароль",
|
||||
"settings.smtp.passwordHelp": "Для изменения введите",
|
||||
"settings.smtp.port": "Порт",
|
||||
"settings.smtp.portHelp": "Порт сервера SMTP.",
|
||||
"settings.smtp.retries": "Повторные попытки",
|
||||
"settings.smtp.retriesHelp": "Количество повторных попыток после ошибки отправки сообщения.",
|
||||
"settings.smtp.setCustomHeaders": "Установка настраиваемых заголовков",
|
||||
"settings.smtp.skipTLS": "Пропустить проверку TLS",
|
||||
"settings.smtp.skipTLSHelp": "Не проверять имя хоста в сертификате TLS.",
|
||||
"settings.smtp.tls": "TLS",
|
||||
"settings.smtp.tlsHelp": "Включить STARTTLS.",
|
||||
"settings.smtp.username": "Имя пользователя",
|
||||
"settings.smtp.waitTimeout": "Таймаут ожидания",
|
||||
"settings.smtp.waitTimeoutHelp": "Время ожидания новой активности в соединении перед тем, как закрыть и удалить его из пула (s, m соттветственно секунды и минуты)",
|
||||
"settings.title": "Параметры",
|
||||
"settings.updateAvailable": "Доступна новая версия: {version}.",
|
||||
"subscribers.advancedQuery": "Дополнительно",
|
||||
"subscribers.advancedQueryHelp": "Частичное выражение SQL для запроса атрибутов подписчика",
|
||||
"subscribers.attribs": "Атрибуты",
|
||||
"subscribers.attribsHelp": "Атрибуты определны, как сопоставление JSON, например:",
|
||||
"subscribers.blocklistedHelp": "Заблокированные подписчики никогда не получат ни одного письма.",
|
||||
"subscribers.confirmBlocklist": "Заблокировать {num} подписчика(ов)?",
|
||||
"subscribers.confirmDelete": "Удалить {num} подписчика(ов)?",
|
||||
"subscribers.confirmExport": "Экспортировать {num} подписчика(ов)?",
|
||||
"subscribers.downloadData": "Загрузить данные",
|
||||
"subscribers.email": "E-mail",
|
||||
"subscribers.emailExists": "E-mail существует.",
|
||||
"subscribers.errorBlocklisting": "Ошибка блокировки подписчиков: {error}",
|
||||
"subscribers.errorInvalidIDs": "Указан один или более неверных ID: {error}",
|
||||
"subscribers.errorNoIDs": "Не указано ни одного ID.",
|
||||
"subscribers.errorNoListsGiven": "Не указано ни одного списка.",
|
||||
"subscribers.errorPreparingQuery": "Ошибка подготовки запроса подписчиков: {error}",
|
||||
"subscribers.errorSendingOptin": "Ошибка отправки письма подтверждения.",
|
||||
"subscribers.export": "Экспорт",
|
||||
"subscribers.invalidAction": "Неверное действие.",
|
||||
"subscribers.invalidEmail": "Неверное письмо.",
|
||||
"subscribers.invalidJSON": "Неверный JSON в атрибутах.",
|
||||
"subscribers.invalidName": "Неверное имя.",
|
||||
"subscribers.listChangeApplied": "Изменения списка применены.",
|
||||
"subscribers.lists": "Списки",
|
||||
"subscribers.listsHelp": "Списки, от которых подписчики сами отписались, не могут быть удалены.",
|
||||
"subscribers.listsPlaceholder": "Списки для подписки",
|
||||
"subscribers.manageLists": "Управление списками",
|
||||
"subscribers.markUnsubscribed": "Ометить, как отписанный",
|
||||
"subscribers.newSubscriber": "Новый подписчик",
|
||||
"subscribers.numSelected": "{num} подписчика(ов) выбрано",
|
||||
"subscribers.optinSubject": "Подтвердить подписку",
|
||||
"subscribers.query": "Запрос",
|
||||
"subscribers.queryPlaceholder": "E-mail или имя",
|
||||
"subscribers.reset": "Сброс",
|
||||
"subscribers.selectAll": "Выбрать все {num}",
|
||||
"subscribers.status.blocklisted": "Заблокирован",
|
||||
"subscribers.status.confirmed": "Подтверждён",
|
||||
"subscribers.status.enabled": "Включён",
|
||||
"subscribers.status.subscribed": "Подписан",
|
||||
"subscribers.status.unconfirmed": "Неподтверждён",
|
||||
"subscribers.status.unsubscribed": "Отписан",
|
||||
"subscribers.subscribersDeleted": "{num} подписчика(ов) удалено",
|
||||
"templates.cantDeleteDefault": "Нельзя удалить шаблон по умолчанию",
|
||||
"templates.default": "По умолчанию",
|
||||
"templates.dummyName": "Пустая компания",
|
||||
"templates.dummySubject": "Рустая тема письма",
|
||||
"templates.errorCompiling": "Ошибка компиляции шаблона: {error}",
|
||||
"templates.errorRendering": "Ошибка рендеринга сообщения: {error}",
|
||||
"templates.fieldInvalidName": "Неверная длина имени.",
|
||||
"templates.makeDefault": "Установить по умолчанию",
|
||||
"templates.newTemplate": "Новый шаблон",
|
||||
"templates.placeholderHelp": "Заполнитель {placeholder} должен присутствовать в шаблоне в одном экземпляре.",
|
||||
"templates.preview": "Предпросмотр",
|
||||
"templates.rawHTML": "Необработанный HTML"
|
||||
}
|
|
@ -11,6 +11,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/knadh/listmonk/internal/i18n"
|
||||
"github.com/knadh/listmonk/internal/messenger"
|
||||
"github.com/knadh/listmonk/models"
|
||||
|
@ -315,7 +316,7 @@ func (m *Manager) messageWorker() {
|
|||
// TemplateFuncs returns the template functions to be applied into
|
||||
// compiled campaign templates.
|
||||
func (m *Manager) TemplateFuncs(c *models.Campaign) template.FuncMap {
|
||||
return template.FuncMap{
|
||||
f := template.FuncMap{
|
||||
"TrackLink": func(url string, msg *CampaignMessage) string {
|
||||
subUUID := msg.Subscriber.UUID
|
||||
if !m.cfg.IndividualTracking {
|
||||
|
@ -353,7 +354,14 @@ func (m *Manager) TemplateFuncs(c *models.Campaign) template.FuncMap {
|
|||
"L": func() *i18n.I18n {
|
||||
return m.i18n
|
||||
},
|
||||
"Safe": func(safeHTML string) template.HTML {
|
||||
return template.HTML(safeHTML)
|
||||
},
|
||||
}
|
||||
for k, v := range sprig.GenericFuncMap() {
|
||||
f[k] = v
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// Close closes and exits the campaign manager.
|
||||
|
|
24
internal/migrations/v1.0.0.go
Normal file
24
internal/migrations/v1.0.0.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/knadh/koanf"
|
||||
"github.com/knadh/stuffbin"
|
||||
)
|
||||
|
||||
// V1_0_0 performs the DB migrations for v.1.0.0.
|
||||
func V1_0_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error {
|
||||
if _, err := db.Exec(`ALTER TYPE content_type ADD VALUE IF NOT EXISTS 'markdown'`); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := db.Exec(`
|
||||
INSERT INTO settings (key, value) VALUES
|
||||
('app.check_updates', 'true')
|
||||
ON CONFLICT DO NOTHING;
|
||||
`); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -91,6 +91,7 @@ type SubReq struct {
|
|||
models.Subscriber
|
||||
Lists pq.Int64Array `json:"lists"`
|
||||
ListUUIDs pq.StringArray `json:"list_uuids"`
|
||||
PreconfirmSubs bool `json:"preconfirm_subscriptions"`
|
||||
}
|
||||
|
||||
type importStatusTpl struct {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
@ -12,7 +13,9 @@ import (
|
|||
"github.com/jmoiron/sqlx"
|
||||
"github.com/jmoiron/sqlx/types"
|
||||
"github.com/lib/pq"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
null "gopkg.in/volatiletech/null.v6"
|
||||
)
|
||||
|
||||
|
@ -39,6 +42,7 @@ const (
|
|||
CampaignTypeOptin = "optin"
|
||||
CampaignContentTypeRichtext = "richtext"
|
||||
CampaignContentTypeHTML = "html"
|
||||
CampaignContentTypeMarkdown = "markdown"
|
||||
CampaignContentTypePlain = "plain"
|
||||
|
||||
// List.
|
||||
|
@ -222,6 +226,19 @@ type Template struct {
|
|||
IsDefault bool `db:"is_default" json:"is_default"`
|
||||
}
|
||||
|
||||
// markdown is a global instance of Markdown parser and renderer.
|
||||
var markdown = goldmark.New(
|
||||
goldmark.WithRendererOptions(
|
||||
html.WithXHTML(),
|
||||
html.WithUnsafe(),
|
||||
),
|
||||
goldmark.WithExtensions(
|
||||
extension.Table,
|
||||
extension.Strikethrough,
|
||||
extension.TaskList,
|
||||
),
|
||||
)
|
||||
|
||||
// GetIDs returns the list of subscriber IDs.
|
||||
func (subs Subscribers) GetIDs() []int {
|
||||
IDs := make([]int, len(subs))
|
||||
|
@ -312,8 +329,18 @@ func (c *Campaign) CompileTemplate(f template.FuncMap) error {
|
|||
return fmt.Errorf("error compiling base template: %v", err)
|
||||
}
|
||||
|
||||
// Compile the campaign message.
|
||||
// If the format is markdown, convert Markdown to HTML.
|
||||
if c.ContentType == CampaignContentTypeMarkdown {
|
||||
var b bytes.Buffer
|
||||
if err := markdown.Convert([]byte(c.Body), &b); err != nil {
|
||||
return err
|
||||
}
|
||||
body = b.String()
|
||||
} else {
|
||||
body = c.Body
|
||||
}
|
||||
|
||||
// Compile the campaign message.
|
||||
for _, r := range regTplFuncs {
|
||||
body = r.regExp.ReplaceAllString(body, r.replace)
|
||||
}
|
||||
|
@ -356,6 +383,30 @@ func (c *Campaign) CompileTemplate(f template.FuncMap) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ConvertContent converts a campaign's body from one format to another,
|
||||
// for example, Markdown to HTML.
|
||||
func (c *Campaign) ConvertContent(from, to string) (string, error) {
|
||||
body := c.Body
|
||||
for _, r := range regTplFuncs {
|
||||
body = r.regExp.ReplaceAllString(body, r.replace)
|
||||
}
|
||||
|
||||
// If the format is markdown, convert Markdown to HTML.
|
||||
var out string
|
||||
if from == CampaignContentTypeMarkdown &&
|
||||
(to == CampaignContentTypeHTML || to == CampaignContentTypeRichtext) {
|
||||
var b bytes.Buffer
|
||||
if err := markdown.Convert([]byte(c.Body), &b); err != nil {
|
||||
return out, err
|
||||
}
|
||||
out = b.String()
|
||||
} else {
|
||||
return out, errors.New("unknown formats to convert")
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// FirstName splits the name by spaces and returns the first chunk
|
||||
// of the name that's greater than 2 characters in length, assuming
|
||||
// that it is the subscriber's first name.
|
||||
|
|
|
@ -58,6 +58,7 @@ SELECT id as subscriber_id,
|
|||
WITH sub AS (
|
||||
INSERT INTO subscribers (uuid, email, name, status, attribs)
|
||||
VALUES($1, $2, $3, $4, $5)
|
||||
ON CONFLICT(email) DO UPDATE SET updated_at=NOW()
|
||||
returning id
|
||||
),
|
||||
listIDs AS (
|
||||
|
@ -70,7 +71,7 @@ subs AS (
|
|||
VALUES(
|
||||
(SELECT id FROM sub),
|
||||
UNNEST(ARRAY(SELECT id FROM listIDs)),
|
||||
(CASE WHEN $4='blocklisted' THEN 'unsubscribed'::subscription_status ELSE 'unconfirmed' END)
|
||||
(CASE WHEN $4='blocklisted' THEN 'unsubscribed'::subscription_status ELSE $8::subscription_status END)
|
||||
)
|
||||
ON CONFLICT (subscriber_id, list_id) DO UPDATE
|
||||
SET updated_at=NOW()
|
||||
|
@ -120,7 +121,7 @@ WITH s AS (
|
|||
email=(CASE WHEN $2 != '' THEN $2 ELSE email END),
|
||||
name=(CASE WHEN $3 != '' THEN $3 ELSE name END),
|
||||
status=(CASE WHEN $4 != '' THEN $4::subscriber_status ELSE status END),
|
||||
attribs=(CASE WHEN $5::TEXT != '' THEN $5::JSONB ELSE attribs END),
|
||||
attribs=(CASE WHEN $5 != '' THEN $5::JSONB ELSE attribs END),
|
||||
updated_at=NOW()
|
||||
WHERE id = $1 RETURNING id
|
||||
),
|
||||
|
|
|
@ -4,7 +4,7 @@ DROP TYPE IF EXISTS subscriber_status CASCADE; CREATE TYPE subscriber_status AS
|
|||
DROP TYPE IF EXISTS subscription_status CASCADE; CREATE TYPE subscription_status AS ENUM ('unconfirmed', 'confirmed', 'unsubscribed');
|
||||
DROP TYPE IF EXISTS campaign_status CASCADE; CREATE TYPE campaign_status AS ENUM ('draft', 'running', 'scheduled', 'paused', 'cancelled', 'finished');
|
||||
DROP TYPE IF EXISTS campaign_type CASCADE; CREATE TYPE campaign_type AS ENUM ('regular', 'optin');
|
||||
DROP TYPE IF EXISTS content_type CASCADE; CREATE TYPE content_type AS ENUM ('richtext', 'html', 'plain');
|
||||
DROP TYPE IF EXISTS content_type CASCADE; CREATE TYPE content_type AS ENUM ('richtext', 'html', 'plain', 'markdown');
|
||||
|
||||
-- subscribers
|
||||
DROP TABLE IF EXISTS subscribers CASCADE;
|
||||
|
@ -178,6 +178,7 @@ INSERT INTO settings (key, value) VALUES
|
|||
('app.message_sliding_window_duration', '"1h"'),
|
||||
('app.message_sliding_window_rate', '10000'),
|
||||
('app.enable_public_subscription_page', 'true'),
|
||||
('app.check_updates', 'true'),
|
||||
('app.notify_emails', '["admin1@mysite.com", "admin2@mysite.com"]'),
|
||||
('app.lang', '"en"'),
|
||||
('privacy.individual_tracking', 'false'),
|
||||
|
|
14
scripts/refresh-i18n.sh
Executable file
14
scripts/refresh-i18n.sh
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
# "Refresh" all i18n language files by merging missing keys in lang files
|
||||
# from a base language file. In addition, sort all files by keys.
|
||||
|
||||
BASE_DIR=$(dirname "$0")"/../i18n" # Exclude the trailing slash.
|
||||
BASE_FILE="en.json"
|
||||
|
||||
# Iterate through all i18n files and merge them into the base file,
|
||||
# filling in missing keys.
|
||||
for fpath in "$BASE_DIR/"*.json; do
|
||||
echo $(basename -- $fpath)
|
||||
echo "$( jq -s '.[0] * .[1]' -S --indent 4 "$BASE_DIR/$BASE_FILE" $fpath )" > $fpath
|
||||
done
|
|
@ -3,7 +3,7 @@
|
|||
<h2>{{ L.Ts "email.status.campaignUpdateTitle" }}</h2>
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td width="30%"><strong>{{ L.Ts "globa.L.Terms.campaign" }}</strong></td>
|
||||
<td width="30%"><strong>{{ L.Ts "global.terms.campaign" }}</strong></td>
|
||||
<td><a href="{{ index . "RootURL" }}/campaigns/{{ index . "ID" }}">{{ index . "Name" }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -274,6 +274,9 @@ input[type="text"], input[type="email"], select {
|
|||
.form .lists {
|
||||
margin-top: 45px;
|
||||
}
|
||||
.form .nonce {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
<div>
|
||||
<p>
|
||||
<label>{{ L.T "subscribers.email" }}</label>
|
||||
<input name="email" required="true" type="email" placeholder="{{ L.T "subscribers.email" }}" >
|
||||
<input name="email" required="true" type="email" placeholder="{{ L.T "subscribers.email" }}" autofocus="true" >
|
||||
|
||||
<input name="nonce" class="nonce" value="" />
|
||||
</p>
|
||||
<p>
|
||||
<label>{{ L.T "public.subName" }}</label>
|
||||
|
|
Loading…
Reference in a new issue