From 868fae6ac2384bf277d1a3d0e912419512b2659d Mon Sep 17 00:00:00 2001 From: Kailash Nadh Date: Sun, 6 Jun 2021 17:31:29 +0530 Subject: [PATCH] Refactor subsbscription status option on the import page. - Refactor subimporter New*() funcs to take opt structs. - Refactor and simplify Vue code. - Remove redundant i18n entries and use existing ones. - Remove redundant subimporter constants and use existing ones. - Consider 'overwrite' option for subscription status as well. - Write Cypress integration tests for the new feature. --- cmd/import.go | 42 +- frontend/cypress/integration/import.js | 40 +- frontend/src/views/Import.vue | 87 ++- i18n/de.json | 1 + i18n/en.json | 8 +- i18n/es.json | 1 + i18n/fr.json | 1 + i18n/it.json | 1 + i18n/ml.json | 1 + i18n/pl.json | 1 + i18n/pt-BR.json | 1 + i18n/pt.json | 1 + i18n/ru.json | 1 + i18n/tr.json | 878 +++++++++++++------------ internal/subimporter/importer.go | 47 +- queries.sql | 2 +- 16 files changed, 579 insertions(+), 534 deletions(-) diff --git a/cmd/import.go b/cmd/import.go index 7f90907..a3fcf83 100644 --- a/cmd/import.go +++ b/cmd/import.go @@ -8,18 +8,10 @@ import ( "strings" "github.com/knadh/listmonk/internal/subimporter" + "github.com/knadh/listmonk/models" "github.com/labstack/echo" ) -// reqImport represents file upload import params. -type reqImport struct { - Mode string `json:"mode"` - SubscriptionStatus string `json:"subscriptionStatus"` - Overwrite bool `json:"overwrite"` - Delim string `json:"delim"` - ListIDs []int `json:"lists"` -} - // handleImportSubscribers handles the uploading and bulk importing of // a ZIP file of one or more CSV files. func handleImportSubscribers(c echo.Context) error { @@ -31,21 +23,34 @@ func handleImportSubscribers(c echo.Context) error { } // Unmarsal the JSON params. - var r reqImport - if err := json.Unmarshal([]byte(c.FormValue("params")), &r); err != nil { + var opt subimporter.SessionOpt + if err := json.Unmarshal([]byte(c.FormValue("params")), &opt); err != nil { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("import.invalidParams", "error", err.Error())) } - if r.Mode != subimporter.ModeSubscribe && r.Mode != subimporter.ModeBlocklist { + // Validate mode. + if opt.Mode != subimporter.ModeSubscribe && opt.Mode != subimporter.ModeBlocklist { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("import.invalidMode")) } - if r.SubscriptionStatus != subimporter.SubscriptionStatusUnconfirmed && r.SubscriptionStatus != subimporter.SubscriptionStatusConfirmed { - return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("import.invalidSubscriptionStatus")) + // If no status is specified, pick a default one. + if opt.SubStatus == "" { + switch opt.Mode { + case subimporter.ModeSubscribe: + opt.SubStatus = models.SubscriptionStatusUnconfirmed + case subimporter.ModeBlocklist: + opt.SubStatus = models.SubscriptionStatusUnsubscribed + } } - if len(r.Delim) != 1 { + if opt.SubStatus != models.SubscriptionStatusUnconfirmed && + opt.SubStatus != models.SubscriptionStatusConfirmed && + opt.SubStatus != models.SubscriptionStatusUnsubscribed { + return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("import.invalidSubStatus")) + } + + if len(opt.Delim) != 1 { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("import.invalidDelim")) } @@ -74,7 +79,8 @@ func handleImportSubscribers(c echo.Context) error { } // Start the importer session. - impSess, err := app.importer.NewSession(file.Filename, r.Mode, r.SubscriptionStatus, r.Overwrite, r.ListIDs) + opt.Filename = file.Filename + impSess, err := app.importer.NewSession(opt) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, app.i18n.Ts("import.errorStarting", "error", err.Error())) @@ -82,7 +88,7 @@ func handleImportSubscribers(c echo.Context) error { go impSess.Start() if strings.HasSuffix(strings.ToLower(file.Filename), ".csv") { - go impSess.LoadCSV(out.Name(), rune(r.Delim[0])) + go impSess.LoadCSV(out.Name(), rune(opt.Delim[0])) } else { // Only 1 CSV from the ZIP is considered. If multiple files have // to be processed, counting the net number of lines (to track progress), @@ -95,7 +101,7 @@ func handleImportSubscribers(c echo.Context) error { return echo.NewHTTPError(http.StatusInternalServerError, app.i18n.Ts("import.errorProcessingZIP", "error", err.Error())) } - go impSess.LoadCSV(dir+"/"+files[0], rune(r.Delim[0])) + go impSess.LoadCSV(dir+"/"+files[0], rune(opt.Delim[0])) } return c.JSON(http.StatusOK, okResp{app.importer.GetStats()}) diff --git a/frontend/cypress/integration/import.js b/frontend/cypress/integration/import.js index b8b8d55..bc3f9eb 100644 --- a/frontend/cypress/integration/import.js +++ b/frontend/cypress/integration/import.js @@ -7,12 +7,19 @@ describe('Import', () => { it('Imports subscribers', () => { const cases = [ - { mode: 'check-subscribe', status: 'enabled', count: 102 }, - { mode: 'check-blocklist', status: 'blocklisted', count: 102 }, + { chkMode: 'subscribe', status: 'enabled', chkSubStatus: 'unconfirmed', subStatus: 'unconfirmed', overwrite: true, count: 102 }, + { chkMode: 'subscribe', status: 'enabled', chkSubStatus: 'confirmed', subStatus: 'confirmed', overwrite: true, count: 102 }, + { chkMode: 'subscribe', status: 'enabled', chkSubStatus: 'unconfirmed', subStatus: 'confirmed', overwrite: false, count: 102 }, + { chkMode: 'blocklist', status: 'blocklisted', chkSubStatus: 'unsubscribed', subStatus: 'unsubscribed', overwrite: true, count: 102 }, ]; cases.forEach((c) => { - cy.get(`[data-cy=${c.mode}] .check`).click(); + cy.get(`[data-cy=check-${c.chkMode}] .check`).click(); + cy.get(`[data-cy=check-${c.chkSubStatus}] .check`).click(); + + if (!c.overwrite) { + cy.get(`[data-cy=overwrite]`).click(); + } if (c.status === 'enabled') { cy.get('.list-selector input').click(); @@ -39,12 +46,39 @@ describe('Import', () => { cy.expect(parseInt($el.text().trim())).to.equal(c.count); }); + // Subscriber status. cy.get('tbody td[data-label=Status]').each(($el) => { cy.wrap($el).find(`.tag.${c.status}`); }); + // Subscription status. + cy.get('tbody td[data-label=E-mail]').each(($el) => { + cy.wrap($el).find(`.tag.${c.subStatus}`); + }); + cy.loginAndVisit('/subscribers/import'); cy.wait(100); }); }); + + it('Imports subscribers incorrectly', () => { + cy.resetDB(); + cy.loginAndVisit('/subscribers/import'); + + cy.get('.list-selector input').click(); + cy.get('.list-selector .autocomplete a').first().click(); + cy.get('input[name=delim]').clear().type('|'); + + 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.wait(250); + cy.get('section.wrap .has-text-danger'); + }); }); diff --git a/frontend/src/views/Import.vue b/frontend/src/views/Import.vue index d815984..24e39cb 100644 --- a/frontend/src/views/Import.vue +++ b/frontend/src/views/Import.vue @@ -8,7 +8,7 @@
- +
- -
-
- - {{ $t('import.unconfirmed') }} - -
-
- - {{ $t('import.confirmed') }} - -
-
- - {{ $t('import.unsubscribed') }} - -
-
+ + + + + {{ $t('subscribers.status.unsubscribed') }} +
+
- +
+
- - + +
@@ -187,7 +182,7 @@ export default Vue.extend({ return { form: { mode: 'subscribe', - subscriptionStatus: 'unconfirmed', + subStatus: 'unconfirmed', delim: ',', lists: [], overwrite: true, @@ -206,15 +201,15 @@ export default Vue.extend({ }, watch: { - form: { - handler(val) { - if (val.mode === 'subscribe') { - this.form.subscriptionStatus = 'unconfirmed'; - } else if (val.mode === 'blocklist') { - this.form.subscriptionStatus = 'unsubscribed'; + 'form.mode': function formMode() { + // Select the appropriate status radio whenever mode changes. + this.$nextTick(() => { + if (this.form.mode === 'subscribe') { + this.form.subStatus = 'unconfirmed'; + } else { + this.form.subStatus = 'unsubscribed'; } - }, - deep: true, + }); }, }, @@ -317,7 +312,7 @@ export default Vue.extend({ const params = new FormData(); params.set('params', JSON.stringify({ mode: this.form.mode, - subscriptionStatus: this.form.subscriptionStatus, + subscription_status: this.form.subStatus, delim: this.form.delim, lists: this.form.lists.map((l) => l.id), overwrite: this.form.overwrite, diff --git a/i18n/de.json b/i18n/de.json index 8c99532..a663ba0 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -186,6 +186,7 @@ "import.invalidFile": "Ungültige Datei: {error}", "import.invalidMode": "Ungültiger Modus", "import.invalidParams": "Ungültiger Parameter: {error}", + "import.invalidSubStatus": "Invalid subscription status", "import.listSubHelp": "Listen die abonniert werden.", "import.mode": "Modus", "import.overwrite": "Überschreiben?", diff --git a/i18n/en.json b/i18n/en.json index a5cd983..491e254 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -185,16 +185,12 @@ "import.invalidDelim": "Delimiter should be a single character.", "import.invalidFile": "Invalid file: {error}", "import.invalidMode": "Invalid mode", - "import.invalidSubscriptionStatus": "Invalid subscription status", "import.invalidParams": "Invalid params: {error}", + "import.invalidSubStatus": "Invalid subscription status", "import.listSubHelp": "Lists to subscribe to.", "import.mode": "Mode", - "import.subscriptionStatus": "Subscription Status", - "import.confirmed": "Confirmed", - "import.unconfirmed": "Unconfirmed", - "import.unsubscribed": "Unsubscribed", "import.overwrite": "Overwrite?", - "import.overwriteHelp": "Overwrite name and attribs of existing subscribers?", + "import.overwriteHelp": "Overwrite name, attribs, subscription status of existing subscribers?", "import.recordsCount": "{num} / {total} records", "import.stopImport": "Stop import", "import.subscribe": "Subscribe", diff --git a/i18n/es.json b/i18n/es.json index 477eac7..f107c8c 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -186,6 +186,7 @@ "import.invalidFile": "Archivo inválido: {error}", "import.invalidMode": "Modo inválido", "import.invalidParams": "Paramétros inválidos: {error}", + "import.invalidSubStatus": "Invalid subscription status", "import.listSubHelp": "Listas a subscribir", "import.mode": "Modo", "import.overwrite": "¿Sobre-escribir?", diff --git a/i18n/fr.json b/i18n/fr.json index f6b9289..651600c 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -186,6 +186,7 @@ "import.invalidFile": "Fichier non valide : {error}", "import.invalidMode": "Mode invalide", "import.invalidParams": "Paramètres non valides : {error}", + "import.invalidSubStatus": "Invalid subscription status", "import.listSubHelp": "Abonner aux listes", "import.mode": "Mode", "import.overwrite": "Écraser ?", diff --git a/i18n/it.json b/i18n/it.json index 6a4fbe8..3dbb4e2 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -186,6 +186,7 @@ "import.invalidFile": "File non valido: {error}", "import.invalidMode": "Modalità non valida", "import.invalidParams": "Parametri non validi: {error}", + "import.invalidSubStatus": "Invalid subscription status", "import.listSubHelp": "Liste a cui iscriversi.", "import.mode": "Modalità", "import.overwrite": "Sovrascrivere?", diff --git a/i18n/ml.json b/i18n/ml.json index db7cd96..5492a22 100644 --- a/i18n/ml.json +++ b/i18n/ml.json @@ -186,6 +186,7 @@ "import.invalidFile": " ഫയൽ അസാധുവാണ് : {error}", "import.invalidMode": "ശൈലി അസാധുവാണ്", "import.invalidParams": "പരാമുകൾ അസാധുവാണ്: {error}", + "import.invalidSubStatus": "Invalid subscription status", "import.listSubHelp": "വരിക്കാരനാകാനുള്ള ലിസ്റ്റുകൾ.", "import.mode": "ശൈലി", "import.overwrite": "തിരുത്തിയെഴുതട്ടേ?", diff --git a/i18n/pl.json b/i18n/pl.json index e89fdd8..6854046 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -186,6 +186,7 @@ "import.invalidFile": "Nieprawidłowy plik: {error}", "import.invalidMode": "Nieprawidłowy tryp", "import.invalidParams": "Nieprawidłowe parametry: {error}", + "import.invalidSubStatus": "Invalid subscription status", "import.listSubHelp": "Listy do subskrybowania.", "import.mode": "Tryb", "import.overwrite": "Nadpisać?", diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index be1e2b8..4ec666b 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -186,6 +186,7 @@ "import.invalidFile": "Arquivo inválido: {error}", "import.invalidMode": "Modo inválido", "import.invalidParams": "Parâmetros inválidos: {error}", + "import.invalidSubStatus": "Invalid subscription status", "import.listSubHelp": "Listas para inscrever.", "import.mode": "Modo", "import.overwrite": "Sobrescrever?", diff --git a/i18n/pt.json b/i18n/pt.json index 4a12eb2..2d3eb52 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -186,6 +186,7 @@ "import.invalidFile": "Ficheiro inválido: {error}", "import.invalidMode": "Modo inválido", "import.invalidParams": "Parâmetros inválidos: {error}", + "import.invalidSubStatus": "Invalid subscription status", "import.listSubHelp": "Listas a subscrever.", "import.mode": "Modo", "import.overwrite": "Sobrescrever?", diff --git a/i18n/ru.json b/i18n/ru.json index 8ff8899..3e94368 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -186,6 +186,7 @@ "import.invalidFile": "Неверный файл: {error}", "import.invalidMode": "Неверный режим", "import.invalidParams": "Неверные параметры: {error}", + "import.invalidSubStatus": "Invalid subscription status", "import.listSubHelp": "Списки для подписки.", "import.mode": "Режим", "import.overwrite": "Перезаписать?", diff --git a/i18n/tr.json b/i18n/tr.json index 24c06d7..2661d3d 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -1,437 +1,441 @@ -{ - "_.code": "tr", - "_.name": "Turkish (tr)", - "admin.errorMarshallingConfig": "Ayarlar ile ilgili hata: {error}", - "campaigns.addAltText": "Alternatif düz metin ekleyin", - "campaigns.cantUpdate": "Gönderilmekte olan veya gönderilmiş kampaynalar güncellenemez.", - "campaigns.clicks": "Tıklama", - "campaigns.confirmDelete": "Sil {name}", - "campaigns.confirmSchedule": "Bu kampanya belirtilen tarihte otomatik olarak başlar. Şimdi ayarla?", - "campaigns.confirmSwitchFormat": "İçerik düzenini yitirebilir. Devam et?", - "campaigns.content": "İçerik", - "campaigns.contentHelp": "İçerik buraya", - "campaigns.continue": "Devam et", - "campaigns.copyOf": "{name} - Kopyası", - "campaigns.dateAndTime": "Tarih ve saat", - "campaigns.ended": "Bitti", - "campaigns.errorSendTest": "Test gönderirken hata: {error}", - "campaigns.fieldInvalidBody": "Kampanya gövdesini oluşturma hatası: {error}", - "campaigns.fieldInvalidFromEmail": "Yanlış `from_email`.", - "campaigns.fieldInvalidListIDs": "Yanlış liste ID'leri.", - "campaigns.fieldInvalidMessenger": "Bilinmeyen mesajcı {name}.", - "campaigns.fieldInvalidName": "İsim uzunluğu yanlış.", - "campaigns.fieldInvalidSendAt": "Tanımlanan tarih gelecekte olmalı.", - "campaigns.fieldInvalidSubject": "Konu uzunluğu yanlış verilmiş.", - "campaigns.fromAddress": "Gelen adres", - "campaigns.fromAddressPlaceholder": "isminiz ", - "campaigns.invalid": "Yanlış tanımlı kapmanya", - "campaigns.needsSendAt": "Kampanya için tanımlanmış bir tarih gerekli.", - "campaigns.newCampaign": "Yeni kampanya", - "campaigns.noKnownSubsToTest": "Test için bilinen üye yok.", - "campaigns.noOptinLists": "Kampanya oluşturmak için opt-in liste bulunmuyor.", - "campaigns.noSubs": "Seçilmiş listelerin içinde kampanya oluşturmak için üye bulunmuyor.", - "campaigns.noSubsToTest": "Hedeflenen üye bulunmuyor.", - "campaigns.notFound": "Kampanya bulunamadı.", - "campaigns.onlyActiveCancel": "Sadece aktif kampanyalar iptal edilebilir.", - "campaigns.onlyActivePause": "Sadece aktif kampanyalar duraklatılabilir.", - "campaigns.onlyDraftAsScheduled": "Sadece taslak kampanyalar zamanlanabilir.", - "campaigns.onlyPausedDraft": "Sadece duraklatılan ve taslak kampanyalar başlatılabilir.", - "campaigns.onlyScheduledAsDraft": "Sadece başlatılmış kampanyalar taslak olarak kaydedilebilir.", - "campaigns.pause": "Duraklat", - "campaigns.plainText": "Düz yazı", - "campaigns.preview": "Önizleme", - "campaigns.progress": "İlerleme durumu", - "campaigns.queryPlaceholder": "İsim veya konu", - "campaigns.rawHTML": "Ham HTML", - "campaigns.removeAltText": "Alternatif düz yazıyı kaldır", - "campaigns.richText": "Zengin metin", - "campaigns.schedule": "Kampanya'yı zamanla", - "campaigns.scheduled": "Zamanlandı", - "campaigns.send": "Gönder", - "campaigns.sendLater": "Sonra gönder", - "campaigns.sendTest": "Test mesajı gönder", - "campaigns.sendTestHelp": "Birden fazla alıcı eklemek için adresi yazdıktan sonra enter tuşuna bas. Adresler mevcut üyelere ait olmalıdır.", - "campaigns.sendToLists": "Gönderilecek listeler", - "campaigns.sent": "Gönder", - "campaigns.start": "Kampanya başlat", - "campaigns.started": "\"{name}\" başlatıldı", - "campaigns.startedAt": "Başlatıldı", - "campaigns.stats": "Durum", - "campaigns.status.cancelled": "İptal edildi", - "campaigns.status.draft": "Taslak", - "campaigns.status.finished": "Sonlandı", - "campaigns.status.paused": "Duraklatıldı", - "campaigns.status.running": "İlerliyor", - "campaigns.status.scheduled": "Zamanlandı", - "campaigns.statusChanged": "\"{name}\" durumu {status}", - "campaigns.subject": "Konu", - "campaigns.testEmails": "E-postalar", - "campaigns.testSent": "Test mesajı gönderildi", - "campaigns.timestamps": "Zaman etiketi", - "campaigns.views": "Görüntülenme", - "dashboard.campaignViews": "Kampanya görüntülenme Sayısı", - "dashboard.linkClicks": "Linklerin tıklanması", - "dashboard.messagesSent": "Mesaj gönderildi", - "dashboard.orphanSubs": "Sahipsiz", - "email.data.info": "Hakkınızda üretilmiş tüm veri JSON formatında bir dosya olarak eklendi. Bir meti düzenleyici ile görüntüleyebilirsiniz.", - "email.data.title": "Sizin veriniz", - "email.optin.confirmSub": "Üyeliği onaylayınız", - "email.optin.confirmSubHelp": "Aşağıdaki düğmeyi tıklayarak Üyeliği onaylayınız.", - "email.optin.confirmSubInfo": "Buradaki listelere eklendiniz:", - "email.optin.confirmSubTitle": "Üyeliği doğrulayınız", - "email.optin.confirmSubWelcome": "Merhaba", - "email.optin.privateList": "Kişisel liste", - "email.status.campaignReason": "Sebep", - "email.status.campaignSent": "Gönderilmiş", - "email.status.campaignUpdateTitle": "Kampanya güncelle", - "email.status.importFile": "Dosya", - "email.status.importRecords": "Kayıtlar", - "email.status.importTitle": "Güncellemeyi içe aktar", - "email.status.status": "Durum", - "email.unsub": "Üyeliği sonlandır", - "email.unsubHelp": "Bu e-posta'ları almak istemiyorum", - "forms.formHTML": "HTML Formu", - "forms.formHTMLHelp": "Harici bir web sayfasında bir abonelik formu göstermek için aşağıdaki HTML'yi kullanın. Formda e-posta alanı ve bir veya daha fazla `l` (liste UUID) alanı bulunmalıdır. `İsim` alanı isteğe bağlıdır.", - "forms.noPublicLists": "Form'a ihtiyaç duyulan erişime açık listeler yok.", - "forms.publicLists": "Erişime açık listeler", - "forms.publicSubPage": "Erişime açık üyelik sayfası", - "forms.selectHelp": "Form içine eklenecek listeleri seç.", - "forms.title": "Formlar", - "globals.buttons.add": "Ekle", - "globals.buttons.addNew": "Ekle yeni", - "globals.buttons.cancel": "İptal", - "globals.buttons.clone": "Klonla", - "globals.buttons.close": "Kapat", - "globals.buttons.continue": "Devam et", - "globals.buttons.delete": "Sil", - "globals.buttons.edit": "Değiştir", - "globals.buttons.enabled": "Etkinleştirildi", - "globals.buttons.learnMore": "Daha fazla öğren", - "globals.buttons.new": "Yeni", - "globals.buttons.ok": "Ok", - "globals.buttons.remove": "Çıkart", - "globals.buttons.save": "Kaydet", - "globals.buttons.saveChanges": "Kaydet değişiklik", - "globals.days.0": "Paz", - "globals.days.1": "Pts", - "globals.days.2": "Sal", - "globals.days.3": "Çar", - "globals.days.4": "Per", - "globals.days.5": "Cum", - "globals.days.6": "Cts", - "globals.fields.createdAt": "Yaratılma", - "globals.fields.id": "ID", - "globals.fields.name": "İsim", - "globals.fields.status": "Durum", - "globals.fields.type": "Tip", - "globals.fields.updatedAt": "Güncelleme", - "globals.fields.uuid": "UUID", - "globals.messages.confirm": "Eminmisiniz?", - "globals.messages.created": "\"{name}\" oluşturma", - "globals.messages.deleted": "\"{name}\" silme", - "globals.messages.emptyState": "Burası Boş", - "globals.messages.errorCreating": "Hata oluşturma {name}: {error}", - "globals.messages.errorDeleting": "Hata silme {name}: {error}", - "globals.messages.errorFetching": "Hata çağırırken {name}: {error}", - "globals.messages.errorUUID": "Hata oluştururken UUID: {error}", - "globals.messages.errorUpdating": "Hata güncellerken {name}: {error}", - "globals.messages.invalidID": "Yanlış ID", - "globals.messages.invalidUUID": "Yanlış UUID", - "globals.messages.notFound": "{name} bulunamadı", - "globals.messages.passwordChange": "Değiştirmek için değer gir", - "globals.messages.updated": "\"{name}\" güncellendi", - "globals.months.1": "Ock", - "globals.months.10": "Eki", - "globals.months.11": "Kas", - "globals.months.12": "Ara", - "globals.months.2": "Şub", - "globals.months.3": "Mar", - "globals.months.4": "Nis", - "globals.months.5": "May", - "globals.months.6": "Haz", - "globals.months.7": "Tem", - "globals.months.8": "Aug", - "globals.months.9": "Eyl", - "globals.terms.campaign": "Kampanya | Kampanyalar", - "globals.terms.campaigns": "Kampanyalar", - "globals.terms.dashboard": "Yönetim Paneli", - "globals.terms.list": "Liste | Listeler", - "globals.terms.lists": "Listeler", - "globals.terms.media": "Medya | Medya", - "globals.terms.messenger": "Messengelar | Messengerlar", - "globals.terms.messengers": "Messengerlar", - "globals.terms.settings": "Ayarlar", - "globals.terms.subscriber": "Üye | Üyeler", - "globals.terms.subscribers": "Üyeler", - "globals.terms.tag": "Tag | Tag(lar)", - "globals.terms.tags": "Tag(lar)", - "globals.terms.template": "Taslak | Taslaklar", - "globals.terms.templates": "Taslaklar", - "import.alreadyRunning": "Bir içe aktarım halen sürüyor. Yeniden denemek için durdurun veya yeniden denemek için bekleyin.", - "import.blocklist": "Engelli listesi", - "import.csvDelim": "CSV ayıracı", - "import.csvDelimHelp": "Varsayılan ayıraç virgüldür.", - "import.csvExample": "Örnek ham CSV dosyası", - "import.csvFile": "CSV veya ZIP dosyası", - "import.csvFileHelp": "Buraya CSV veya Zip dosyası bırak veya tıkla", - "import.errorCopyingFile": "Hata, dosya kopyalamrken: {error}", - "import.errorProcessingZIP": "Hata, zip dosyası işleme: {error}", - "import.errorStarting": "Hata, içeri aktarım başlama: {error}", - "import.importDone": "Bitti", - "import.importStarted": "İçeri aktarım başladı", - "import.instructions": "Kullanım talimatı", - "import.instructionsHelp": "Toplu üyeleri yükleyebilmek için bir CSV dosyası veya CSV dosyası içeren bir ZIP dosyası yükleyiniz. CSV dosyasının aynen buradaki isimlere sahip başlıklara sahip olması gerekir. attributes (seçime bağlı) verisi çift tırnak ile verilerin tanımlandığı gerçerli bir JSON olmalıdır.", - "import.invalidDelim": "Ayıraç tek bir karakter olmalı.", - "import.invalidFile": "Hatalı dosya: {error}", - "import.invalidMode": "Hatalı mod", - "import.invalidParams": "Hatalı parametre: {error}", - "import.listSubHelp": "Üye olunacak listeler.", - "import.mode": "Mod", - "import.overwrite": "Üzerine yaz?", - "import.overwriteHelp": "İsim ve attribs parametrelerini var olan üyelerin üzerine yaz?", - "import.recordsCount": "{num} / {total} kayıt", - "import.stopImport": "İçeri aktarmayı durdur", - "import.subscribe": "Üye ol", - "import.title": "Üyeleri içeri aktar", - "import.upload": "Yükle", - "lists.confirmDelete": "Eminmisiniz? Bu işlem üyeleri silmeyecek.", - "lists.confirmSub": "{name} için üyelik(leri) doğrula", - "lists.invalidName": "Yanlış isim", - "lists.newList": "Yeni liste", - "lists.optin": "Opt-in", - "lists.optinHelp": "Çifte opt-in üyelerin doğrulanması için e-posta gönderir. Çifte opt-in listelerde, kampanyalar sadece doğrulanan üyelere gönderilir.", - "lists.optinTo": "{name} için opt-in", - "lists.optins.double": "Çifte opt-in", - "lists.optins.single": "Tek opt-in", - "lists.sendCampaign": "Kampanyayı gönder", - "lists.sendOptinCampaign": "opt-in kampanyasını gönder", - "lists.type": "Tip", - "lists.typeHelp": "Erişime açık listelere heryerden erişilebilirdir ve üye olunabilir. Ayrıca üyelik yönetim sayfaları internet üzerinden erişime açık yerlerdir.", - "lists.types.private": "Kişisel", - "lists.types.public": "Erişime açık", - "logs.title": "Loglar", - "media.errorReadingFile": "Hata, dosya okurken: {error}", - "media.errorResizing": "Hata, resim büyüklüğü değişirken: {error}", - "media.errorSavingThumbnail": "Hata, önizleme oluşurken: {error}", - "media.errorUploading": "Hata, dosya yüklerken: {error}", - "media.invalidFile": "Hatalı dosya: {error}", - "media.title": "Medya", - "media.unsupportedFileType": "Desteklenmeyen dosya tipi ({type})", - "media.upload": "Yükleme", - "media.uploadHelp": "Bir veya daha fazla resmi buraya bırak veya tıkla", - "media.uploadImage": "Resim yükle", - "menu.allCampaigns": "Tüm kampanyalar", - "menu.allLists": "Tüm listeler", - "menu.allSubscribers": "Tüm üyeler", - "menu.dashboard": "Yönetim paneli", - "menu.forms": "Formlar", - "menu.import": "İçeri aktar", - "menu.logs": "Loglar", - "menu.media": "Medya", - "menu.newCampaign": "Yeni oluştur", - "menu.settings": "Ayarlar", - "public.campaignNotFound": "E-posta mesajı bulunamadı.", - "public.confirmOptinSubTitle": "Üyeliği doğrula", - "public.confirmSub": "Üyeliği doğrula", - "public.confirmSubInfo": "Buradaki listeler içersine eklendiniz:", - "public.confirmSubTitle": "Doğrula", - "public.dataRemoved": "Tüm üyelikleriniz ve size ait olan tüm veriler silinmiştir.", - "public.dataRemovedTitle": "Veri silindi", - "public.dataSent": "Size ait olan bilgiler size e-posta olarak gönderilmiştir.", - "public.dataSentTitle": "Veri e-posta olarak gönderildi.", - "public.errorFetchingCampaign": "Hata, e-posta getirilirken.", - "public.errorFetchingEmail": "E-posta mesajı bulunamadı", - "public.errorFetchingLists": "Listeleri getirme hatası. Lütfen tekrarla.", - "public.errorProcessingRequest": "İstek işleme hatası. Lütfen tekrarla.", - "public.errorTitle": "Hata", - "public.invalidFeature": "Bu özellik geçerli değil.", - "public.invalidLink": "Geçersiz link", - "public.noListsAvailable": "Eklenecek liste yok.", - "public.noListsSelected": "Bağlanılacak geçerli bir liste seçilmedi.", - "public.noSubInfo": "Doğrulanacak üyelik bulunmuyor.", - "public.noSubTitle": "Üyelik yok", - "public.notFoundTitle": "Bulunamadı", - "public.privacyConfirmWipe": "Tüm üyelik verilerinizin kalıcı olarak silinmesini istediğinize eminmisiniz?", - "public.privacyExport": "Verinizi dışarı aktarın", - "public.privacyExportHelp": "Size ait verilerin bir kopyası size e-posta ile gönderilecektir.", - "public.privacyTitle": "Kişisel veriler", - "public.privacyWipe": "Veriyi tamamen temizle", - "public.privacyWipeHelp": "Tüm üyeliklerinizi ve ilişkili verilerinizi veritabanından silin.", - "public.sub": "Üyelik", - "public.subConfirmed": "Başarıyla üye olundu.", - "public.subOptinPending": "Üyelik doğrulaması için bir e-posta gönderilmiştir.", - "public.subConfirmedTitle": "Doğrulanmıştır", - "public.subName": "İsim (opsiyonel)", - "public.subNotFound": "Üyelik bulunamadı.", - "public.subPrivateList": "Kişisel liste", - "public.subTitle": "Üye ol", - "public.unsub": "Üyelikten ayrıl", - "public.unsubFull": "Gelecekte gelecek tüm e-postalar dahil üyeliği sonlandır.", - "public.unsubHelp": "Bu e-posta listesinden ayrılmayı istermisiniz?", - "public.unsubTitle": "Üyelikten ayrıl", - "public.unsubbedInfo": "Başarı ile üyeliğinizi bitirdiniz.", - "public.unsubbedTitle": "Üyelik bitirildi.", - "public.unsubscribeTitle": "e-posta listesi üyeliğini bitir", - "settings.needsRestart": "Ayarlar değişti. Çalışan tüm kampanyaları durdur ve uygulamayı yeniden başlat.", - "settings.confirmRestart": "Çalışan kampanyaların duraklatıldığından emin ol. Yeniden başlat?", - "settings.updateAvailable": "Yeni bir güncel sürüm {version} mevcuttur.", - "settings.restart": "Yeniden başlat", - "settings.duplicateMessengerName": "Çoklanmış messenger ismi: {name}", - "settings.errorEncoding": "Hatalı kodlama ayarları: {error}", - "settings.errorNoSMTP": "En azından bir SMTP bloğu etkin olmalı", - "settings.general.adminNotifEmails": "Yönetici e-posta bildirimleri", - "settings.general.adminNotifEmailsHelp": "İçe aktarma güncellemeleri, kampanya tamamlama, başarısızlık gibi yönetici bildirimlerinin gönderilmesi gereken e-posta adreslerinin virgülle ayrılmış listesi.", - "settings.general.enablePublicSubPage": "Erişime açık üyelik sayfasını etkinleştir", - "settings.general.enablePublicSubPageHelp": "Kişilerin abone olması için tüm genel listeleri içeren genel bir abonelik sayfası gösterin.", - "settings.general.faviconURL": "Favicon URL", - "settings.general.faviconURLHelp": "(İsteğe bağlı) abonelik iptal sayfası gibi kullanıcıya bakan görünümde görüntülenecek statik faviconun tam URL'si.", - "settings.general.fromEmail": "Varsayılan `gelen` e-postası", - "settings.general.fromEmailHelp": "Varsayılan `gelen` e-postası, tüm gönderilen kampanyalarda gösterilecek. Her kampanya için değiştirilebilir.", - "settings.general.language": "Dil", - "settings.general.logoURL": "Logo URL", - "settings.general.logoURLHelp": "(İsteğe bağlı) abonelik iptal sayfası gibi kullanıcıya bakan görünümde görüntülenecek statik logonun tam URL'si.", - "settings.general.name": "Genel", - "settings.general.rootURL": "Kök URL", - "settings.general.rootURLHelp": "Kurulumun genel URL'si (bölme çizgisi yok).", - "settings.invalidMessengerName": "Geçersiz messenger adı.", - "settings.media.provider": "Sağlayıcı", - "settings.media.s3.bucket": "Bucket", - "settings.media.s3.bucketPath": "Bucket yolu", - "settings.media.s3.bucketPathHelp": "Dosyaları yüklemek için paketin içindeki yol. Varsayılan /", - "settings.media.s3.bucketType": "Bucket tipi", - "settings.media.s3.bucketTypePrivate": "Özel", - "settings.media.s3.bucketTypePublic": "Erişime açık", - "settings.media.s3.key": "AWS erişim anahtarı", - "settings.media.s3.region": "Bölge", - "settings.media.s3.secret": "AWS erişim şifresi(secret)", - "settings.media.s3.uploadExpiry": "Yükleme sona erme", - "settings.media.s3.uploadExpiryHelp": "(İsteğe bağlı) Oluşturulan önceden imzalanmış URL için TTL'yi (saniye cinsinden) belirtin. Yalnızca özel paketler için geçerlidir (saniye, dakika, saat, gün için s, m, h, d).", - "settings.media.title": "Medya yüklemeleri", - "settings.media.upload.path": "Yükleme yolu", - "settings.media.upload.pathHelp": "Medyanın yükleneceği dizinin yolu.", - "settings.media.upload.uri": "Yüklwmw URI si", - "settings.media.upload.uriHelp": "Dış dünya tarafından görülebilen URI'yi yükleyin. Upload_path'e yüklenen medyaya {root_url} altından herkese açık erişime sahip olacak, örneğin https://www.siteniz.com/uploads.", - "settings.messengers.maxConns": "Maksimum bağlantı", - "settings.messengers.maxConnsHelp": "Sunucuya maksimum çoklu bağlantı.", - "settings.messengers.messageDiscard": "Değişiklikleri yoksay?", - "settings.messengers.messageSaved": "Ayarlar kaydedildi. Uygulama yeniden yükleniyor ...", - "settings.messengers.name": "Messengerlar", - "settings.messengers.nameHelp": "örn.: my-sms. Alpfanumerik / bölü.", - "settings.messengers.password": "Parola", - "settings.messengers.retries": "Tekrarlama", - "settings.messengers.retriesHelp": "Bir mesaj başarısız olduğunda yeniden deneme sayısı.", - "settings.messengers.skipTLSHelp": "TLS sertifikasında ana bilgisayar adı kontrolünü atlayın.", - "settings.messengers.timeout": "Boşta zaman aşımı", - "settings.messengers.timeoutHelp": "Bir bağlantıdaki yeni etkinliği kapatmadan ve havuzdan kaldırmadan önce bekleme süresi (s saniye, m dakika).", - "settings.messengers.url": "URL", - "settings.messengers.urlHelp": "Postback sunusucu için kök URL.", - "settings.messengers.username": "Kullanıcı adı", - "settings.performance.batchSize": "Batch büyüklüğü", - "settings.performance.batchSizeHelp": "Veritabanından tek bir yinelemede çekilecek abone sayısı. Her yineleme, aboneleri veritabanından çeker, onlara mesajlar gönderir ve ardından bir sonraki grubu çekmek için bir sonraki yinelemeye geçer. Bu, ideal olarak elde edilebilecek maksimum iş hacminden (eşzamanlılık * ileti_ hızı) daha yüksek olmalıdır.", - "settings.performance.concurrency": "Çoklu bağlantı", - "settings.performance.concurrencyHelp": "Aynı anda ileti göndermeyi deneyecek maksimum eşzamanlı worker (thread) sayısı.", - "settings.performance.maxErrThreshold": "Maksimum hata eşiği", - "settings.performance.maxErrThresholdHelp": "The number of errors (eg: SMTP timeouts while e-mailing) a running campaign should tolerate before it is paused for manual investigation or intervention. Set to 0 to never pause.", - "settings.performance.messageRate": "Mesaj oranı", - "settings.performance.messageRateHelp": "Çalışan başına saniyede bir saniyede gönderilecek maksimum mesaj sayısı. Concurrency = 10 ve message_rate = 10 ise, her saniye 10x10 = 100'e kadar mesaj gönderilebilir. Bu, eşzamanlılık ile birlikte, net mesajların saniyede dışarı çıkmasını hedef mesaj sunucularının hız limitlerinin altında tutmak için ince ayar yapılmalıdır.", - "settings.performance.name": "Performans", - "settings.performance.slidingWindow": "Kayan pencere sınırını etkinleştir", - "settings.performance.slidingWindowDuration": "Süre", - "settings.performance.slidingWindowDurationHelp": "Kayar pencere periyodunun süresi (dakika için m, saat için h).", - "settings.performance.slidingWindowHelp": "Belirli bir süre içinde gönderilen toplam ileti sayısını sınırlayın. Bu sınıra ulaşıldığında, mesajların gönderimi zaman penceresi temizlenene kadar bekletilir.", - "settings.performance.slidingWindowRate": "Maksimum. mesaj", - "settings.performance.slidingWindowRateHelp": "Maximum number of messages to send within the window duration.", - "settings.privacy.allowBlocklist": "Liste bloklama izini ver", - "settings.privacy.allowBlocklistHelp": "Abonelerin tüm posta listelerinden çıkmalarına ve kendilerini engellenmiş olarak işaretlemelerine izin verin?", - "settings.privacy.allowExport": "Dışa aktarım için izin ver", - "settings.privacy.allowExportHelp": "Abonelerin üzerlerinde toplanan verileri dışa aktarmalarına izin verin?", - "settings.privacy.allowWipe": "Silmek için izin ver", - "settings.privacy.allowWipeHelp": "Abonelerin, abonelikleri ve veritabanındaki diğer tüm veriler dahil olmak üzere kendilerini silmesine izin verin. Kampanya görüntülemeleri ve bağlantı tıklamaları da, görünümler ve tıklama sayıları kalır (bunlarla ilişkilendirilmiş abone olmadan), böylece istatistikler ve analizler etkilenmez.", - "settings.privacy.individualSubTracking": "Bireysel üye takibi", - "settings.privacy.individualSubTrackingHelp": "Abone düzeyinde kampanya görüntülemelerini ve tıklamalarını izleyin. Devre dışı bırakıldığında, bireysel abonelere bağlanmadan görüntüleme ve tıklama izleme devam eder.", - "settings.privacy.listUnsubHeader": " `List-Unsubscribe` Başlık bilgisini ekle", - "settings.privacy.listUnsubHeaderHelp": "E-posta istemcilerinin kullanıcıların tek bir tıklamayla abonelikten çıkmalarına olanak tanıyan abonelik iptal başlıklarını ekleyin.", - "settings.privacy.name": "Gizlilik", - "settings.smtp.authProtocol": "Protokol", - "settings.smtp.customHeaders": "Özel başlık bilgisi", - "settings.smtp.customHeadersHelp": "Bu sunucudan gönderilen tüm iletilere eklenecek isteğe bağlı e-posta başlıkları dizisi. Örnek: [{\"X-Custom\": \"value\"}, {\"X-Custom2\": \"value\"}]", - "settings.smtp.enabled": "Etkinleştirildi", - "settings.smtp.heloHost": "HELO İstemci adı", - "settings.smtp.heloHostHelp": "Opsiyonel. Bazı SMTP sunucuları istemci adı olarak FQDN isterler. Varsayılan olarak, 'localhost' üzerine HELLO gönderilecektir. Farklı bir sunucu adı kullanılacaksa tanımlayın lütfen.", - "settings.smtp.host": "İstemci", - "settings.smtp.hostHelp": "SMTP sunucusu adresi.", - "settings.smtp.idleTimeout": "Idle süresi", - "settings.smtp.idleTimeoutHelp": "Bir bağlantıdaki yeni etkinliği kapatmadan ve havuzdan kaldırmadan önce bekleme süresi (s saniye, m dakika).", - "settings.smtp.maxConns": "Maks. bağ. say.", - "settings.smtp.maxConnsHelp": "SMTP sunucusuna aynı anda gönderilecek çoklu istek sayısı.", - "settings.smtp.name": "SMTP", - "settings.smtp.password": "Parola", - "settings.smtp.passwordHelp": "Değiştirmek için giriniz", - "settings.smtp.port": "Port", - "settings.smtp.portHelp": "SMTP sunucusu port numarası.", - "settings.smtp.retries": "Tekrarlama", - "settings.smtp.retriesHelp": "Mesaj hata verdiğinde tekrar deneme sayısı.", - "settings.smtp.setCustomHeaders": "Özel başlık tanımla", - "settings.smtp.skipTLS": "TLS doğrulamasını atla", - "settings.smtp.skipTLSHelp": "TLS sertifikaları için sunucu adı doğrulamayı atla.", - "settings.smtp.tls": "TLS", - "settings.smtp.tlsHelp": "STARTTLS tanımla.", - "settings.smtp.username": "Kullanıcı adı", - "settings.smtp.waitTimeout": "Bekleme süresi aşımı", - "settings.smtp.waitTimeoutHelp": "Bir bağlantıdaki yeni etkinliği kapatmadan ve havuzdan kaldırmadan önce bekleme süresi (saniye için s, dakika için m). ", - "settings.title": "Ayarlar", - "subscribers.advancedQuery": "İleri düzey", - "subscribers.advancedQueryHelp": "Üye attributes verisini görüntülemek için SQL verisi", - "subscribers.attribs": "Attributes", - "subscribers.attribsHelp": "Attributes verisi JSON map olarak tanımlı, örnek olarak:", - "subscribers.blocklistedHelp": "Erişime engelli üyeler hiçbir zaman e-posta alamayacak.", - "subscribers.confirmBlocklist": "Erişime engelli {num} üye(leri)?", - "subscribers.confirmDelete": "Sil {num} üye(leri)?", - "subscribers.confirmExport": "Dışa aktar {num} üye(leri)?", - "subscribers.downloadData": "Veriyi indir", - "subscribers.email": "E-posta", - "subscribers.emailExists": "E-posta zaten mevcut.", - "subscribers.errorBlocklisting": "Hata, erişime engelli üyeleri gösterme: {error}", - "subscribers.errorInvalidIDs": "Bir yada daha fazla geçersiz ID: {error}", - "subscribers.errorNoIDs": "Herhangi bir ID verilmedi.", - "subscribers.errorNoListsGiven": "Liste tanımı yapılmamış.", - "subscribers.errorPreparingQuery": "Hata, üye sorgusu hazırlarken: {error}", - "subscribers.errorSendingOptin": "Hata, opt-in e-postası gönderirken.", - "subscribers.export": "Export", - "subscribers.invalidAction": "Gerçersiz aksiyon.", - "subscribers.invalidEmail": "Geçersiz e-posta.", - "subscribers.invalidJSON": "Attribute tanımı içinde geçersiz JSON.", - "subscribers.invalidName": "Hatalı isim.", - "subscribers.listChangeApplied": "Liste değişikliği uygulandı.", - "subscribers.lists": "Listeler", - "subscribers.listsHelp": "Üyelerin kendilerini sildikleri listeler silinemez.", - "subscribers.listsPlaceholder": "Üye olunacak liste", - "subscribers.manageLists": "Listeleri yönet", - "subscribers.markUnsubscribed": "Üyelikten ayrılmış olarak işaretle", - "subscribers.newSubscriber": "Yeni üye", - "subscribers.numSelected": "{num} üye(ler) seçildi", - "subscribers.optinSubject": "Üyeliği doğrula", - "subscribers.query": "Sorgu", - "subscribers.queryPlaceholder": "E-posta veya isim", - "subscribers.reset": "Sıfırla", - "subscribers.selectAll": "Select all {num}", - "subscribers.status.blocklisted": "Engellenmiş", - "subscribers.status.confirmed": "Doğrulanmış", - "subscribers.status.enabled": "Etkinleştirildi", - "subscribers.status.subscribed": "Üye olundu", - "subscribers.status.unconfirmed": "Onaylanmadı", - "subscribers.status.unsubscribed": "Üyeliği sonlandı", - "subscribers.subscribersDeleted": "{num} tane üye(ler) silindi", - "templates.cantDeleteDefault": "Varsayılan taslak silinemez", - "templates.default": "Varsayılan", - "templates.dummyName": "Boş kampanya", - "templates.dummySubject": "Boş kampanya konusu", - "templates.errorCompiling": "Hata, taslak oluşturulurken: {error}", - "templates.errorRendering": "Mesajı oluşturma hatası: {error}", - "templates.fieldInvalidName": "İsim için yanlış uzunluk.", - "templates.makeDefault": "Varsayılan tanımla", - "templates.newTemplate": "Yeni taslak", - "templates.placeholderHelp": "Yer tutucu {placeholder} taslak içinde sadece bir kere olmalıdır.", - "templates.preview": "Önizleme", - "templates.rawHTML": "Ham HTML" -} \ No newline at end of file +{ + "_.code": "tr", + "_.name": "Turkish (tr)", + "admin.errorMarshallingConfig": "Ayarlar ile ilgili hata: {error}", + "campaigns.addAltText": "Alternatif düz metin ekleyin", + "campaigns.cantUpdate": "Gönderilmekte olan veya gönderilmiş kampaynalar güncellenemez.", + "campaigns.clicks": "Tıklama", + "campaigns.confirmDelete": "Sil {name}", + "campaigns.confirmSchedule": "Bu kampanya belirtilen tarihte otomatik olarak başlar. Şimdi ayarla?", + "campaigns.confirmSwitchFormat": "İçerik düzenini yitirebilir. Devam et?", + "campaigns.content": "İçerik", + "campaigns.contentHelp": "İçerik buraya", + "campaigns.continue": "Devam et", + "campaigns.copyOf": "{name} - Kopyası", + "campaigns.dateAndTime": "Tarih ve saat", + "campaigns.ended": "Bitti", + "campaigns.errorSendTest": "Test gönderirken hata: {error}", + "campaigns.fieldInvalidBody": "Kampanya gövdesini oluşturma hatası: {error}", + "campaigns.fieldInvalidFromEmail": "Yanlış `from_email`.", + "campaigns.fieldInvalidListIDs": "Yanlış liste ID'leri.", + "campaigns.fieldInvalidMessenger": "Bilinmeyen mesajcı {name}.", + "campaigns.fieldInvalidName": "İsim uzunluğu yanlış.", + "campaigns.fieldInvalidSendAt": "Tanımlanan tarih gelecekte olmalı.", + "campaigns.fieldInvalidSubject": "Konu uzunluğu yanlış verilmiş.", + "campaigns.fromAddress": "Gelen adres", + "campaigns.fromAddressPlaceholder": "isminiz ", + "campaigns.invalid": "Yanlış tanımlı kapmanya", + "campaigns.markdown": "Markdown", + "campaigns.needsSendAt": "Kampanya için tanımlanmış bir tarih gerekli.", + "campaigns.newCampaign": "Yeni kampanya", + "campaigns.noKnownSubsToTest": "Test için bilinen üye yok.", + "campaigns.noOptinLists": "Kampanya oluşturmak için opt-in liste bulunmuyor.", + "campaigns.noSubs": "Seçilmiş listelerin içinde kampanya oluşturmak için üye bulunmuyor.", + "campaigns.noSubsToTest": "Hedeflenen üye bulunmuyor.", + "campaigns.notFound": "Kampanya bulunamadı.", + "campaigns.onlyActiveCancel": "Sadece aktif kampanyalar iptal edilebilir.", + "campaigns.onlyActivePause": "Sadece aktif kampanyalar duraklatılabilir.", + "campaigns.onlyDraftAsScheduled": "Sadece taslak kampanyalar zamanlanabilir.", + "campaigns.onlyPausedDraft": "Sadece duraklatılan ve taslak kampanyalar başlatılabilir.", + "campaigns.onlyScheduledAsDraft": "Sadece başlatılmış kampanyalar taslak olarak kaydedilebilir.", + "campaigns.pause": "Duraklat", + "campaigns.plainText": "Düz yazı", + "campaigns.preview": "Önizleme", + "campaigns.progress": "İlerleme durumu", + "campaigns.queryPlaceholder": "İsim veya konu", + "campaigns.rawHTML": "Ham HTML", + "campaigns.removeAltText": "Alternatif düz yazıyı kaldır", + "campaigns.richText": "Zengin metin", + "campaigns.schedule": "Kampanya'yı zamanla", + "campaigns.scheduled": "Zamanlandı", + "campaigns.send": "Gönder", + "campaigns.sendLater": "Sonra gönder", + "campaigns.sendTest": "Test mesajı gönder", + "campaigns.sendTestHelp": "Birden fazla alıcı eklemek için adresi yazdıktan sonra enter tuşuna bas. Adresler mevcut üyelere ait olmalıdır.", + "campaigns.sendToLists": "Gönderilecek listeler", + "campaigns.sent": "Gönder", + "campaigns.start": "Kampanya başlat", + "campaigns.started": "\"{name}\" başlatıldı", + "campaigns.startedAt": "Başlatıldı", + "campaigns.stats": "Durum", + "campaigns.status.cancelled": "İptal edildi", + "campaigns.status.draft": "Taslak", + "campaigns.status.finished": "Sonlandı", + "campaigns.status.paused": "Duraklatıldı", + "campaigns.status.running": "İlerliyor", + "campaigns.status.scheduled": "Zamanlandı", + "campaigns.statusChanged": "\"{name}\" durumu {status}", + "campaigns.subject": "Konu", + "campaigns.testEmails": "E-postalar", + "campaigns.testSent": "Test mesajı gönderildi", + "campaigns.timestamps": "Zaman etiketi", + "campaigns.views": "Görüntülenme", + "dashboard.campaignViews": "Kampanya görüntülenme Sayısı", + "dashboard.linkClicks": "Linklerin tıklanması", + "dashboard.messagesSent": "Mesaj gönderildi", + "dashboard.orphanSubs": "Sahipsiz", + "email.data.info": "Hakkınızda üretilmiş tüm veri JSON formatında bir dosya olarak eklendi. Bir meti düzenleyici ile görüntüleyebilirsiniz.", + "email.data.title": "Sizin veriniz", + "email.optin.confirmSub": "Üyeliği onaylayınız", + "email.optin.confirmSubHelp": "Aşağıdaki düğmeyi tıklayarak Üyeliği onaylayınız.", + "email.optin.confirmSubInfo": "Buradaki listelere eklendiniz:", + "email.optin.confirmSubTitle": "Üyeliği doğrulayınız", + "email.optin.confirmSubWelcome": "Merhaba", + "email.optin.privateList": "Kişisel liste", + "email.status.campaignReason": "Sebep", + "email.status.campaignSent": "Gönderilmiş", + "email.status.campaignUpdateTitle": "Kampanya güncelle", + "email.status.importFile": "Dosya", + "email.status.importRecords": "Kayıtlar", + "email.status.importTitle": "Güncellemeyi içe aktar", + "email.status.status": "Durum", + "email.unsub": "Üyeliği sonlandır", + "email.unsubHelp": "Bu e-posta'ları almak istemiyorum", + "forms.formHTML": "HTML Formu", + "forms.formHTMLHelp": "Harici bir web sayfasında bir abonelik formu göstermek için aşağıdaki HTML'yi kullanın. Formda e-posta alanı ve bir veya daha fazla `l` (liste UUID) alanı bulunmalıdır. `İsim` alanı isteğe bağlıdır.", + "forms.noPublicLists": "Form'a ihtiyaç duyulan erişime açık listeler yok.", + "forms.publicLists": "Erişime açık listeler", + "forms.publicSubPage": "Erişime açık üyelik sayfası", + "forms.selectHelp": "Form içine eklenecek listeleri seç.", + "forms.title": "Formlar", + "globals.buttons.add": "Ekle", + "globals.buttons.addNew": "Ekle yeni", + "globals.buttons.cancel": "İptal", + "globals.buttons.clone": "Klonla", + "globals.buttons.close": "Kapat", + "globals.buttons.continue": "Devam et", + "globals.buttons.delete": "Sil", + "globals.buttons.edit": "Değiştir", + "globals.buttons.enabled": "Etkinleştirildi", + "globals.buttons.learnMore": "Daha fazla öğren", + "globals.buttons.new": "Yeni", + "globals.buttons.ok": "Ok", + "globals.buttons.remove": "Çıkart", + "globals.buttons.save": "Kaydet", + "globals.buttons.saveChanges": "Kaydet değişiklik", + "globals.days.0": "Paz", + "globals.days.1": "Pts", + "globals.days.2": "Sal", + "globals.days.3": "Çar", + "globals.days.4": "Per", + "globals.days.5": "Cum", + "globals.days.6": "Cts", + "globals.fields.createdAt": "Yaratılma", + "globals.fields.id": "ID", + "globals.fields.name": "İsim", + "globals.fields.status": "Durum", + "globals.fields.type": "Tip", + "globals.fields.updatedAt": "Güncelleme", + "globals.fields.uuid": "UUID", + "globals.messages.confirm": "Eminmisiniz?", + "globals.messages.created": "\"{name}\" oluşturma", + "globals.messages.deleted": "\"{name}\" silme", + "globals.messages.emptyState": "Burası Boş", + "globals.messages.errorCreating": "Hata oluşturma {name}: {error}", + "globals.messages.errorDeleting": "Hata silme {name}: {error}", + "globals.messages.errorFetching": "Hata çağırırken {name}: {error}", + "globals.messages.errorUUID": "Hata oluştururken UUID: {error}", + "globals.messages.errorUpdating": "Hata güncellerken {name}: {error}", + "globals.messages.invalidID": "Yanlış ID", + "globals.messages.invalidUUID": "Yanlış UUID", + "globals.messages.notFound": "{name} bulunamadı", + "globals.messages.passwordChange": "Değiştirmek için değer gir", + "globals.messages.updated": "\"{name}\" güncellendi", + "globals.months.1": "Ock", + "globals.months.10": "Eki", + "globals.months.11": "Kas", + "globals.months.12": "Ara", + "globals.months.2": "Şub", + "globals.months.3": "Mar", + "globals.months.4": "Nis", + "globals.months.5": "May", + "globals.months.6": "Haz", + "globals.months.7": "Tem", + "globals.months.8": "Aug", + "globals.months.9": "Eyl", + "globals.terms.campaign": "Kampanya | Kampanyalar", + "globals.terms.campaigns": "Kampanyalar", + "globals.terms.dashboard": "Yönetim Paneli", + "globals.terms.list": "Liste | Listeler", + "globals.terms.lists": "Listeler", + "globals.terms.media": "Medya | Medya", + "globals.terms.messenger": "Messengelar | Messengerlar", + "globals.terms.messengers": "Messengerlar", + "globals.terms.settings": "Ayarlar", + "globals.terms.subscriber": "Üye | Üyeler", + "globals.terms.subscribers": "Üyeler", + "globals.terms.tag": "Tag | Tag(lar)", + "globals.terms.tags": "Tag(lar)", + "globals.terms.template": "Taslak | Taslaklar", + "globals.terms.templates": "Taslaklar", + "import.alreadyRunning": "Bir içe aktarım halen sürüyor. Yeniden denemek için durdurun veya yeniden denemek için bekleyin.", + "import.blocklist": "Engelli listesi", + "import.csvDelim": "CSV ayıracı", + "import.csvDelimHelp": "Varsayılan ayıraç virgüldür.", + "import.csvExample": "Örnek ham CSV dosyası", + "import.csvFile": "CSV veya ZIP dosyası", + "import.csvFileHelp": "Buraya CSV veya Zip dosyası bırak veya tıkla", + "import.errorCopyingFile": "Hata, dosya kopyalamrken: {error}", + "import.errorProcessingZIP": "Hata, zip dosyası işleme: {error}", + "import.errorStarting": "Hata, içeri aktarım başlama: {error}", + "import.importDone": "Bitti", + "import.importStarted": "İçeri aktarım başladı", + "import.instructions": "Kullanım talimatı", + "import.instructionsHelp": "Toplu üyeleri yükleyebilmek için bir CSV dosyası veya CSV dosyası içeren bir ZIP dosyası yükleyiniz. CSV dosyasının aynen buradaki isimlere sahip başlıklara sahip olması gerekir. attributes (seçime bağlı) verisi çift tırnak ile verilerin tanımlandığı gerçerli bir JSON olmalıdır.", + "import.invalidDelim": "Ayıraç tek bir karakter olmalı.", + "import.invalidFile": "Hatalı dosya: {error}", + "import.invalidMode": "Hatalı mod", + "import.invalidParams": "Hatalı parametre: {error}", + "import.invalidSubStatus": "Invalid subscription status", + "import.listSubHelp": "Üye olunacak listeler.", + "import.mode": "Mod", + "import.overwrite": "Üzerine yaz?", + "import.overwriteHelp": "İsim ve attribs parametrelerini var olan üyelerin üzerine yaz?", + "import.recordsCount": "{num} / {total} kayıt", + "import.stopImport": "İçeri aktarmayı durdur", + "import.subscribe": "Üye ol", + "import.title": "Üyeleri içeri aktar", + "import.upload": "Yükle", + "lists.confirmDelete": "Eminmisiniz? Bu işlem üyeleri silmeyecek.", + "lists.confirmSub": "{name} için üyelik(leri) doğrula", + "lists.invalidName": "Yanlış isim", + "lists.newList": "Yeni liste", + "lists.optin": "Opt-in", + "lists.optinHelp": "Çifte opt-in üyelerin doğrulanması için e-posta gönderir. Çifte opt-in listelerde, kampanyalar sadece doğrulanan üyelere gönderilir.", + "lists.optinTo": "{name} için opt-in", + "lists.optins.double": "Çifte opt-in", + "lists.optins.single": "Tek opt-in", + "lists.sendCampaign": "Kampanyayı gönder", + "lists.sendOptinCampaign": "opt-in kampanyasını gönder", + "lists.type": "Tip", + "lists.typeHelp": "Erişime açık listelere heryerden erişilebilirdir ve üye olunabilir. Ayrıca üyelik yönetim sayfaları internet üzerinden erişime açık yerlerdir.", + "lists.types.private": "Kişisel", + "lists.types.public": "Erişime açık", + "logs.title": "Loglar", + "media.errorReadingFile": "Hata, dosya okurken: {error}", + "media.errorResizing": "Hata, resim büyüklüğü değişirken: {error}", + "media.errorSavingThumbnail": "Hata, önizleme oluşurken: {error}", + "media.errorUploading": "Hata, dosya yüklerken: {error}", + "media.invalidFile": "Hatalı dosya: {error}", + "media.title": "Medya", + "media.unsupportedFileType": "Desteklenmeyen dosya tipi ({type})", + "media.upload": "Yükleme", + "media.uploadHelp": "Bir veya daha fazla resmi buraya bırak veya tıkla", + "media.uploadImage": "Resim yükle", + "menu.allCampaigns": "Tüm kampanyalar", + "menu.allLists": "Tüm listeler", + "menu.allSubscribers": "Tüm üyeler", + "menu.dashboard": "Yönetim paneli", + "menu.forms": "Formlar", + "menu.import": "İçeri aktar", + "menu.logs": "Loglar", + "menu.media": "Medya", + "menu.newCampaign": "Yeni oluştur", + "menu.settings": "Ayarlar", + "public.campaignNotFound": "E-posta mesajı bulunamadı.", + "public.confirmOptinSubTitle": "Üyeliği doğrula", + "public.confirmSub": "Üyeliği doğrula", + "public.confirmSubInfo": "Buradaki listeler içersine eklendiniz:", + "public.confirmSubTitle": "Doğrula", + "public.dataRemoved": "Tüm üyelikleriniz ve size ait olan tüm veriler silinmiştir.", + "public.dataRemovedTitle": "Veri silindi", + "public.dataSent": "Size ait olan bilgiler size e-posta olarak gönderilmiştir.", + "public.dataSentTitle": "Veri e-posta olarak gönderildi.", + "public.errorFetchingCampaign": "Hata, e-posta getirilirken.", + "public.errorFetchingEmail": "E-posta mesajı bulunamadı", + "public.errorFetchingLists": "Listeleri getirme hatası. Lütfen tekrarla.", + "public.errorProcessingRequest": "İstek işleme hatası. Lütfen tekrarla.", + "public.errorTitle": "Hata", + "public.invalidFeature": "Bu özellik geçerli değil.", + "public.invalidLink": "Geçersiz link", + "public.noListsAvailable": "Eklenecek liste yok.", + "public.noListsSelected": "Bağlanılacak geçerli bir liste seçilmedi.", + "public.noSubInfo": "Doğrulanacak üyelik bulunmuyor.", + "public.noSubTitle": "Üyelik yok", + "public.notFoundTitle": "Bulunamadı", + "public.privacyConfirmWipe": "Tüm üyelik verilerinizin kalıcı olarak silinmesini istediğinize eminmisiniz?", + "public.privacyExport": "Verinizi dışarı aktarın", + "public.privacyExportHelp": "Size ait verilerin bir kopyası size e-posta ile gönderilecektir.", + "public.privacyTitle": "Kişisel veriler", + "public.privacyWipe": "Veriyi tamamen temizle", + "public.privacyWipeHelp": "Tüm üyeliklerinizi ve ilişkili verilerinizi veritabanından silin.", + "public.sub": "Üyelik", + "public.subConfirmed": "Başarıyla üye olundu.", + "public.subConfirmedTitle": "Doğrulanmıştır", + "public.subName": "İsim (opsiyonel)", + "public.subNotFound": "Üyelik bulunamadı.", + "public.subOptinPending": "Üyelik doğrulaması için bir e-posta gönderilmiştir.", + "public.subPrivateList": "Kişisel liste", + "public.subTitle": "Üye ol", + "public.unsub": "Üyelikten ayrıl", + "public.unsubFull": "Gelecekte gelecek tüm e-postalar dahil üyeliği sonlandır.", + "public.unsubHelp": "Bu e-posta listesinden ayrılmayı istermisiniz?", + "public.unsubTitle": "Üyelikten ayrıl", + "public.unsubbedInfo": "Başarı ile üyeliğinizi bitirdiniz.", + "public.unsubbedTitle": "Üyelik bitirildi.", + "public.unsubscribeTitle": "e-posta listesi üyeliğini bitir", + "settings.confirmRestart": "Çalışan kampanyaların duraklatıldığından emin ol. Yeniden başlat?", + "settings.duplicateMessengerName": "Çoklanmış messenger ismi: {name}", + "settings.errorEncoding": "Hatalı kodlama ayarları: {error}", + "settings.errorNoSMTP": "En azından bir SMTP bloğu etkin olmalı", + "settings.general.adminNotifEmails": "Yönetici e-posta bildirimleri", + "settings.general.adminNotifEmailsHelp": "İçe aktarma güncellemeleri, kampanya tamamlama, başarısızlık gibi yönetici bildirimlerinin gönderilmesi gereken e-posta adreslerinin virgülle ayrılmış listesi.", + "settings.general.checkUpdates": "Check for updates", + "settings.general.checkUpdatesHelp": "Periodically check for new app releases and notify.", + "settings.general.enablePublicSubPage": "Erişime açık üyelik sayfasını etkinleştir", + "settings.general.enablePublicSubPageHelp": "Kişilerin abone olması için tüm genel listeleri içeren genel bir abonelik sayfası gösterin.", + "settings.general.faviconURL": "Favicon URL", + "settings.general.faviconURLHelp": "(İsteğe bağlı) abonelik iptal sayfası gibi kullanıcıya bakan görünümde görüntülenecek statik faviconun tam URL'si.", + "settings.general.fromEmail": "Varsayılan `gelen` e-postası", + "settings.general.fromEmailHelp": "Varsayılan `gelen` e-postası, tüm gönderilen kampanyalarda gösterilecek. Her kampanya için değiştirilebilir.", + "settings.general.language": "Dil", + "settings.general.logoURL": "Logo URL", + "settings.general.logoURLHelp": "(İsteğe bağlı) abonelik iptal sayfası gibi kullanıcıya bakan görünümde görüntülenecek statik logonun tam URL'si.", + "settings.general.name": "Genel", + "settings.general.rootURL": "Kök URL", + "settings.general.rootURLHelp": "Kurulumun genel URL'si (bölme çizgisi yok).", + "settings.invalidMessengerName": "Geçersiz messenger adı.", + "settings.media.provider": "Sağlayıcı", + "settings.media.s3.bucket": "Bucket", + "settings.media.s3.bucketPath": "Bucket yolu", + "settings.media.s3.bucketPathHelp": "Dosyaları yüklemek için paketin içindeki yol. Varsayılan /", + "settings.media.s3.bucketType": "Bucket tipi", + "settings.media.s3.bucketTypePrivate": "Özel", + "settings.media.s3.bucketTypePublic": "Erişime açık", + "settings.media.s3.key": "AWS erişim anahtarı", + "settings.media.s3.region": "Bölge", + "settings.media.s3.secret": "AWS erişim şifresi(secret)", + "settings.media.s3.uploadExpiry": "Yükleme sona erme", + "settings.media.s3.uploadExpiryHelp": "(İsteğe bağlı) Oluşturulan önceden imzalanmış URL için TTL'yi (saniye cinsinden) belirtin. Yalnızca özel paketler için geçerlidir (saniye, dakika, saat, gün için s, m, h, d).", + "settings.media.title": "Medya yüklemeleri", + "settings.media.upload.path": "Yükleme yolu", + "settings.media.upload.pathHelp": "Medyanın yükleneceği dizinin yolu.", + "settings.media.upload.uri": "Yüklwmw URI si", + "settings.media.upload.uriHelp": "Dış dünya tarafından görülebilen URI'yi yükleyin. Upload_path'e yüklenen medyaya {root_url} altından herkese açık erişime sahip olacak, örneğin https://www.siteniz.com/uploads.", + "settings.messengers.maxConns": "Maksimum bağlantı", + "settings.messengers.maxConnsHelp": "Sunucuya maksimum çoklu bağlantı.", + "settings.messengers.messageDiscard": "Değişiklikleri yoksay?", + "settings.messengers.messageSaved": "Ayarlar kaydedildi. Uygulama yeniden yükleniyor ...", + "settings.messengers.name": "Messengerlar", + "settings.messengers.nameHelp": "örn.: my-sms. Alpfanumerik / bölü.", + "settings.messengers.password": "Parola", + "settings.messengers.retries": "Tekrarlama", + "settings.messengers.retriesHelp": "Bir mesaj başarısız olduğunda yeniden deneme sayısı.", + "settings.messengers.skipTLSHelp": "TLS sertifikasında ana bilgisayar adı kontrolünü atlayın.", + "settings.messengers.timeout": "Boşta zaman aşımı", + "settings.messengers.timeoutHelp": "Bir bağlantıdaki yeni etkinliği kapatmadan ve havuzdan kaldırmadan önce bekleme süresi (s saniye, m dakika).", + "settings.messengers.url": "URL", + "settings.messengers.urlHelp": "Postback sunusucu için kök URL.", + "settings.messengers.username": "Kullanıcı adı", + "settings.needsRestart": "Ayarlar değişti. Çalışan tüm kampanyaları durdur ve uygulamayı yeniden başlat.", + "settings.performance.batchSize": "Batch büyüklüğü", + "settings.performance.batchSizeHelp": "Veritabanından tek bir yinelemede çekilecek abone sayısı. Her yineleme, aboneleri veritabanından çeker, onlara mesajlar gönderir ve ardından bir sonraki grubu çekmek için bir sonraki yinelemeye geçer. Bu, ideal olarak elde edilebilecek maksimum iş hacminden (eşzamanlılık * ileti_ hızı) daha yüksek olmalıdır.", + "settings.performance.concurrency": "Çoklu bağlantı", + "settings.performance.concurrencyHelp": "Aynı anda ileti göndermeyi deneyecek maksimum eşzamanlı worker (thread) sayısı.", + "settings.performance.maxErrThreshold": "Maksimum hata eşiği", + "settings.performance.maxErrThresholdHelp": "The number of errors (eg: SMTP timeouts while e-mailing) a running campaign should tolerate before it is paused for manual investigation or intervention. Set to 0 to never pause.", + "settings.performance.messageRate": "Mesaj oranı", + "settings.performance.messageRateHelp": "Çalışan başına saniyede bir saniyede gönderilecek maksimum mesaj sayısı. Concurrency = 10 ve message_rate = 10 ise, her saniye 10x10 = 100'e kadar mesaj gönderilebilir. Bu, eşzamanlılık ile birlikte, net mesajların saniyede dışarı çıkmasını hedef mesaj sunucularının hız limitlerinin altında tutmak için ince ayar yapılmalıdır.", + "settings.performance.name": "Performans", + "settings.performance.slidingWindow": "Kayan pencere sınırını etkinleştir", + "settings.performance.slidingWindowDuration": "Süre", + "settings.performance.slidingWindowDurationHelp": "Kayar pencere periyodunun süresi (dakika için m, saat için h).", + "settings.performance.slidingWindowHelp": "Belirli bir süre içinde gönderilen toplam ileti sayısını sınırlayın. Bu sınıra ulaşıldığında, mesajların gönderimi zaman penceresi temizlenene kadar bekletilir.", + "settings.performance.slidingWindowRate": "Maksimum. mesaj", + "settings.performance.slidingWindowRateHelp": "Maximum number of messages to send within the window duration.", + "settings.privacy.allowBlocklist": "Liste bloklama izini ver", + "settings.privacy.allowBlocklistHelp": "Abonelerin tüm posta listelerinden çıkmalarına ve kendilerini engellenmiş olarak işaretlemelerine izin verin?", + "settings.privacy.allowExport": "Dışa aktarım için izin ver", + "settings.privacy.allowExportHelp": "Abonelerin üzerlerinde toplanan verileri dışa aktarmalarına izin verin?", + "settings.privacy.allowWipe": "Silmek için izin ver", + "settings.privacy.allowWipeHelp": "Abonelerin, abonelikleri ve veritabanındaki diğer tüm veriler dahil olmak üzere kendilerini silmesine izin verin. Kampanya görüntülemeleri ve bağlantı tıklamaları da, görünümler ve tıklama sayıları kalır (bunlarla ilişkilendirilmiş abone olmadan), böylece istatistikler ve analizler etkilenmez.", + "settings.privacy.individualSubTracking": "Bireysel üye takibi", + "settings.privacy.individualSubTrackingHelp": "Abone düzeyinde kampanya görüntülemelerini ve tıklamalarını izleyin. Devre dışı bırakıldığında, bireysel abonelere bağlanmadan görüntüleme ve tıklama izleme devam eder.", + "settings.privacy.listUnsubHeader": " `List-Unsubscribe` Başlık bilgisini ekle", + "settings.privacy.listUnsubHeaderHelp": "E-posta istemcilerinin kullanıcıların tek bir tıklamayla abonelikten çıkmalarına olanak tanıyan abonelik iptal başlıklarını ekleyin.", + "settings.privacy.name": "Gizlilik", + "settings.restart": "Yeniden başlat", + "settings.smtp.authProtocol": "Protokol", + "settings.smtp.customHeaders": "Özel başlık bilgisi", + "settings.smtp.customHeadersHelp": "Bu sunucudan gönderilen tüm iletilere eklenecek isteğe bağlı e-posta başlıkları dizisi. Örnek: [{\"X-Custom\": \"value\"}, {\"X-Custom2\": \"value\"}]", + "settings.smtp.enabled": "Etkinleştirildi", + "settings.smtp.heloHost": "HELO İstemci adı", + "settings.smtp.heloHostHelp": "Opsiyonel. Bazı SMTP sunucuları istemci adı olarak FQDN isterler. Varsayılan olarak, 'localhost' üzerine HELLO gönderilecektir. Farklı bir sunucu adı kullanılacaksa tanımlayın lütfen.", + "settings.smtp.host": "İstemci", + "settings.smtp.hostHelp": "SMTP sunucusu adresi.", + "settings.smtp.idleTimeout": "Idle süresi", + "settings.smtp.idleTimeoutHelp": "Bir bağlantıdaki yeni etkinliği kapatmadan ve havuzdan kaldırmadan önce bekleme süresi (s saniye, m dakika).", + "settings.smtp.maxConns": "Maks. bağ. say.", + "settings.smtp.maxConnsHelp": "SMTP sunucusuna aynı anda gönderilecek çoklu istek sayısı.", + "settings.smtp.name": "SMTP", + "settings.smtp.password": "Parola", + "settings.smtp.passwordHelp": "Değiştirmek için giriniz", + "settings.smtp.port": "Port", + "settings.smtp.portHelp": "SMTP sunucusu port numarası.", + "settings.smtp.retries": "Tekrarlama", + "settings.smtp.retriesHelp": "Mesaj hata verdiğinde tekrar deneme sayısı.", + "settings.smtp.setCustomHeaders": "Özel başlık tanımla", + "settings.smtp.skipTLS": "TLS doğrulamasını atla", + "settings.smtp.skipTLSHelp": "TLS sertifikaları için sunucu adı doğrulamayı atla.", + "settings.smtp.tls": "TLS", + "settings.smtp.tlsHelp": "STARTTLS tanımla.", + "settings.smtp.username": "Kullanıcı adı", + "settings.smtp.waitTimeout": "Bekleme süresi aşımı", + "settings.smtp.waitTimeoutHelp": "Bir bağlantıdaki yeni etkinliği kapatmadan ve havuzdan kaldırmadan önce bekleme süresi (saniye için s, dakika için m). ", + "settings.title": "Ayarlar", + "settings.updateAvailable": "Yeni bir güncel sürüm {version} mevcuttur.", + "subscribers.advancedQuery": "İleri düzey", + "subscribers.advancedQueryHelp": "Üye attributes verisini görüntülemek için SQL verisi", + "subscribers.attribs": "Attributes", + "subscribers.attribsHelp": "Attributes verisi JSON map olarak tanımlı, örnek olarak:", + "subscribers.blocklistedHelp": "Erişime engelli üyeler hiçbir zaman e-posta alamayacak.", + "subscribers.confirmBlocklist": "Erişime engelli {num} üye(leri)?", + "subscribers.confirmDelete": "Sil {num} üye(leri)?", + "subscribers.confirmExport": "Dışa aktar {num} üye(leri)?", + "subscribers.downloadData": "Veriyi indir", + "subscribers.email": "E-posta", + "subscribers.emailExists": "E-posta zaten mevcut.", + "subscribers.errorBlocklisting": "Hata, erişime engelli üyeleri gösterme: {error}", + "subscribers.errorInvalidIDs": "Bir yada daha fazla geçersiz ID: {error}", + "subscribers.errorNoIDs": "Herhangi bir ID verilmedi.", + "subscribers.errorNoListsGiven": "Liste tanımı yapılmamış.", + "subscribers.errorPreparingQuery": "Hata, üye sorgusu hazırlarken: {error}", + "subscribers.errorSendingOptin": "Hata, opt-in e-postası gönderirken.", + "subscribers.export": "Export", + "subscribers.invalidAction": "Gerçersiz aksiyon.", + "subscribers.invalidEmail": "Geçersiz e-posta.", + "subscribers.invalidJSON": "Attribute tanımı içinde geçersiz JSON.", + "subscribers.invalidName": "Hatalı isim.", + "subscribers.listChangeApplied": "Liste değişikliği uygulandı.", + "subscribers.lists": "Listeler", + "subscribers.listsHelp": "Üyelerin kendilerini sildikleri listeler silinemez.", + "subscribers.listsPlaceholder": "Üye olunacak liste", + "subscribers.manageLists": "Listeleri yönet", + "subscribers.markUnsubscribed": "Üyelikten ayrılmış olarak işaretle", + "subscribers.newSubscriber": "Yeni üye", + "subscribers.numSelected": "{num} üye(ler) seçildi", + "subscribers.optinSubject": "Üyeliği doğrula", + "subscribers.query": "Sorgu", + "subscribers.queryPlaceholder": "E-posta veya isim", + "subscribers.reset": "Sıfırla", + "subscribers.selectAll": "Select all {num}", + "subscribers.status.blocklisted": "Engellenmiş", + "subscribers.status.confirmed": "Doğrulanmış", + "subscribers.status.enabled": "Etkinleştirildi", + "subscribers.status.subscribed": "Üye olundu", + "subscribers.status.unconfirmed": "Onaylanmadı", + "subscribers.status.unsubscribed": "Üyeliği sonlandı", + "subscribers.subscribersDeleted": "{num} tane üye(ler) silindi", + "templates.cantDeleteDefault": "Varsayılan taslak silinemez", + "templates.default": "Varsayılan", + "templates.dummyName": "Boş kampanya", + "templates.dummySubject": "Boş kampanya konusu", + "templates.errorCompiling": "Hata, taslak oluşturulurken: {error}", + "templates.errorRendering": "Mesajı oluşturma hatası: {error}", + "templates.fieldInvalidName": "İsim için yanlış uzunluk.", + "templates.makeDefault": "Varsayılan tanımla", + "templates.newTemplate": "Yeni taslak", + "templates.placeholderHelp": "Yer tutucu {placeholder} taslak içinde sadece bir kere olmalıdır.", + "templates.preview": "Önizleme", + "templates.rawHTML": "Ham HTML" +} diff --git a/internal/subimporter/importer.go b/internal/subimporter/importer.go index 4306bb7..a016da5 100644 --- a/internal/subimporter/importer.go +++ b/internal/subimporter/importer.go @@ -46,9 +46,6 @@ const ( ModeSubscribe = "subscribe" ModeBlocklist = "blocklist" - - SubscriptionStatusUnconfirmed = "unconfirmed" - SubscriptionStatusConfirmed = "confirmed" ) // Importer represents the bulk CSV subscriber import system. @@ -75,10 +72,17 @@ type Session struct { subQueue chan SubReq log *log.Logger - mode string - subscriptionStatus string - overwrite bool - listIDs []int + opt SessionOpt +} + +// SessionOpt represents the options for an importer session. +type SessionOpt struct { + Filename string `json:"filename"` + Mode string `json:"mode"` + SubStatus string `json:"subscription_status"` + Overwrite bool `json:"overwrite"` + Delim string `json:"delim"` + ListIDs []int `json:"lists"` } // Status reporesents statistics from an ongoing import session. @@ -131,28 +135,25 @@ func New(opt Options, db *sql.DB) *Importer { // NewSession returns an new instance of Session. It takes the name // of the uploaded file, but doesn't do anything with it but retains it for stats. -func (im *Importer) NewSession(fName, mode string, subscriptionStatus string, overWrite bool, listIDs []int) (*Session, error) { +func (im *Importer) NewSession(opt SessionOpt) (*Session, error) { if im.getStatus() != StatusNone { return nil, errors.New("an import is already running") } im.Lock() im.status = Status{Status: StatusImporting, - Name: fName, + Name: opt.Filename, logBuf: bytes.NewBuffer(nil)} im.Unlock() s := &Session{ - im: im, - log: log.New(im.status.logBuf, "", log.Ldate|log.Ltime|log.Lshortfile), - subQueue: make(chan SubReq, commitBatchSize), - mode: mode, - subscriptionStatus: subscriptionStatus, - overwrite: overWrite, - listIDs: listIDs, + im: im, + log: log.New(im.status.logBuf, "", log.Ldate|log.Ltime|log.Lshortfile), + subQueue: make(chan SubReq, commitBatchSize), + opt: opt, } - s.log.Printf("processing '%s'", fName) + s.log.Printf("processing '%s'", opt.Filename) return s, nil } @@ -240,10 +241,10 @@ func (s *Session) Start() { total = 0 cur = 0 - listIDs = make(pq.Int64Array, len(s.listIDs)) + listIDs = make(pq.Int64Array, len(s.opt.ListIDs)) ) - for i, v := range s.listIDs { + for i, v := range s.opt.ListIDs { listIDs[i] = int64(v) } @@ -256,7 +257,7 @@ func (s *Session) Start() { continue } - if s.mode == ModeSubscribe { + if s.opt.Mode == ModeSubscribe { stmt = tx.Stmt(s.im.opt.UpsertStmt) } else { stmt = tx.Stmt(s.im.opt.BlocklistStmt) @@ -270,9 +271,9 @@ func (s *Session) Start() { break } - if s.mode == ModeSubscribe { - _, err = stmt.Exec(uu, sub.Email, sub.Name, sub.Attribs, listIDs, s.subscriptionStatus, s.overwrite) - } else if s.mode == ModeBlocklist { + if s.opt.Mode == ModeSubscribe { + _, err = stmt.Exec(uu, sub.Email, sub.Name, sub.Attribs, listIDs, s.opt.SubStatus, s.opt.Overwrite) + } else if s.opt.Mode == ModeBlocklist { _, err = stmt.Exec(uu, sub.Email, sub.Name, sub.Attribs) } if err != nil { diff --git a/queries.sql b/queries.sql index 408d12f..e7f7ed7 100644 --- a/queries.sql +++ b/queries.sql @@ -95,7 +95,7 @@ subs AS ( INSERT INTO subscriber_lists (subscriber_id, list_id, status) VALUES((SELECT id FROM sub), UNNEST($5::INT[]), $6) ON CONFLICT (subscriber_id, list_id) DO UPDATE - SET updated_at=NOW() + SET updated_at=NOW(), status=(CASE WHEN $7 THEN $6 ELSE subscriber_lists.status END) ) SELECT uuid, id from sub;