diff --git a/ROADMAP.md b/ROADMAP.md index 1fde191e..ed05d5e2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6,8 +6,8 @@ A high-performance, omni-channel, self-hosted customer support desk. ## Near Term - OAuth inbox support for Microsoft accounts - Done - Draft saving support for conversations - Done -- Support for mentions - WIP -- Ability to import agents in bulk - WIP +- Support for mentions - Done +- Ability to import agents in bulk - Done ## Mid Term - Full-fledged live chat widget - WIP diff --git a/cmd/inboxes.go b/cmd/inboxes.go index 9a92eaf8..86897738 100644 --- a/cmd/inboxes.go +++ b/cmd/inboxes.go @@ -4,8 +4,10 @@ import ( "encoding/json" "net/mail" "strconv" + "strings" "github.com/abhinavxd/libredesk/internal/envelope" + "github.com/abhinavxd/libredesk/internal/inbox" "github.com/abhinavxd/libredesk/internal/inbox/channel/email/oauth" "github.com/abhinavxd/libredesk/internal/inbox/channel/livechat" imodels "github.com/abhinavxd/libredesk/internal/inbox/models" @@ -56,6 +58,11 @@ func handleCreateInbox(r *fastglue.Request) error { return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), err.Error(), envelope.InputError) } + // Trim whitespace from inbox fields and config. + if err := trimInboxFields(&inbox); err != nil { + return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "config"), err.Error(), envelope.InputError) + } + createdInbox, err := app.inbox.Create(inbox) if err != nil { return sendErrorEnvelope(r, err) @@ -94,6 +101,11 @@ func handleUpdateInbox(r *fastglue.Request) error { return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "{globals.terms.request}"), err.Error(), envelope.InputError) } + // Trim whitespace from inbox fields and config. + if err := trimInboxFields(&inbox); err != nil { + return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.errorParsing", "name", "config"), err.Error(), envelope.InputError) + } + if err := validateInbox(app, inbox); err != nil { return sendErrorEnvelope(r, err) } @@ -278,3 +290,49 @@ func validateEmailConfig(app *App, configJSON json.RawMessage) error { return nil } + +// trimInboxFields trims whitespace from inbox fields and its email config if applicable. +func trimInboxFields(inb *imodels.Inbox) error { + inb.Name = strings.TrimSpace(inb.Name) + inb.From = strings.TrimSpace(inb.From) + + // Trim email config fields if this is an email channel. + if inb.Channel == inbox.ChannelEmail && len(inb.Config) > 0 { + var cfg imodels.Config + if err := json.Unmarshal(inb.Config, &cfg); err != nil { + return err + } + trimEmailConfig(&cfg) + trimmedConfig, err := json.Marshal(cfg) + if err != nil { + return err + } + inb.Config = trimmedConfig + } + return nil +} + +// trimEmailConfig trims whitespace from email configuration fields. +// Passwords and secrets are intentionally NOT trimmed. +func trimEmailConfig(cfg *imodels.Config) { + // Trim IMAP configs. + for i := range cfg.IMAP { + cfg.IMAP[i].Host = strings.TrimSpace(cfg.IMAP[i].Host) + cfg.IMAP[i].Username = strings.TrimSpace(cfg.IMAP[i].Username) + cfg.IMAP[i].Mailbox = strings.TrimSpace(cfg.IMAP[i].Mailbox) + } + + // Trim SMTP configs. + for i := range cfg.SMTP { + cfg.SMTP[i].Host = strings.TrimSpace(cfg.SMTP[i].Host) + cfg.SMTP[i].Username = strings.TrimSpace(cfg.SMTP[i].Username) + cfg.SMTP[i].HelloHostname = strings.TrimSpace(cfg.SMTP[i].HelloHostname) + } + + // Trim OAuth config. + if cfg.OAuth != nil { + cfg.OAuth.Provider = strings.TrimSpace(cfg.OAuth.Provider) + cfg.OAuth.ClientID = strings.TrimSpace(cfg.OAuth.ClientID) + cfg.OAuth.TenantID = strings.TrimSpace(cfg.OAuth.TenantID) + } +} diff --git a/cmd/oauth.go b/cmd/oauth.go index e3dc9984..ca09a047 100644 --- a/cmd/oauth.go +++ b/cmd/oauth.go @@ -152,9 +152,9 @@ func handleOAuthCallback(r *fastglue.Request) error { redirectURI := oauthData["redirect_uri"] clientID := oauthData["client_id"] clientSecret := oauthData["client_secret"] - tenantID := oauthData["tenant_id"] // Empty string if not set - flowType := oauthData["flow_type"] // "new_inbox" or "reconnect" - inboxIDStr := oauthData["inbox_id"] // Inbox ID for reconnect flow + tenantID := oauthData["tenant_id"] // Empty string if not set + flowType := oauthData["flow_type"] // "new_inbox" or "reconnect" + inboxIDStr := oauthData["inbox_id"] // Inbox ID for reconnect flow // Validate provider matches URL parameter if storedProvider != provider { @@ -310,11 +310,12 @@ func handleOAuthCallback(r *fastglue.Request) error { // Create inbox config config := imodels.Config{ - SMTP: []imodels.SMTPConfig{smtpConfig}, - IMAP: []imodels.IMAPConfig{imapConfig}, - From: userEmail, - AuthType: imodels.AuthTypeOAuth2, - OAuth: oauthConfig, + SMTP: []imodels.SMTPConfig{smtpConfig}, + IMAP: []imodels.IMAPConfig{imapConfig}, + From: userEmail, + AuthType: imodels.AuthTypeOAuth2, + OAuth: oauthConfig, + EnablePlusAddressing: true, } configJSON, err := json.Marshal(config) diff --git a/cmd/settings.go b/cmd/settings.go index 264c4997..4182a082 100644 --- a/cmd/settings.go +++ b/cmd/settings.go @@ -47,14 +47,19 @@ func handleUpdateGeneralSettings(r *fastglue.Request) error { return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("globals.messages.badRequest"), nil, envelope.InputError) } + // Trim whitespace from string fields. + req.SiteName = strings.TrimSpace(req.SiteName) + req.FaviconURL = strings.TrimSpace(req.FaviconURL) + req.LogoURL = strings.TrimSpace(req.LogoURL) + req.Timezone = strings.TrimSpace(req.Timezone) + // Trim whitespace and trailing slash from root URL. + req.RootURL = strings.TrimRight(strings.TrimSpace(req.RootURL), "/") + // Get current language before update. app.Lock() oldLang := ko.String("app.lang") app.Unlock() - // Remove any trailing slash `/` from the root url. - req.RootURL = strings.TrimRight(req.RootURL, "/") - if err := app.setting.Update(req); err != nil { return sendErrorEnvelope(r, err) } @@ -113,6 +118,14 @@ func handleUpdateEmailNotificationSettings(r *fastglue.Request) error { return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.T("globals.messages.badRequest"), nil, envelope.InputError) } + // Trim whitespace from string fields (Password intentionally NOT trimmed). + req.Host = strings.TrimSpace(req.Host) + req.Username = strings.TrimSpace(req.Username) + req.EmailAddress = strings.TrimSpace(req.EmailAddress) + req.HelloHostname = strings.TrimSpace(req.HelloHostname) + req.IdleTimeout = strings.TrimSpace(req.IdleTimeout) + req.WaitTimeout = strings.TrimSpace(req.WaitTimeout) + out, err := app.setting.GetByPrefix("notification.email") if err != nil { return sendErrorEnvelope(r, err) diff --git a/cmd/upgrade.go b/cmd/upgrade.go index c2e0c8bb..7514d9eb 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -39,7 +39,8 @@ var migList = []migFunc{ {"v0.8.5", migrations.V0_8_5}, {"v0.9.1", migrations.V0_9_1}, {"v0.10.0", migrations.V0_10_0}, - {"v0.12.0", migrations.V0_12_0}, + {"v1.0.1", migrations.V1_0_1}, + {"v2.0.0", migrations.V2_0_0}, } // upgrade upgrades the database to the current version by running SQL migration files diff --git a/config.sample.toml b/config.sample.toml index 55c2b603..46b8150f 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -23,8 +23,8 @@ write_timeout = "5s" # Maximum request body size in bytes (100MB) # If you are using proxy, you may need to configure them to allow larger request bodies. max_body_size = 104857600 -# Size of the read buffer for incoming requests -read_buffer_size = 4096 +# Size of the read buffer for incoming requests (also limits max header size). +read_buffer_size = 65536 # Keepalive settings. keepalive_timeout = "10s" diff --git a/frontend/apps/main/src/features/admin/general/GeneralSettingForm.vue b/frontend/apps/main/src/features/admin/general/GeneralSettingForm.vue index 111b6c59..2a1e41d9 100644 --- a/frontend/apps/main/src/features/admin/general/GeneralSettingForm.vue +++ b/frontend/apps/main/src/features/admin/general/GeneralSettingForm.vue @@ -30,6 +30,7 @@ French Italian Japanese + Spanish Marathi diff --git a/frontend/apps/main/src/features/admin/inbox/EmailInboxForm.vue b/frontend/apps/main/src/features/admin/inbox/EmailInboxForm.vue index 66bdfd95..401e5731 100644 --- a/frontend/apps/main/src/features/admin/inbox/EmailInboxForm.vue +++ b/frontend/apps/main/src/features/admin/inbox/EmailInboxForm.vue @@ -54,11 +54,25 @@ -

+

{{ $t('admin.inbox.csatSurveys.description_2') }}

+ + +
+ {{ $t('admin.inbox.enablePlusAddressing') }} + + {{ $t('admin.inbox.enablePlusAddressing.description') }} + +
+ + + +
+
+ @@ -729,6 +743,7 @@ const form = useForm({ from: '', enabled: true, csat_enabled: false, + enable_plus_addressing: true, auth_type: AUTH_TYPE_PASSWORD, imap: { host: 'imap.gmail.com', diff --git a/frontend/apps/main/src/features/admin/inbox/formSchema.js b/frontend/apps/main/src/features/admin/inbox/formSchema.js index 9ddc4b4e..d91f00fd 100644 --- a/frontend/apps/main/src/features/admin/inbox/formSchema.js +++ b/frontend/apps/main/src/features/admin/inbox/formSchema.js @@ -7,6 +7,7 @@ export const createFormSchema = (t) => z.object({ from: z.string().min(1, t('globals.messages.required')), enabled: z.boolean().optional(), csat_enabled: z.boolean().optional(), + enable_plus_addressing: z.boolean().optional(), auth_type: z.enum([AUTH_TYPE_PASSWORD, AUTH_TYPE_OAUTH2]), oauth: z.object({ access_token: z.string().optional(), diff --git a/frontend/apps/main/src/views/admin/inbox/EditInbox.vue b/frontend/apps/main/src/views/admin/inbox/EditInbox.vue index 04f08029..e3179e43 100644 --- a/frontend/apps/main/src/views/admin/inbox/EditInbox.vue +++ b/frontend/apps/main/src/views/admin/inbox/EditInbox.vue @@ -44,16 +44,15 @@ const breadcrumbLinks = [ const submitForm = (values) => { let payload - + if (inbox.value.channel === 'email') { - // Prepare request payload from form values const config = { auth_type: values.auth_type, + enable_plus_addressing: values.enable_plus_addressing, imap: [{ ...values.imap }], smtp: [{ ...values.smtp }] } - // Only add oauth if auth_type is oauth2 if (values.auth_type === AUTH_TYPE_OAUTH2) { config.oauth = values.oauth } @@ -64,7 +63,6 @@ const submitForm = (values) => { config } - // Set dummy passwords to empty string if (payload.config.imap[0].password?.includes('•')) { payload.config.imap[0].password = '' } @@ -130,6 +128,7 @@ onMounted(async () => { } inboxData.auth_type = inboxData?.config?.auth_type || AUTH_TYPE_PASSWORD inboxData.oauth = inboxData?.config?.oauth || {} + inboxData.enable_plus_addressing = inboxData?.config?.enable_plus_addressing || false inbox.value = inboxData } catch (error) { emitter.emit(EMITTER_EVENTS.SHOW_TOAST, { diff --git a/frontend/apps/main/src/views/admin/inbox/NewInbox.vue b/frontend/apps/main/src/views/admin/inbox/NewInbox.vue index 5cddcbc0..84a12e5a 100644 --- a/frontend/apps/main/src/views/admin/inbox/NewInbox.vue +++ b/frontend/apps/main/src/views/admin/inbox/NewInbox.vue @@ -173,6 +173,7 @@ const submitForm = (values) => { from: values.from, channel: channelName, config: { + enable_plus_addressing: values.enable_plus_addressing, imap: [values.imap], smtp: [values.smtp] } diff --git a/i18n/da.json b/i18n/da.json index 3eab0df5..bfbbd76b 100644 --- a/i18n/da.json +++ b/i18n/da.json @@ -35,7 +35,6 @@ "globals.terms.dashboard": "Kontrolpanel | Kontrolpaneler", "globals.terms.tag": "Tag | Tags", "globals.terms.sla": "SLA | SLA'er", - "globals.terms.slaPolicy": "SLA-politik | SLA-politikker", "globals.terms.csatSurvey": "CSAT-undersøgelse | CSAT-undersøgelser", "globals.terms.csatResponse": "CSAT-svar | CSAT-svar", "globals.terms.inbox": "Indbakke | Indbakker", @@ -389,10 +388,8 @@ "admin.general.logoURL": "Logo-URL", "admin.general.logoURL.description": "Logo-URL til appen.", "admin.general.logoURL.valid": "Logo-URL skal være en gyldig URL", - "admin.general.maxAllowedFileUploadSize": "Maks. tilladt filstørrelse til upload", "admin.general.maxAllowedFileUploadSize.description": "Maks. tilladt filstørrelse til upload i MB.", "admin.general.maxAllowedFileUploadSize.valid": "Maks. tilladt filstørrelse til upload skal være mellem 1 og 500 MB", - "admin.general.allowedFileUploadExtensions": "Tilladte filtyper til upload", "admin.general.allowedFileUploadExtensions.description": "Benyt `*` for at tillade alle filtyper, f.eks. `jpg, png, pdf`", "admin.businessHours.unauthorized": "Manglende rettighed til at få vist åbningstider.", "admin.businessHours.setBusinessHours": "Indstil åbningstider", @@ -556,7 +553,6 @@ "report.chart.newConversations": "Nye samtaler", "report.chart.resolvedConversations": "Løste samtaler", "report.chart.title": "Samtaletendenser", - "report.sla.cardTitle": "SLA-ydeevne (seneste {days} dage)", "report.sla.firstRespMet": "Første svar opfyldt", "report.sla.firstRespBreached": "Første svar brudt", "report.sla.avgFirstResp": "Gns. første svartid", diff --git a/i18n/de.json b/i18n/de.json index 50b910e0..a7b3fccc 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -3,12 +3,12 @@ "_.name": "Deutsch (de)", "globals.terms.user": "Benutzer | Benutzer", "globals.terms.contact": "Kontakt | Kontakte", - "globals.terms.agent": "Agent | Mitarbeiter", + "globals.terms.agent": "Mitarbeiter | Mitarbeiter", "globals.terms.team": "Team | Teams", "globals.terms.message": "Nachricht | Nachrichten", "globals.terms.activityMessage": "Aktivitätsnachricht | Aktivitätsnachrichten", "globals.terms.account": "Konto | Konten", - "globals.terms.conversation": "Unterhaltung | Unterhaltungen", + "globals.terms.conversation": "Konversation | Konversationen", "globals.terms.provider": "Anbieter | Anbieter", "globals.terms.state": "Bundesland | Bundesländer", "globals.terms.webhook": "Webhook | Webhooks", @@ -26,7 +26,7 @@ "globals.terms.setting": "Einstellung | Einstellungen", "globals.terms.template": "Vorlage | Vorlagen", "globals.terms.rule": "Regel | Regeln", - "globals.terms.businessHour": "Geschäftszeit | Geschäftszeiten", + "globals.terms.businessHour": "Öffnungszeiten | Öffnungszeiten", "globals.terms.priority": "Priorität | Prioritäten", "globals.terms.status": "Status | Status", "globals.terms.secret": "Geheimnis | Geheimnisse", @@ -54,9 +54,10 @@ "globals.terms.role": "Rolle | Rollen", "globals.terms.avatar": "Profilbild | Profilbilder", "globals.terms.view": "Ansicht | Ansichten", + "globals.terms.sharedView": "Geteilte Ansicht | Geteilte Ansichten", "globals.terms.email": "E-Mail | E-Mails", "globals.terms.condition": "Bedingung | Bedingungen", - "globals.terms.sso": " | ", + "globals.terms.sso": "SSO | SSOs", "globals.terms.hour": "Stunde | Stunden", "globals.terms.day": "Tag | Tage", "globals.terms.filter": "Filter | Filter", @@ -82,10 +83,11 @@ "globals.terms.open": "Offen", "globals.terms.awaitingResponse": "Wartend auf Rückmeldung", "globals.terms.unassigned": "Nicht zugewiesen", + "globals.terms.mention": "Erwähnung | Erwähnungen", "globals.terms.pending": "Ausstehend", "globals.terms.active": "Aktiv", "globals.terms.url": "URL | URLs", - "globals.terms.rootURL": "Stamm-URL", + "globals.terms.rootURL": "Basis-URL", "globals.terms.key": "Schlüssel | Schlüssel", "globals.terms.note": "Notiz | Notizen", "globals.terms.ipAddress": "IP-Adresse | IP-Adressen", @@ -101,7 +103,7 @@ "globals.terms.notification": "Benachrichtigung | Benachrichtigungen", "globals.terms.security": "Sicherheit | Sicherheit", "globals.terms.myInbox": "Mein Posteingang | Meine Posteingänge", - "globals.terms.teamInbox": "Team Posteingang | Team Posteingänge", + "globals.terms.teamInbox": "Team-Posteingang | Team-Posteingänge", "globals.terms.optional": "Optional | Optional", "globals.terms.visibility": "Sichtbarkeit | Sichtbarkeit", "globals.terms.privateNote": "Private Notiz | Private Notizen", @@ -135,7 +137,7 @@ "globals.terms.confirmation": "Bestätigung | Bestätigungen", "globals.terms.dialog": "Dialog | Dialoge", "globals.terms.modal": "Modal | Modals", - "globals.terms.timezone": "Zeitzonen | Zeitzonen", + "globals.terms.timezone": "Zeitzone | Zeitzonen", "globals.terms.language": "Sprache | Sprachen", "globals.terms.regex": "Regex | Regexes", "globals.terms.appliesTo": "Gilt für", @@ -256,7 +258,7 @@ "globals.messages.noResults": "Keine {name} gefunden", "globals.messages.enter": "{name} eingeben", "globals.messages.yes": "Ja {name}", - "globals.messages.no": "No {name}", + "globals.messages.no": "Nein {name}", "globals.messages.select": "{name} auswählen", "globals.messages.copied": "In die Zwischenablage kopiert", "globals.messages.search": "{name} suchen", @@ -410,10 +412,10 @@ "admin.general.logoURL": "Logo URL", "admin.general.logoURL.description": "Logo URL für die App.", "admin.general.logoURL.valid": "Logo URL muss eine gültige URL sein", - "admin.general.maxAllowedFileUploadSize": "Maximal erlaubte Datei-Upload-Größe", - "admin.general.maxAllowedFileUploadSize.description": "Maximal erlaubte Datei-Upload-Größe in MB.", - "admin.general.maxAllowedFileUploadSize.valid": "Maximal erlaubte Datei-Upload-Größe sollte zwischen 1 und 500 MB liegen", - "admin.general.allowedFileUploadExtensions": "Erlaubte Datei-Upload-Erweiterungen", + "admin.general.maxAllowedFileUploadSize": "Maximal erlaubte Größe für Datei-Uploads", + "admin.general.maxAllowedFileUploadSize.description": "Maximal erlaubte Größe für Datei-Uploads in MB.", + "admin.general.maxAllowedFileUploadSize.valid": "Die maximal erlaubte Größe für Datei-Uploads sollte zwischen 1 und 500 MB liegen", + "admin.general.allowedFileUploadExtensions": "Erlaubte Dateiendungen für Uploads", "admin.general.allowedFileUploadExtensions.description": "Verwende `*` um alle Dateitypen zu erlauben. Zum Beispiel: `jpg, png, pdf`", "admin.businessHours.unauthorized": "Du hast keine Berechtigung, um Öffnungszeiten anzuzeigen.", "admin.businessHours.setBusinessHours": "Öffnungszeiten auswählen", @@ -555,7 +557,7 @@ "admin.automation.invalid": "Stelle sicher, dass du mindestens eine Aktion und eine Regel hast und ihre Werte nicht leer sind.", "admin.notification.restartApp": "Einstellungen erfolgreich aktualisiert. Bitte starte die App neu, damit die Änderungen wirksam werden.", "admin.banner.restartMessage": "Einige Einstellungen wurden geändert, die einen Neustart der Anwendung erfordern.", - "admin.template.outgoingEmailTemplates": "Ausgehende E-Mail Vorlagen", + "admin.template.outgoingEmailTemplates": "Vorlagen für ausgehende E-Mails", "admin.template.emailNotificationTemplates": "Vorlage für E-Mail Benachrichtigungen", "admin.template.makeSureTemplateHasContent": "Stelle sicher, dass die Vorlage nur einmal {content} enthält.", "admin.template.onlyOneDefaultOutgoingTemplate": "Du kannst nur eine Standardvorlage für ausgehende E-Mails haben.", @@ -563,7 +565,7 @@ "admin.customAttributes.regex.description": "Regex um den Wert dieses benutzerdefinierten Attributs zu überprüfen. Leer lassen um die Überprüfung zu überspringen.", "admin.customAttributes.regexHint.description": "Hinweis für Regex-Muster.", "admin.customAttributes.keyNotAllowed": "Der angegebene Schlüssel ist nicht zulässig, da er mit den Standardattributen kollidiert. Bitte verwenden Sie einen anderen Schlüssel.", - "admin.tags.deleteConfirmation": "Sind Sie sicher, dass Sie dieses Tag löschen möchten? Dadurch wird es auch von allen Unterhaltungen entfernt", + "admin.tags.deleteConfirmation": "Sind Sie sicher, dass Sie dieses Tag löschen möchten? Dadurch wird es auch von allen Konversationen entfernt", "command.typeCmdOrSearch": "Gib einen Befehl ein oder suche...", "command.noCommandAvailable": "Kein Befehl verfügbar", "command.selectAMacro": "Wähle ein Makro um Details anzuzeigen", @@ -580,13 +582,30 @@ "report.sla.cardTitle": "SLA-Leistung (Letzte {days} Tage)", "report.sla.firstRespMet": "Erste Antwort", "report.sla.firstRespBreached": "Erste Antwort verletzt", - "report.sla.avgFirstResp": "Durchschnittliche erste Antwortzeit", + "report.sla.avgFirstResp": "Durchschnittliche Zeit für erste Antwort", "report.sla.nextRespMet": "Nächste Antwort", "report.sla.nextRespBreached": "Nächste Antwort verletzt", "report.sla.avgNextResp": "Durchschnittliche Antwortzeit für die nächste", "report.sla.resolutionMet": "Lösung Erfüllt", "report.sla.resolutionBreached": "Lösung verletzt", "report.sla.avgResolution": "Durchschnittliche Lösungszeit", + "report.openConversations": "Offene Konversationen", + "report.agentStatus": "Mitarbeiterstatus", + "report.csat.title": "Kundenzufriedenheit", + "report.csat.cardTitle": "Kundenzufriedenheit (letzte {days} Tage)", + "report.csat.avgRating": "Durchschnittliche Bewertung", + "report.csat.responseRate": "Antwortquote", + "report.csat.responses": "Antworten", + "report.messages.title": "Nachrichtenvolumen", + "report.messages.cardTitle": "Nachrichtenvolumen (letzte {days} Tage)", + "report.messages.total": "Gesamt", + "report.messages.incoming": "Eingehend", + "report.messages.outgoing": "Ausgehend", + "report.messages.perConversation": "pro Konversation", + "report.tags.title": "Tag-Verteilung", + "report.tags.cardTitle": "Tag-Verteilung (letzte {days} Tage)", + "report.tags.tagged": "Getaggt", + "report.tags.topTags": "Top-Tags", "search.noResultsForQuery": "Keine Ergebnisse für die Abfrage `{query}` gefunden. Versuche einen anderen Suchbegriff.", "search.minQueryLength": "Bitte gib mindestens {length} Zeichen ein, um zu suchen.", "search.searchBy": "Suche nach Referenznummer, Kontakt-E-Mail-Adresse oder Nachrichten in Konversationen.", @@ -605,32 +624,32 @@ "account.removeAvatar": "Profilbild entfernen", "account.cropAvatar": "Profilbild zuschneiden", "account.avatarRemoved": "Profilbild entfernt", - "conversation.resolveWithoutAssignee": "Die Unterhaltung kann ohne einen zugewiesenen Benutzer nicht abgeschlossen werden. Bitte weise vor dem Abschließen einen Benutzer zu", + "conversation.resolveWithoutAssignee": "Die Konversation kann ohne einen zugewiesenen Benutzer nicht abgeschlossen werden. Bitte weise vor dem Abschließen einen Benutzer zu", "conversation.notMemberOfTeam": "Du bist kein Mitglied dieses Teams. Bitte aktualisiere die Seite und versuche es erneut", "conversation.viewPermissionDenied": "Du hast keinen Zugriff auf diese Ansicht", "conversation.errorGeneratingMessageID": "Fehler beim Generieren der Nachrichten-ID", "conversation.invalidSnoozeDuration": "Ungültige Schlummerdauer", "conversation.errorUnassigningOpenConversations": "Fehler beim Aufheben der Zuweisung der offenen Konversationen", "conversation.errorRemovingConversationAssignee": "Fehler beim Entfernen des zugewiesenen Mitarbeiters", - "conversation.placeholder": "Wähle eine Konversation vom linken Fenster.", + "conversation.placeholder": "Wähle eine Konversation aus dem linken Bereich aus.", "conversation.searchContact": "Kontakt per E-Mail suchen oder neue E-Mail eingeben", "conversation.sort.oldestActivity": "Älteste Aktivität", - "conversation.sort.newestActivity": "Älteste Aktivität", + "conversation.sort.newestActivity": "Letzte Aktivität", "conversation.sort.startedFirst": "Zuerst begonnen", "conversation.sort.startedLast": "Zuletzt gestartet", "conversation.sort.waitingLongest": "Am längsten wartend", "conversation.sort.nextSLATarget": "Nächster SLA Ablauf", "conversation.sort.priorityFirst": "Priorisierte zuerst", - "conversation.noConversationsFound": "Keine Unterhaltungen gefunden", + "conversation.noConversationsFound": "Keine Konversationen gefunden", "conversation.tryAdjustingFilters": "Versuche, die Filter anzupassen", - "conversation.couldNotFetch": "Unterhaltungen konnten nicht abgerufen werden", - "conversation.allLoaded": "Alle Unterhaltungen geladen", + "conversation.couldNotFetch": "Konversationen konnten nicht abgerufen werden", + "conversation.allLoaded": "Alle Konversationen geladen", "conversation.showQuotedText": "Zitierten Text anzeigen", "conversation.hideQuotedText": "Zitierten Text ausblenden", "conversation.sidebar.information": "Information", "conversation.sidebar.contactAttributes": "Kontaktattribute", - "conversation.sidebar.previousConvo": "Vorherige Unterhaltungen", - "conversation.sidebar.noPreviousConvo": "Keine vorherigen Unterhaltungen", + "conversation.sidebar.previousConvo": "Vorherige Konversationen", + "conversation.sidebar.noPreviousConvo": "Keine vorherigen Konversationen", "conversation.sidebar.notAvailable": "Nicht verfügbar", "editor.newLine": "Shift + Enter um eine neue Zeile hinzuzufügen. ", "editor.send": " Strg + Enter zum senden. ", diff --git a/i18n/en.json b/i18n/en.json index ca18ea5d..a099dd0e 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -559,6 +559,8 @@ "admin.inbox.heloHostname.description": "The hostname to use in the HELO/EHLO command. If not set, defaults to localhost.", "admin.inbox.skipTLSVerification": "Skip TLS Verification", "admin.inbox.skipTLSVerification.description": "Skip hostname check on the TLS certificate.", + "admin.inbox.enablePlusAddressing": "Enable plus addressing", + "admin.inbox.enablePlusAddressing.description": "Improves conversation threading but requires provider support (e.g., Gmail, Microsoft 365).", "admin.inbox.chooseChannel": "Choose a channel", "admin.inbox.configureChannel": "Configure channel", "admin.inbox.createEmailInbox": "Create Email Inbox", diff --git a/i18n/es.json b/i18n/es.json new file mode 100644 index 00000000..babba271 --- /dev/null +++ b/i18n/es.json @@ -0,0 +1,150 @@ +{ + "_.code": "en", + "_.name": "Inglés (en)", + "globals.terms.user": "Usuario | Usuarios", + "globals.terms.contact": "Contacto | Contactos", + "globals.terms.agent": "Agente | Agentes", + "globals.terms.team": "Equipo | Equipos", + "globals.terms.message": "Mensaje | Mensajes", + "globals.terms.activityMessage": "Actualización de actividad | Actualización de actividades", + "globals.terms.account": "Cuenta | Cuentas", + "globals.terms.conversation": "Conversación | Conversaciones", + "globals.terms.provider": "Proveedor | Proveedores", + "globals.terms.state": "Estado | Estados", + "globals.terms.webhook": "Web Hook | Hooks web", + "globals.terms.session": "Sesión | Sesiones", + "globals.terms.media": "Medio | Medios", + "globals.terms.permission": "Permiso | Permisos", + "globals.terms.request": "Solicitud | Solicitudes", + "globals.terms.file": "Archivo | Archivos", + "globals.terms.actor": "Actor | Actores", + "globals.terms.page": "Página | Páginas", + "globals.terms.activityLog": "Historial de actividad | Historial de actividades", + "globals.terms.name": "Nombre | Nombres", + "globals.terms.image": "Imagen | Imágenes", + "globals.terms.thumbnail": "Vista en miniatura | Vistas en miniatura", + "globals.terms.setting": "Ajuste | Ajustes", + "globals.terms.template": "Plantilla | Plantillas", + "globals.terms.rule": "Regla | Reglas", + "globals.terms.businessHour": "Horario de atención | Horario de trabajo", + "globals.terms.priority": "Prioridad | Prioridades", + "globals.terms.status": "Estado | Estados", + "globals.terms.secret": "Secreto | Secretos", + "globals.terms.inactive": "Inactivo | Inactivos", + "globals.terms.integration": "Integración | Integraciones", + "globals.terms.content": "Contenido | Contenidos", + "globals.terms.appRootURL": "URL raíz de la aplicación", + "globals.terms.dashboard": "Tablero | Tableros", + "globals.terms.tag": "Etiqueta | Etiquetas", + "globals.terms.sla": "Acuerdo de Nivel de Servicio | Acuerdos de Nivel de Servicio", + "globals.terms.csatSurvey": "Encuesta CSAT | Encuestas CSAT", + "globals.terms.csatResponse": "Repuesta CSAT | Respuestas CSAT", + "globals.terms.inbox": "Bandeja de entrada | Bandejas de entrada", + "globals.terms.conversationParticipant": "Participante de la conversación | Participantes de la conversación", + "globals.terms.config": "Configuración | Configuraciones", + "globals.terms.macro": "Macro | Macros", + "globals.terms.macroAction": "Acción de macro | Acciones de macro", + "globals.terms.action": "Acción | Acciones", + "globals.terms.value": "Valor | Valores", + "globals.terms.event": "Evento | Eventos", + "globals.terms.automation": "Automatización | Automatizaciones", + "globals.terms.oidc": "OIDC | OIDCs", + "globals.terms.oidcProvider": "Proveedor OIDC | Proveedores OIDC", + "globals.terms.role": "Rol | Roles", + "globals.terms.avatar": "Avatar | Avatares", + "globals.terms.view": "Vista | Vistas", + "globals.terms.email": "Correo Electrónico | Correos Electrónicos", + "globals.terms.condition": "Condición | Condiciones", + "globals.terms.sso": "SSO | SSO", + "globals.terms.hour": "Hora | Horas", + "globals.terms.day": "Día | Días", + "globals.terms.filter": "Filtro | Filtros", + "globals.terms.profile": "Perfil | Perfiles", + "globals.terms.apiKey": "Llave API | Llaves API", + "globals.terms.loading": "Cargando...", + "globals.terms.loadMore": "Cargar Más", + "globals.terms.holiday": "Festivo | Vacaciones", + "globals.terms.password": "Contraseña | Contraseñas", + "globals.terms.result": "Resultado | Resultados", + "globals.terms.meta": "Meta | Metas", + "globals.terms.online": "En línea | En línea", + "globals.terms.offline": "Desconectado | Desconectado", + "globals.terms.away": "Ausente | Ausente", + "globals.terms.admin": "Administrador | Administradores", + "globals.terms.customAttribute": "Atributo personalizado | Atributos personalizados", + "globals.terms.attribute": "Atributo | Atributo", + "globals.terms.tryAgain": "Intenta de nuevo", + "globals.terms.search": "Búsqueda", + "globals.terms.live": "En vivo", + "globals.terms.lastUpdated": "Última actualización", + "globals.terms.total": "Total", + "globals.terms.open": "Abrir", + "globals.terms.awaitingResponse": "Esperando respuesta", + "globals.terms.unassigned": "No asignado", + "globals.terms.pending": "Pendiente", + "globals.terms.active": "Activo", + "globals.terms.url": "URL | URLs", + "globals.terms.rootURL": "URL raíz", + "globals.terms.key": "Clave | Claves", + "globals.terms.note": "Nota | Notas", + "globals.terms.ipAddress": "Dirección IP | Direcciones IP", + "globals.terms.alert": "Alerta | Alertas", + "globals.terms.duration": "Duración | Duraciones", + "globals.terms.slaMetric": "Metrica SLA | Metricas SLA ", + "globals.terms.general": "General", + "globals.terms.optional": "Opcional | Opcionales", + "globals.terms.visibility": "Visibilidad | Visibilidades", + "globals.terms.privateNote": "Nota privada | Notas privadas", + "globals.terms.automationRule": "Regla de automatización | Reglas de automatización", + "globals.terms.subject": "Asunto | Asuntos", + "globals.terms.today": "Hoy", + "globals.terms.csat": "CSAT | CSATs", + "globals.terms.field": "Campo | Campos", + "globals.terms.column": "Columna | Columnas", + "globals.terms.row": "Fila | Filas", + "globals.terms.button": "Botón | Botones", + "globals.terms.link": "Vínculo | Vínculos", + "globals.terms.icon": "Icono | Iconos", + "globals.terms.error": "Error | Errores", + "globals.terms.success": "Éxito | Éxitos", + "globals.terms.type": "Tipo | Tipos", + "globals.terms.warning": "Advertencia | Advertencias", + "globals.terms.info": "Información | Información", + "globals.terms.enabled": "Habilitado", + "globals.terms.disabled": "Deshabilitado", + "globals.terms.required": "Requerido", + "globals.terms.min": "Mínimo | Mínimos", + "globals.terms.max": "Máximo | Máximos", + "globals.terms.import": "Importar | Importar", + "globals.terms.export": "Exportar | Exportar", + "globals.terms.appliesTo": "Aplica a", + "globals.terms.createdOn": "Creado el", + "globals.terms.awayReassigning": "Ausente y reasignando", + "globals.terms.availabilityStatus": "Estado de disponibilidad", + "globals.terms.lastActive": "Última actividad", + "globals.terms.lastLogin": "Último acceso", + "globals.terms.providerURL": "URL del proveedor", + "globals.terms.clientID": "ID del cliente", + "globals.terms.clientSecret": "Secreto del cliente", + "globals.terms.callbackURL": "URL de retorno (callback)", + "globals.terms.referenceNumber": "Número de referencia", + "globals.terms.initiatedAt": "Iniciado el", + "globals.terms.firstReplyAt": "Primera respuesta el", + "globals.terms.lastReplyAt": "Última respuesta el", + "globals.terms.resolvedAt": "Resuelto el", + "globals.terms.closedAt": "Cerrado el", + "globals.terms.isDefault": "Es predeterminado", + "globals.terms.body": "Contenido principal", + "globals.terms.default": "Predeterminado", + "globals.terms.channel": "Canal", + "globals.terms.configure": "Configurar", + "globals.terms.date": "Fecha", + "globals.terms.fromEmailAddress": "Correo electrónico del remitente | Correos electrónicos de los remitentes", + "globals.messages.custom": "{name} personalizado", + "globals.messages.replying": "Respondiendo", + "globals.messages.hoursSince": "Horas desde", + "globals.messages.hoursSinceCreated": "Horas desde que se creó", + "globals.messages.hoursSinceFirstReply": "Horas desde la primera respuesta", + "globals.messages.hoursSinceLastReply": "Horas desde la última respuesta", + "globals.messages.hoursSinceResolved": "Horas desde que se resolvió" +} \ No newline at end of file diff --git a/i18n/fa.json b/i18n/fa.json index 73217a7c..fd0c7ae3 100644 --- a/i18n/fa.json +++ b/i18n/fa.json @@ -35,7 +35,6 @@ "globals.terms.dashboard": "داشبورد | داشبوردها", "globals.terms.tag": "برچسب | برچسب‌ها", "globals.terms.sla": "SLA | SLAs", - "globals.terms.slaPolicy": "سیاست SLA | سیاست‌های SLA", "globals.terms.csatSurvey": "نظرسنجی CSAT | نظرسنجی‌های CSAT", "globals.terms.csatResponse": "پاسخ CSAT | پاسخ‌های CSAT", "globals.terms.inbox": "صندوق ورودی | صندوق‌های ورودی", @@ -227,10 +226,8 @@ "admin.general.logoURL": "آدرس URL لوگو", "admin.general.logoURL.description": "آدرس URL لوگو برای اپلیکیشن.", "admin.general.logoURL.valid": "آدرس URL لوگو باید یک URL معتبر باشد", - "admin.general.maxAllowedFileUploadSize": "حداکثر اندازه مجاز بارگذاری فایل", "admin.general.maxAllowedFileUploadSize.description": "حداکثر اندازه مجاز بارگذاری فایل به مگابایت.", "admin.general.maxAllowedFileUploadSize.valid": "حداکثر اندازه مجاز بارگذاری فایل باید بین 1 و 500 مگابایت باشد", - "admin.general.allowedFileUploadExtensions": "پسوندهای مجاز بارگذاری فایل", "admin.businessHours.unauthorized": "شما مجوز مشاهده ساعات کاری را ندارید.", "admin.businessHours.setBusinessHours": "تنظیم ساعات کاری", "admin.businessHours.customBusinessHours": "ساعات کاری سفارشی", diff --git a/i18n/fr.json b/i18n/fr.json index c33ecfb5..6cde0991 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -37,7 +37,6 @@ "globals.terms.dashboard": "Tableau de bord | Tableaux de bord", "globals.terms.tag": "Étiquette | Étiquettes", "globals.terms.sla": "SLA | SLA", - "globals.terms.slaPolicy": "Politique de SLA | Politiques de SLA", "globals.terms.csatSurvey": "Enquête de satisfaction | Enquêtes de satisfaction", "globals.terms.csatResponse": "Réponse à l'enquête de satisfaction | Réponses à l'enquête de satisfaction", "globals.terms.inbox": "Boîte de réception | Boîtes de réception", @@ -409,10 +408,8 @@ "admin.general.logoURL": "URL du logo", "admin.general.logoURL.description": "URL du logo de l'application.", "admin.general.logoURL.valid": "L'URL du logo doit être valide", - "admin.general.maxAllowedFileUploadSize": "Taille maximale autorisée pour le téléchargement de fichiers", "admin.general.maxAllowedFileUploadSize.description": "Taille maximale autorisée pour le téléchargement de fichiers en Mo.", "admin.general.maxAllowedFileUploadSize.valid": "La taille maximale autorisée pour le téléchargement de fichiers doit être comprise entre 1 et 500 Mo", - "admin.general.allowedFileUploadExtensions": "Extensions autorisées pour le téléchargement de fichiers", "admin.general.allowedFileUploadExtensions.description": "Utilisez `*` pour autoriser tous les types de fichiers. Par exemple : `jpg, png, pdf`", "admin.businessHours.unauthorized": "Vous n'êtes pas autorisé à consulter les heures d'ouverture.", "admin.businessHours.setBusinessHours": "Fixer les heures d'ouverture", @@ -494,6 +491,7 @@ "admin.role.conversations.readAll": "Voir toutes les conversations", "admin.role.conversations.readUnassigned": "Voir toutes les conversations non attribuées", "admin.role.conversations.readTeamInbox": "Voir les conversations dans la boîte de réception de l'équipe", + "admin.role.conversations.readTeamAll": "Voir les conversations de votre équipe", "admin.role.conversations.updateUserAssignee": "Attribuer des conversations à des utilisateurs", "admin.role.conversations.updateTeamAssignee": "Affecter des conversations à des équipes", "admin.role.conversations.updatePriority": "Changer la priorité de la conversation", @@ -576,7 +574,6 @@ "report.chart.newConversations": "Nouvelles conversations", "report.chart.resolvedConversations": "Conversations résolues", "report.chart.title": "Tendances de conversation", - "report.sla.cardTitle": "Performance du SLA ({days} derniers jours)", "report.sla.firstRespMet": "Date de première réponse", "report.sla.firstRespBreached": "Temps Première Réponse", "report.sla.avgFirstResp": "Durée Moyenne de Première Réponse", diff --git a/i18n/it.json b/i18n/it.json index ce82091b..ec4c7e1d 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -8,6 +8,7 @@ "globals.terms.message": "Messaggio | Messaggi", "globals.terms.activityMessage": "Messaggio attività | Messaggi attività", "globals.terms.account": "Account | Account", + "globals.terms.authorization": "Autorizzazione", "globals.terms.conversation": "Conversazione | Conversazioni", "globals.terms.provider": "Fornitore | Fornitori", "globals.terms.state": "Stato | Stati", @@ -17,9 +18,18 @@ "globals.terms.permission": "Permesso | Permessi", "globals.terms.request": "Richiesta | Richieste", "globals.terms.file": "File | File", + "globals.terms.csvFile": "Uno | Altro", "globals.terms.actor": "Attore | Attori", "globals.terms.page": "Pagina | Pagine", + "globals.terms.log": "Uno | Registri", "globals.terms.activityLog": "Log Attività | Logs Attività", + "activityLog.type.agentLogin": "Accesso agente", + "activityLog.type.agentLogout": "Uscita agente", + "activityLog.type.agentAway": "Agente assente", + "activityLog.type.agentAwayReassigned": "Agente assente riassegnato", + "activityLog.type.agentOnline": "Agente online", + "activityLog.type.agentPasswordSet": "Password agente impostata", + "activityLog.type.agentRolePermissionsChanged": "Permessi del ruolo dell’agente modificati", "globals.terms.name": "Nome | Nomi", "globals.terms.image": "Immagine | Immagini", "globals.terms.thumbnail": "Miniatura | Miniature", @@ -37,7 +47,7 @@ "globals.terms.dashboard": "Scrivania | Scrivanie", "globals.terms.tag": "Tag | Tags", "globals.terms.sla": "SLA | Contratti di servizio", - "globals.terms.slaPolicy": "Politica di servizio | Polizze di Servizio", + "globals.terms.slaPolicy": "Politica SLA | Politiche SLA", "globals.terms.csatSurvey": "Questionario di soddisfazione | Questionarii di soddisfazione", "globals.terms.csatResponse": "Risposta | Risposte", "globals.terms.inbox": "Casella in arrivo | Caselle di posta", @@ -54,6 +64,7 @@ "globals.terms.role": "Ruolo | Ruoli", "globals.terms.avatar": "Avatar | Avatar", "globals.terms.view": "Vista | Viste", + "globals.terms.sharedView": "Vista condivisa | Viste condivise", "globals.terms.email": "Email | Email", "globals.terms.condition": "Condizione | Condizioni", "globals.terms.sso": "SSO | SSO", @@ -82,6 +93,7 @@ "globals.terms.open": "Aperto", "globals.terms.awaitingResponse": "In attesa di risposta", "globals.terms.unassigned": "Non assegnato", + "globals.terms.mention": "Menzione | Menzioni", "globals.terms.pending": "In sospeso", "globals.terms.active": "Attivo", "globals.terms.url": "URL | URLs", @@ -126,6 +138,10 @@ "globals.terms.min": "Minimo | Minimi", "globals.terms.max": "Massimo | Massimi", "globals.terms.length": "Lunghezza | Lunghezze", + "globals.terms.google": "Google", + "globals.terms.microsoft": "Microsoft", + "globals.terms.reconnect": "Riconnetti", + "globals.terms.protocol": "protocollo", "globals.terms.size": "Dimensione | Dimensioni", "globals.terms.upload": "Caricamento | Caricamenti", "globals.terms.download": "Scarica | Scaricamenti", @@ -189,6 +205,9 @@ "globals.terms.recipient": "Destinatario | Destinatari", "globals.terms.tls": "TLS | TLS", "globals.terms.credential": "Credenziale | Credenziali", + "globals.terms.tenantID": "ID tenant", + "globals.terms.copy": "Copia", + "globals.messages.markAsUnread": "Segna come non letto", "globals.messages.welcomeToLibredesk": "Benvenuti su Libredesk", "globals.messages.invalid": "{name} Non Valido", "globals.messages.custom": "{name} Personalizzato", @@ -238,6 +257,9 @@ "globals.messages.revokedSuccessfully": "{name} revocato correttamente", "globals.messages.errorRevoking": "Errore nella revoca di {name}", "globals.messages.generatedSuccessfully": "{name} generato correttamente", + "globals.messages.connectedSuccessfully": "{name} connesso correttamente", + "globals.messages.reconnectedSuccessfully": "{name} riconnesso correttamente", + "globals.messages.errorConnecting": "Errore durante la connessione di {name}", "globals.messages.generate": "Generazione {name}", "globals.messages.generated": "{name} generato", "globals.messages.regenerate": "Rigenera", @@ -252,6 +274,8 @@ "globals.messages.adding": "Aggiunta {name}", "globals.messages.starting": "Inizio {name}", "globals.messages.all": "Tutti {name}", + "globals.messages.deleteAll": "Elimina tutto", + "globals.messages.markAllAsRead": "Segna tutto come letto", "globals.messages.denied": "{name} negato", "globals.messages.noResults": "Nessun {name} trovato", "globals.messages.enter": "Inserisci {name}", @@ -259,6 +283,7 @@ "globals.messages.no": "No {name}", "globals.messages.select": "Seleziona {name}", "globals.messages.copied": "Copiato negli appunti", + "globals.messages.errorCopying": "Errore durante la copia negli appunti", "globals.messages.search": "Cerca {name}", "globals.messages.type": "Tipo {name}", "globals.messages.typeOf": "Tipo di {name}", @@ -294,6 +319,8 @@ "globals.messages.saveChanges": "Salva le modifiche", "globals.messages.cancel": "Annulla", "globals.messages.submit": "Invia", + "globals.messages.continue": "Continua", + "globals.messages.connecting": "Connessione in corso...", "globals.messages.send": "Invia {name}", "globals.messages.update": "Aggiorna {name}", "globals.messages.setUp": "Imposta", @@ -306,6 +333,7 @@ "globals.messages.upload": "Caricamento", "globals.messages.back": "Indietro", "globals.messages.close": "Chiudi", + "globals.messages.import": "Importa {name}", "globals.messages.apply": "Applica {name}", "globals.messages.reset": "Resetta {name}", "globals.messages.lastNItems": "Ultimo {n} {name} | Ultimi {n} {name}", @@ -332,8 +360,13 @@ "user.errorGeneratingPasswordToken": "Errore nella generazione del token password", "media.fileSizeTooLarge": "Dimensione del file troppo grande, carica un file meno di {size} ", "media.fileTypeNotAllowed": "Tipo file non consentito", + "media.fileEmpty": "Questo file è di 0 byte, quindi non verrà allegato.", + "media.invalidOrExpiredURL": "URL del contenuto multimediale non valido o scaduto", "inbox.emptyIMAP": "Configurazione IMAP vuota", "inbox.emptySMTP": "Configurazione SMTP vuota", + "inbox.oauthAlreadyExists": "Esiste già una casella di posta con questa email. Usa Riconnetti per aggiornare le credenziali.", + "inbox.oauthNotFound": "Nessuna casella di posta trovata con questa email da riconnettere.", + "inbox.oauthEmailMismatch": "L’email autorizzata non corrisponde a questa casella di posta. Autorizza con l’account corretto.", "template.defaultTemplateAlreadyExists": "Il modello predefinito esiste già", "template.cannotDeleteBuiltInTemplate": "Impossibile eliminare il modello integrato", "role.invalidPermission": "Permesso non valido {name}", @@ -409,10 +442,10 @@ "admin.general.logoURL": "URL del logo", "admin.general.logoURL.description": "URL del logo per l'applicazione.", "admin.general.logoURL.valid": "L'URL del logo dovrebbe essere un URL valido", - "admin.general.maxAllowedFileUploadSize": "Dimensione Massima Di Caricamento File Consentita", + "admin.general.maxAllowedFileUploadSize": "Dimensione massima consentita per il caricamento dei file", "admin.general.maxAllowedFileUploadSize.description": "Dimensione massima di caricamento file consentita in MB.", "admin.general.maxAllowedFileUploadSize.valid": "La dimensione massima consentita per il caricamento dei file dovrebbe essere compresa tra 1 e 500 MB", - "admin.general.allowedFileUploadExtensions": "Estensioni consentite per il caricamento dei file", + "admin.general.allowedFileUploadExtensions": "Estensioni di file consentite per il caricamento", "admin.general.allowedFileUploadExtensions.description": "Usa `*` per consentire tutti i tipi di file. Per esempio: `jpg, png, pdf`", "admin.businessHours.unauthorized": "Non hai il permesso di visualizzare l'orario lavorativo.", "admin.businessHours.setBusinessHours": "Imposta l'orario di lavoro", @@ -481,6 +514,25 @@ "admin.inbox.chooseChannel": "Scegli un canale", "admin.inbox.configureChannel": "Configura canale", "admin.inbox.createEmailInbox": "Crea casella email", + "admin.inbox.oauth.chooseSetupMethod": "Scegli il metodo di configurazione", + "admin.inbox.oauth.selectConnectionMethod": "Seleziona come vuoi collegare il tuo account email", + "admin.inbox.oauth.googleDescription": "Connetti con Google Workspace o Gmail", + "admin.inbox.oauth.microsoftDescription": "Connetti con Microsoft 365 o Outlook", + "admin.inbox.oauth.otherProvider": "Altro provider", + "admin.inbox.oauth.otherProviderDescription": "Configura manualmente IMAP e SMTP", + "admin.inbox.oauth.connectedVia": "Connesso tramite OAuth - {provider}", + "admin.inbox.oauth.connectAccount": "Connetti account {provider}", + "admin.inbox.oauth.followSteps": "Segui i passaggi seguenti per collegare il tuo account email", + "admin.inbox.oauth.step1CreateApp": "1. Crea un'app OAuth a", + "admin.inbox.oauth.googleCloudConsole": "Google Cloud Console", + "admin.inbox.oauth.microsoftAzurePortal": "Portale Microsoft Azure", + "admin.inbox.oauth.step2AddCallback": "2. Aggiungi questo URL di callback:", + "admin.inbox.oauth.step3EnterCredentials": "3. Inserisci le tue credenziali qui sotto:", + "admin.inbox.oauth.enterClientID": "Inserisci l’ID client OAuth", + "admin.inbox.oauth.enterClientSecret": "Inserisci il tuo segreto client OAuth", + "admin.inbox.oauth.clientIDSecretRequired": "Fornisci sia l’ID client che il client secret", + "admin.inbox.oauth.reconnectAccount": "Riconnetti l’account {provider}", + "admin.inbox.oauth.reconnectDescription": "Reinserisci le tue credenziali per aggiornare la connessione OAuth", "admin.agent.deleteConfirmation": "Questo eliminerà definitivamente l'agente. Considera invece di disabilitare l'account.", "admin.agent.apiKey.description": "Genera chiavi API per questo agente per accedere a libredesk programmaticamente.", "admin.agent.apiKey.noKey": "Nessuna chiave API è stata generata per questo agente.", @@ -494,6 +546,7 @@ "admin.role.conversations.readAll": "Mostra tutte le conversazioni", "admin.role.conversations.readUnassigned": "Visualizza tutte le conversazioni non assegnate", "admin.role.conversations.readTeamInbox": "Visualizza le conversazioni nella posta in arrivo del team", + "admin.role.conversations.readTeamAll": "Visualizza le conversazioni della tua squadra", "admin.role.conversations.updateUserAssignee": "Assegna conversazioni ai team", "admin.role.conversations.updateTeamAssignee": "Assegna conversazioni ai team", "admin.role.conversations.updatePriority": "Cambia priorità conversazione", @@ -503,6 +556,7 @@ "admin.role.messages.write": "Invia messaggi nelle conversazioni", "admin.role.messages.writeAsContact": "Invia messaggi come contatto", "admin.role.view.manage": "Crea e gestisci le viste delle conversazioni", + "admin.role.sharedViews.manage": "Gestisci Viste Condivise", "admin.role.generalSettings.manage": "Gestisci impostazioni generali", "admin.role.notificationSettings.manage": "Gestisci Impostazioni Di Notifica", "admin.role.status.manage": "Gestisci Stato Conversazione", @@ -559,6 +613,7 @@ "admin.template.makeSureTemplateHasContent": "Assicurati che nel modello appaia {content} una volta soltanto.", "admin.template.onlyOneDefaultOutgoingTemplate": "Puoi avere un solo modello predefinito di email in uscita.", "admin.sso.setThisUrlForCallback": "Imposta questo URI per la callback.", + "admin.sso.logoURLDescription": "URL del logo personalizzato da visualizzare nella pagina di login.", "admin.customAttributes.regex.description": "Regex per convalidare il valore di questo attributo personalizzato. Lasciare vuoto per saltare la convalida.", "admin.customAttributes.regexHint.description": "Suggerimento modello Regex.", "admin.customAttributes.keyNotAllowed": "La chiave fornita non è consentita in quanto è in conflitto con gli attributi predefiniti. Si prega di utilizzare una chiave diversa.", @@ -586,6 +641,29 @@ "report.sla.resolutionMet": "Orario di risoluzione rispettato", "report.sla.resolutionBreached": "Risoluzione violata", "report.sla.avgResolution": "Orario di risoluzione medio", + "report.sla.compliance": "Conformità", + "report.sla.met": "Incontrato", + "report.sla.breached": "Violazione", + "report.sla.firstResponse": "Prima Risposta", + "report.sla.nextResponse": "Risposta Successiva", + "report.sla.resolution": "Risoluzione", + "report.openConversations": "Apri conversazione", + "report.agentStatus": "Stato dell’agente", + "report.csat.title": "Soddisfazione del cliente", + "report.csat.cardTitle": "Soddisfazione del cliente (ultimo {days} giorni)", + "report.csat.avgRating": "Valutazione media", + "report.csat.responseRate": "Tasso di risposta", + "report.csat.responses": "Risposte", + "report.messages.title": "Volume Messaggio", + "report.messages.cardTitle": "Volume messaggio (ultimo {days} giorni)", + "report.messages.total": "Totale", + "report.messages.incoming": "In arrivo", + "report.messages.outgoing": "In uscita", + "report.messages.perConversation": "Per conversazione", + "report.tags.title": "Distribuzione dei tag", + "report.tags.cardTitle": "Distribuzione dei tag (ultimi {days} giorni)", + "report.tags.tagged": "Taggato", + "report.tags.topTags": "Tag principali", "search.noResultsForQuery": "Nessun risultato trovato per la query `{query}`. Prova un termine di ricerca diverso.", "search.minQueryLength": "Per favore inserisci almeno {length} caratteri per la ricerca.", "search.searchBy": "Cerca per numero di riferimento, indirizzo email di contatto o messaggi nelle conversazioni.", @@ -646,5 +724,10 @@ "contact.notes.help": "Aggiungi una nota per questo contatto per tenere traccia di importanti informazioni e conversazioni.", "setup.completeYourSetup": "Completa la tua configurazione", "setup.createFirstInbox": "Crea la tua prima casella di posta", - "setup.inviteTeammates": "Invita colleghi" + "setup.inviteTeammates": "Invita colleghi", + "importer.requiredCSVFormat": "Formato CSV richiesto", + "importer.importCompleted": "Importazione completata: {success} di {total} riuscite, {errors} non riuscite", + "importer.csvMustContainHeadersAndData": "Il CSV deve contenere intestazioni e almeno una riga di dati", + "importer.importAlreadyInProgress": "Importazione già in corso", + "importer.agentCaseSensitiveNote": "Ruoli e team devono corrispondere esattamente (distinzione tra maiuscole e minuscole)" } \ No newline at end of file diff --git a/i18n/ja.json b/i18n/ja.json index f8ec5c31..e04ab6a4 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -8,6 +8,7 @@ "globals.terms.message": "メッセージ", "globals.terms.activityMessage": "アクティビティメッセージ", "globals.terms.account": "アカウント", + "globals.terms.authorization": "認証", "globals.terms.conversation": "会話", "globals.terms.provider": "プロバイダー", "globals.terms.state": "都道府県", @@ -17,9 +18,18 @@ "globals.terms.permission": "アクセス許可", "globals.terms.request": "リクエスト", "globals.terms.file": "ファイル", + "globals.terms.csvFile": "CSV ファイル", "globals.terms.actor": "操作担当者", "globals.terms.page": "ページ", + "globals.terms.log": "ログ", "globals.terms.activityLog": "アクティビティログ", + "activityLog.type.agentLogin": "担当者がログインしました", + "activityLog.type.agentLogout": "担当者がログアウトしました", + "activityLog.type.agentAway": "担当者が離席しました", + "activityLog.type.agentAwayReassigned": "担当者が離席し再割り当てをしました", + "activityLog.type.agentOnline": "担当者がオンラインになりました", + "activityLog.type.agentPasswordSet": "担当者がパスワードをセットしました", + "activityLog.type.agentRolePermissionsChanged": "担当者のロール権限が変更されました", "globals.terms.name": "名前", "globals.terms.image": "画像", "globals.terms.thumbnail": "サムネイル", @@ -54,6 +64,7 @@ "globals.terms.role": "役割", "globals.terms.avatar": "アバター", "globals.terms.view": "表示", + "globals.terms.sharedView": "共有ビュー", "globals.terms.email": "Eメール", "globals.terms.condition": "条件", "globals.terms.sso": "SSO", @@ -70,7 +81,7 @@ "globals.terms.meta": "メタ", "globals.terms.online": "オンライン", "globals.terms.offline": "オフライン", - "globals.terms.away": "不在", + "globals.terms.away": "離席中", "globals.terms.admin": "管理者", "globals.terms.customAttribute": "カスタム属性", "globals.terms.attribute": "属性", @@ -82,6 +93,7 @@ "globals.terms.open": "オープン", "globals.terms.awaitingResponse": "回答待ち", "globals.terms.unassigned": "未割り当て", + "globals.terms.mention": "あなた宛て", "globals.terms.pending": "保留中", "globals.terms.active": "有効", "globals.terms.url": "URL", @@ -126,6 +138,10 @@ "globals.terms.min": "最小", "globals.terms.max": "最大", "globals.terms.length": "長さ", + "globals.terms.google": "Google", + "globals.terms.microsoft": "Microsoft", + "globals.terms.reconnect": "再接続", + "globals.terms.protocol": "プロトコル", "globals.terms.size": "サイズ", "globals.terms.upload": "アップロード", "globals.terms.download": "ダウンロード", @@ -140,7 +156,7 @@ "globals.terms.regex": "正規表現", "globals.terms.appliesTo": "適用先", "globals.terms.createdOn": "作成日時", - "globals.terms.awayReassigning": "不在時の再割り当て", + "globals.terms.awayReassigning": "離席と再割り当て", "globals.terms.availabilityStatus": "対応状況", "globals.terms.lastActive": "最終活動日時", "globals.terms.lastLogin": "最終ログイン", @@ -189,6 +205,9 @@ "globals.terms.recipient": "受信者", "globals.terms.tls": "TLS", "globals.terms.credential": "認証情報", + "globals.terms.tenantID": "テナントID", + "globals.terms.copy": "コピー", + "globals.messages.markAsUnread": "未読にする", "globals.messages.welcomeToLibredesk": "Libredesk へようこそ", "globals.messages.invalid": "無効な {name}", "globals.messages.custom": "カスタム {name}", @@ -238,20 +257,25 @@ "globals.messages.revokedSuccessfully": "{name} は正常に取り消されました", "globals.messages.errorRevoking": "{name} の取り消し中にエラーが発生しました", "globals.messages.generatedSuccessfully": "{name} は正常に生成されました", + "globals.messages.connectedSuccessfully": "{name} は正常に接続されました", + "globals.messages.reconnectedSuccessfully": "{name} は正常に再接続されました", + "globals.messages.errorConnecting": "{name} の接続中にエラーが発生しました", "globals.messages.generate": "{name} を生成", "globals.messages.generated": "{name} が生成されました", "globals.messages.regenerate": "再生成", "globals.messages.revoke": "取り消す", "globals.messages.lastUsed": "最終使用日時", "globals.messages.pageTooLarge": "ページサイズが大きすぎます。 {max} 以下にしてください", - "globals.messages.edit": "{name} を編集", - "globals.messages.delete": "{name} を削除", + "globals.messages.edit": "編集 {name}", + "globals.messages.delete": "削除 {name}", "globals.messages.create": "{name} を作成", "globals.messages.new": "{name} を新規作成", "globals.messages.add": "{name} を追加", "globals.messages.adding": "{name} を追加", "globals.messages.starting": "{name} を開始", "globals.messages.all": "すべての {name}", + "globals.messages.deleteAll": "すべて削除", + "globals.messages.markAllAsRead": "すべて既読にする", "globals.messages.denied": "{name} を拒否しました", "globals.messages.noResults": "{name} が見つかりませんでした", "globals.messages.enter": "{name} を入力します", @@ -259,6 +283,7 @@ "globals.messages.no": "いいえ、{name}", "globals.messages.select": "{name} を選択", "globals.messages.copied": "クリップボードにコピーしました", + "globals.messages.errorCopying": "クリップボードへのコピーに失敗しました", "globals.messages.search": "{name} を検索", "globals.messages.type": "{name} のタイプ", "globals.messages.typeOf": "{name} のタイプ", @@ -282,7 +307,7 @@ "globals.messages.selectAFutureTime": "未来の時刻を選択してください", "globals.messages.assign": "{name} を割り当て", "globals.messages.set": "{name} を設定", - "globals.messages.remove": "{name} を削除", + "globals.messages.remove": "削除 {name}", "globals.messages.deletionConfirmation": "この操作は元に戻せません。この操作は {name} を完全に削除します。", "globals.messages.startTypingToSearch": "入力を開始して検索…", "globals.messages.goHourMinuteDuration": "無効な期間形式です。数字の後に「h」(時間)または「m」(分)を付けて入力してください。", @@ -294,6 +319,8 @@ "globals.messages.saveChanges": "変更を保存", "globals.messages.cancel": "キャンセル", "globals.messages.submit": "送信", + "globals.messages.continue": "続ける", + "globals.messages.connecting": "接続中…", "globals.messages.send": "{name} を送信", "globals.messages.update": "{name} を更新", "globals.messages.setUp": "設定", @@ -306,6 +333,7 @@ "globals.messages.upload": "アップロード", "globals.messages.back": "戻る", "globals.messages.close": "クローズ", + "globals.messages.import": "{name} をインポート", "globals.messages.apply": "{name} を適用", "globals.messages.reset": "{name} をリセット", "globals.messages.lastNItems": "直近の {n} {name}", @@ -333,8 +361,12 @@ "media.fileSizeTooLarge": "ファイルサイズが大きすぎます。 {size} 未満のファイルをアップロードしてください ", "media.fileTypeNotAllowed": "ファイルタイプが許可されていません", "media.fileEmpty": "このファイルは 0 バイトなので、添付されません。", + "media.invalidOrExpiredURL": "無効または期限切れのメディア URL", "inbox.emptyIMAP": "IMAP の設定が空です", "inbox.emptySMTP": "SMTPの設定が空です", + "inbox.oauthAlreadyExists": "このメールの受信トレイは既に存在します。認証情報を更新するには再接続してください。", + "inbox.oauthNotFound": "このメールアドレスで再接続する受信トレイが見つかりません。", + "inbox.oauthEmailMismatch": "承認されたメールアドレスがこの受信トレイと一致しません。正しいアカウントで認証してください。", "template.defaultTemplateAlreadyExists": "デフォルトのテンプレートは既に存在します", "template.cannotDeleteBuiltInTemplate": "組み込みテンプレートは削除できません", "role.invalidPermission": "{name} の権限が無効です", @@ -346,7 +378,7 @@ "macro.couldNotApply": "マクロを適用できませんでした", "macro.partiallyApplied": "マクロは部分的に適用されました", "macro.applied": "マクロが適用されました", - "sla.firstResponseTimeAfterResolution": "初回対応時間は解決時間より後には設定できません", + "sla.firstResponseTimeAfterResolution": "初回応答時間は解決時間より後には設定できません", "conversationStatus.alreadyInUse": "このステータスは使用中のため削除できません。削除する前に、すべての会話からこのステータスを削除してください", "conversationStatus.cannotUpdateDefault": "デフォルトの会話ステータスは更新できません", "csat.alreadySubmitted": "CSAT はすでに送信されました", @@ -411,7 +443,7 @@ "admin.general.logoURL.description": "アプリのロゴURLです。", "admin.general.logoURL.valid": "ロゴのURLは有効なURLである必要があります", "admin.general.maxAllowedFileUploadSize": "許可されたファイルの最大アップロードサイズ", - "admin.general.maxAllowedFileUploadSize.description": "アップロード可能な最大ファイルサイズ(MB単位)。", + "admin.general.maxAllowedFileUploadSize.description": "許可されているファイルの最大アップロードサイズ(MB)。", "admin.general.maxAllowedFileUploadSize.valid": "アップロード可能な最大ファイルサイズは 1~500 MB の間で指定してください", "admin.general.allowedFileUploadExtensions": "アップロード可能なファイル拡張子", "admin.general.allowedFileUploadExtensions.description": "すべてのファイルタイプを許可するには、`*` を使用します。例: `jpg, png, pdf`", @@ -423,34 +455,34 @@ "admin.businessHours.openClose.required": "開始時間と終了時間は必須です", "admin.sla.name.valid": "SLAポリシー名は 1~255 文字の間で指定してください", "admin.sla.description.valid": "SLAポリシーの説明は 1~255 文字の間で指定してください", - "admin.sla.firstResponseTime": "初回返信時間", + "admin.sla.firstResponseTime": "初回応答時間", "admin.sla.resolutionTime": "解決時間", - "admin.sla.nextResponseTime": "次回対応時間", + "admin.sla.nextResponseTime": "追加応答時間", "admin.sla.alertConfiguration": "アラート設定", "admin.sla.alertConfiguration.description": "アラートのトリガー条件と通知先を設定", - "admin.sla.addBreachAlert": "違反アラートを追加", + "admin.sla.addBreachAlert": "超過アラートを追加", "admin.sla.addWarningAlert": "警告アラートを追加", "admin.sla.warning": "警告", - "admin.sla.breach": "違反", + "admin.sla.breach": "超過", "admin.sla.triggerTiming": "トリガータイミング", - "admin.sla.immediatelyOnBreach": "違反発生時に即時", + "admin.sla.immediatelyOnBreach": "超過発生時に即時", "admin.sla.afterSpecificDuration": "指定時間経過後", "admin.sla.advanceWarning": "事前警告", "admin.sla.followUpDelay": "フォローアップ遅延", "admin.sla.alertRecipients": "アラート受信者", "admin.sla.noAlertsConfigured": "アラートが設定されていません", - "admin.sla.atleastOneSLATimeRequired": "「初回応答時間」「次回応答時間」「解決時間」のいずれかを設定する必要があります。", + "admin.sla.atleastOneSLATimeRequired": "「初回応答時間」「追加応答時間」「解決時間」のいずれかを設定する必要があります。", "admin.conversationTags.edit.description": "タグ名を変更してください。変更が終わったら「保存」をクリックしてください。", "admin.conversationTags.new.description": "タグ名を設定してください。設定が終わったら「保存」をクリックしてください。", "admin.conversationTags.name.valid": "タグ名は少なくとも 3 文字必要です", - "admin.macro.messageContent": "マクロ使用時に送信する返信(任意)", + "admin.macro.messageContent": "マクロを使用したときに送信される応答 (オプション)", "admin.macro.actions": "アクション (任意)", "admin.macro.messageOrActionRequired": "メッセージ内容または操作のいずれかを設定する必要があります", "admin.macro.actionInvalid": "各アクションには種類と値の両方を指定する必要があります", "admin.conversationStatus.name.description": "ステータス名を設定してください。設定が終わったら「保存」をクリックしてください。", "admin.inbox.name.description": "受信トレイの名前。", "admin.inbox.fromEmailAddress.placeholder": "自分の受信トレイ ", - "admin.inbox.fromEmailAddress.description": "受信トレイの送信元メールアドレス(例:自分の受信トレイ support@example.com)", + "admin.inbox.fromEmailAddress.description": "受信トレイの送信元メールアドレス。 例:サポート ", "admin.inbox.enabled.description": "受信トレイのスキャンと送信の切替。", "admin.inbox.csatSurveys": "CSAT調査", "admin.inbox.csatSurveys.description_1": "会話が「解決済み」にマークされたときに、顧客満足度アンケートを送信する。", @@ -482,6 +514,25 @@ "admin.inbox.chooseChannel": "送信先のチャンネルを選択", "admin.inbox.configureChannel": "チャンネルの設定", "admin.inbox.createEmailInbox": "メール受信トレイを作成", + "admin.inbox.oauth.chooseSetupMethod": "セットアップ方法を選択", + "admin.inbox.oauth.selectConnectionMethod": "メールアカウントを接続する方法を選択してください", + "admin.inbox.oauth.googleDescription": "Google ワークスペースまたは Gmail に接続", + "admin.inbox.oauth.microsoftDescription": "Microsoft 365 または Outlook と接続", + "admin.inbox.oauth.otherProvider": "その他のプロバイダー", + "admin.inbox.oauth.otherProviderDescription": "IMAP および SMTP の手動設定", + "admin.inbox.oauth.connectedVia": "OAuth 経由で接続 - {provider}", + "admin.inbox.oauth.connectAccount": "{provider} アカウントと接続", + "admin.inbox.oauth.followSteps": "以下の手順に従ってメールアカウントを接続してください", + "admin.inbox.oauth.step1CreateApp": "1. OAuth アプリを作成する", + "admin.inbox.oauth.googleCloudConsole": "Google クラウドコンソール", + "admin.inbox.oauth.microsoftAzurePortal": "Microsoft Azure ポータル", + "admin.inbox.oauth.step2AddCallback": "2. このコールバックURLを追加:", + "admin.inbox.oauth.step3EnterCredentials": "3. あなたの資格情報を以下に入力してください:", + "admin.inbox.oauth.enterClientID": "OAuth クライアント ID を入力してください", + "admin.inbox.oauth.enterClientSecret": "OAuthクライアントシークレットを入力してください", + "admin.inbox.oauth.clientIDSecretRequired": "クライアントIDとクライアントシークレットの両方を入力してください", + "admin.inbox.oauth.reconnectAccount": "{provider} アカウントに再接続する", + "admin.inbox.oauth.reconnectDescription": "OAuth接続を更新するには資格情報を再入力してください", "admin.agent.deleteConfirmation": "これによりエージェントが完全に削除されます。代わりにアカウントを無効化することを検討してください。", "admin.agent.apiKey.description": "このエージェントが LibreDesk にプログラムからアクセスできるように API キーを生成します。", "admin.agent.apiKey.noKey": "この担当者にはまだ API キーが生成されていません。", @@ -495,6 +546,7 @@ "admin.role.conversations.readAll": "すべての会話を表示", "admin.role.conversations.readUnassigned": "割り当てられていないすべての会話を表示", "admin.role.conversations.readTeamInbox": "チームの受信トレイ内の会話を表示", + "admin.role.conversations.readTeamAll": "チームの会話を表示", "admin.role.conversations.updateUserAssignee": "ユーザーに会話を割り当てる", "admin.role.conversations.updateTeamAssignee": "チームに会話を割り当てる", "admin.role.conversations.updatePriority": "会話の優先度を変更", @@ -504,6 +556,7 @@ "admin.role.messages.write": "会話内でメッセージを送信", "admin.role.messages.writeAsContact": "連絡先としてメッセージを送信", "admin.role.view.manage": "会話ビューを作成・管理", + "admin.role.sharedViews.manage": "共有ビューの管理", "admin.role.generalSettings.manage": "一般設定を管理", "admin.role.notificationSettings.manage": "通知設定を管理", "admin.role.status.manage": "会話ステータスの管理", @@ -560,6 +613,7 @@ "admin.template.makeSureTemplateHasContent": "テンプレート内に {content} は1回だけ含めるようにしてください。", "admin.template.onlyOneDefaultOutgoingTemplate": "デフォルトの送信メールテンプレートは1つのみ設定できます。", "admin.sso.setThisUrlForCallback": "コールバック用の URI を設定します。", + "admin.sso.logoURLDescription": "ログインページに表示するカスタムロゴURLです。", "admin.customAttributes.regex.description": "このカスタム属性の値を検証する正規表現。検証を行わない場合は空欄にしてください。", "admin.customAttributes.regexHint.description": "正規表現パターンのヒント。", "admin.customAttributes.keyNotAllowed": "指定したキーはデフォルト属性と重複しているため使用できません。別のキーを使用してください。", @@ -577,16 +631,39 @@ "report.chart.newConversations": "新しい会話", "report.chart.resolvedConversations": "解決済みの会話", "report.chart.title": "会話の傾向", - "report.sla.cardTitle": "SLA 達成状況(過去 {days} 日間)", + "report.sla.cardTitle": "SLA 達成状況(直近 {days} 日間)", "report.sla.firstRespMet": "初回応答達成", - "report.sla.firstRespBreached": "初回応答未達", + "report.sla.firstRespBreached": "初回応答超過", "report.sla.avgFirstResp": "平均初回応答時間", - "report.sla.nextRespMet": "次回応答達成", - "report.sla.nextRespBreached": "次回応答未達", - "report.sla.avgNextResp": "平均次回応答時間", + "report.sla.nextRespMet": "追加応答達成", + "report.sla.nextRespBreached": "追加応答超過", + "report.sla.avgNextResp": "平均追加応答時間", "report.sla.resolutionMet": "解決達成", - "report.sla.resolutionBreached": "解決未達", + "report.sla.resolutionBreached": "解決超過", "report.sla.avgResolution": "平均解決時間", + "report.sla.compliance": "コンプライアンス", + "report.sla.met": "達成", + "report.sla.breached": "違反", + "report.sla.firstResponse": "初回応答", + "report.sla.nextResponse": "追加応答", + "report.sla.resolution": "解決", + "report.openConversations": "会話を開く", + "report.agentStatus": "担当者の状態", + "report.csat.title": "顧客満足度", + "report.csat.cardTitle": "顧客満足度(直近 {days} 日間)", + "report.csat.avgRating": "平均評価", + "report.csat.responseRate": "応答率", + "report.csat.responses": "応答", + "report.messages.title": "メッセージ数", + "report.messages.cardTitle": "メッセージの数(直近 {days} 日間)", + "report.messages.total": "合計", + "report.messages.incoming": "受信", + "report.messages.outgoing": "送信", + "report.messages.perConversation": "会話ごと", + "report.tags.title": "タグの分布", + "report.tags.cardTitle": "タグの分布(直近 {days} 日間)", + "report.tags.tagged": "タグを付けました", + "report.tags.topTags": "トップタグ", "search.noResultsForQuery": "「{query}」に一致する結果は見つかりませんでした。別の検索語を試してください。", "search.minQueryLength": " 検索するには、少なくとも {length} 文字を入力してください。", "search.searchBy": "参照番号、連絡先のメールアドレス、または会話内のメッセージで検索できます。", @@ -617,7 +694,7 @@ "conversation.sort.oldestActivity": "最も古いアクティビティ", "conversation.sort.newestActivity": "最新のアクティビティ", "conversation.sort.startedFirst": "最初に開始", - "conversation.sort.startedLast": "最後に開始", + "conversation.sort.startedLast": "開始時刻", "conversation.sort.waitingLongest": "待機時間最長", "conversation.sort.nextSLATarget": "次のSLA目標", "conversation.sort.priorityFirst": "優先度優先", @@ -647,5 +724,10 @@ "contact.notes.help": "この連絡先の重要な情報や会話を記録するために、ノートを追加してください。", "setup.completeYourSetup": "セットアップを完了してください", "setup.createFirstInbox": "最初の受信トレイを作成してください", - "setup.inviteTeammates": "チームメンバーを招待" -} + "setup.inviteTeammates": "チームメンバーを招待", + "importer.requiredCSVFormat": "必須のCSV形式", + "importer.importCompleted": "インポートが完了しました: {total} 件のうち {success} 件が成功しました。 {errors} 件が失敗しました", + "importer.csvMustContainHeadersAndData": "CSVにはヘッダーと少なくとも1つのデータ行が含まれている必要があります", + "importer.importAlreadyInProgress": "インポートはすでに進行中です", + "importer.agentCaseSensitiveNote": "ロールとチームは正確に一致 (大文字と小文字を区別)する必要があります" +} \ No newline at end of file diff --git a/i18n/mr.json b/i18n/mr.json index 1ec842ea..4e6a3ae4 100644 --- a/i18n/mr.json +++ b/i18n/mr.json @@ -35,7 +35,6 @@ "globals.terms.dashboard": "डॅशबोर्ड | डॅशबोर्ड्स", "globals.terms.tag": "टॅग | टॅग्ज", "globals.terms.sla": "SLA | SLAs", - "globals.terms.slaPolicy": "SLA धोरण | SLA धोरणे", "globals.terms.csatSurvey": "CSAT सर्व्हे | CSAT सर्व्हे", "globals.terms.csatResponse": "CSAT प्रतिसाद | CSAT प्रतिसाद", "globals.terms.inbox": "इनबॉक्स | इनबॉक्स", @@ -279,10 +278,8 @@ "admin.general.logoURL": "लोगो URL", "admin.general.logoURL.description": "अॅपसाठी लोगो URL.", "admin.general.logoURL.valid": "लोगो URL वैध असावा", - "admin.general.maxAllowedFileUploadSize": "कमाल फाइल अपलोड आकार", "admin.general.maxAllowedFileUploadSize.description": "MB मध्ये कमाल फाइल अपलोड आकार.", "admin.general.maxAllowedFileUploadSize.valid": "कमाल फाइल आकार 1 ते 500 MB दरम्यान असावा", - "admin.general.allowedFileUploadExtensions": "परवानगी असलेली फाइल एक्सटेंशन्स", "admin.businessHours.unauthorized": "व्यवसाय तास पाहण्याची परवानगी नाही.", "admin.businessHours.setBusinessHours": "व्यवसाय तास सेट करा", "admin.businessHours.customBusinessHours": "सानुकूल व्यवसाय तास", diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 7c250281..214fb69f 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -101,6 +101,7 @@ func New(cfg Config, i18n *i18n.I18n, rd *redis.Client, logger *logf.Logger) (*A }) st := sessredisstore.New(context.TODO(), rd) + st.SetTTL(time.Hour*9, true) sess.UseStore(st) sess.SetCookieHooks(simpleSessGetCookieCB, simpleSessSetCookieCB) diff --git a/internal/inbox/channel/email/email.go b/internal/inbox/channel/email/email.go index 53eed3ce..8b437aa3 100644 --- a/internal/inbox/channel/email/email.go +++ b/internal/inbox/channel/email/email.go @@ -33,6 +33,7 @@ type Email struct { headers map[string]string lo *logf.Logger from string + enablePlusAddressing bool messageStore inbox.MessageStore userStore inbox.UserStore wg sync.WaitGroup @@ -77,6 +78,7 @@ func New(store inbox.MessageStore, userStore inbox.UserStore, opts Opts) (*Email userStore: userStore, oauth: opts.Config.OAuth, authType: opts.Config.AuthType, + enablePlusAddressing: opts.Config.EnablePlusAddressing, tokenRefreshCallback: opts.TokenRefreshCallback, } return e, nil @@ -124,11 +126,12 @@ func (e *Email) getCurrentConfig() models.Config { e.oauthMu.RUnlock() return models.Config{ - SMTP: e.smtpCfg, - IMAP: e.imapCfg, - From: e.from, - OAuth: oauth, - AuthType: e.authType, + SMTP: e.smtpCfg, + IMAP: e.imapCfg, + From: e.from, + OAuth: oauth, + AuthType: e.authType, + EnablePlusAddressing: e.enablePlusAddressing, } } diff --git a/internal/inbox/channel/email/smtp.go b/internal/inbox/channel/email/smtp.go index 20f11fe2..588fcc99 100644 --- a/internal/inbox/channel/email/smtp.go +++ b/internal/inbox/channel/email/smtp.go @@ -180,9 +180,9 @@ func (e *Email) Send(m models.OutboundMessage) error { } email.Headers.Set(headerLibredeskLoopPrevention, emailAddress) - // Set Reply-To with plus-addressing for conversation matching + // Set Reply-To with plus-addressing for conversation matching (if enabled) // e.g., support@company.com → support+conv-{uuid}@company.com - if m.ConversationUUID != "" { + if e.enablePlusAddressing && m.ConversationUUID != "" { replyToAddr := buildPlusAddress(emailAddress, m.ConversationUUID) email.Headers.Set("Reply-To", replyToAddr) e.lo.Debug("Reply-To header set with plus-addressing", "reply_to", replyToAddr) diff --git a/internal/inbox/inbox.go b/internal/inbox/inbox.go index 642ff887..22939ea8 100644 --- a/internal/inbox/inbox.go +++ b/internal/inbox/inbox.go @@ -332,16 +332,18 @@ func (m *Manager) Update(id int, inbox imodels.Inbox) (imodels.Inbox, error) { switch current.Channel { case "email": var currentCfg struct { - AuthType string `json:"auth_type"` - OAuth map[string]string `json:"oauth"` - IMAP []map[string]interface{} `json:"imap"` - SMTP []map[string]interface{} `json:"smtp"` + AuthType string `json:"auth_type"` + OAuth map[string]string `json:"oauth"` + IMAP []map[string]any `json:"imap"` + SMTP []map[string]any `json:"smtp"` + EnablePlusAddressing bool `json:"enable_plus_addressing"` } var updateCfg struct { - AuthType string `json:"auth_type"` - OAuth map[string]string `json:"oauth"` - IMAP []map[string]interface{} `json:"imap"` - SMTP []map[string]interface{} `json:"smtp"` + AuthType string `json:"auth_type"` + OAuth map[string]string `json:"oauth"` + IMAP []map[string]any `json:"imap"` + SMTP []map[string]any `json:"smtp"` + EnablePlusAddressing bool `json:"enable_plus_addressing"` } if err := json.Unmarshal(current.Config, ¤tCfg); err != nil { @@ -541,15 +543,15 @@ func (m *Manager) encryptInboxConfig(config json.RawMessage) (json.RawMessage, e return config, nil } - var cfg map[string]interface{} + var cfg map[string]any if err := json.Unmarshal(config, &cfg); err != nil { return nil, fmt.Errorf("unmarshalling config: %w", err) } // Encrypt SMTP passwords - if smtpSlice, ok := cfg["smtp"].([]interface{}); ok { + if smtpSlice, ok := cfg["smtp"].([]any); ok { for i, smtpItem := range smtpSlice { - if smtpMap, ok := smtpItem.(map[string]interface{}); ok { + if smtpMap, ok := smtpItem.(map[string]any); ok { if password, ok := smtpMap["password"].(string); ok && password != "" { encrypted, err := crypto.Encrypt(password, m.encryptionKey) if err != nil { @@ -562,9 +564,9 @@ func (m *Manager) encryptInboxConfig(config json.RawMessage) (json.RawMessage, e } // Encrypt IMAP passwords - if imapSlice, ok := cfg["imap"].([]interface{}); ok { + if imapSlice, ok := cfg["imap"].([]any); ok { for i, imapItem := range imapSlice { - if imapMap, ok := imapItem.(map[string]interface{}); ok { + if imapMap, ok := imapItem.(map[string]any); ok { if password, ok := imapMap["password"].(string); ok && password != "" { encrypted, err := crypto.Encrypt(password, m.encryptionKey) if err != nil { @@ -577,7 +579,7 @@ func (m *Manager) encryptInboxConfig(config json.RawMessage) (json.RawMessage, e } // Encrypt OAuth fields if present - if oauthMap, ok := cfg["oauth"].(map[string]interface{}); ok { + if oauthMap, ok := cfg["oauth"].(map[string]any); ok { fields := []string{"client_secret", "access_token", "refresh_token"} for _, fieldName := range fields { if fieldValue, ok := oauthMap[fieldName].(string); ok && fieldValue != "" { @@ -604,15 +606,15 @@ func (m *Manager) decryptInboxConfig(config json.RawMessage) (json.RawMessage, e return config, nil } - var cfg map[string]interface{} + var cfg map[string]any if err := json.Unmarshal(config, &cfg); err != nil { return nil, fmt.Errorf("unmarshalling config: %w", err) } // Decrypt SMTP passwords - if smtpSlice, ok := cfg["smtp"].([]interface{}); ok { + if smtpSlice, ok := cfg["smtp"].([]any); ok { for i, smtpItem := range smtpSlice { - if smtpMap, ok := smtpItem.(map[string]interface{}); ok { + if smtpMap, ok := smtpItem.(map[string]any); ok { if password, ok := smtpMap["password"].(string); ok && password != "" { decrypted, err := crypto.Decrypt(password, m.encryptionKey) if err != nil { @@ -625,9 +627,9 @@ func (m *Manager) decryptInboxConfig(config json.RawMessage) (json.RawMessage, e } // Decrypt IMAP passwords - if imapSlice, ok := cfg["imap"].([]interface{}); ok { + if imapSlice, ok := cfg["imap"].([]any); ok { for i, imapItem := range imapSlice { - if imapMap, ok := imapItem.(map[string]interface{}); ok { + if imapMap, ok := imapItem.(map[string]any); ok { if password, ok := imapMap["password"].(string); ok && password != "" { decrypted, err := crypto.Decrypt(password, m.encryptionKey) if err != nil { @@ -640,7 +642,7 @@ func (m *Manager) decryptInboxConfig(config json.RawMessage) (json.RawMessage, e } // Decrypt OAuth fields if present - if oauthMap, ok := cfg["oauth"].(map[string]interface{}); ok { + if oauthMap, ok := cfg["oauth"].(map[string]any); ok { fields := []string{"client_secret", "access_token", "refresh_token"} for _, fieldName := range fields { if fieldValue, ok := oauthMap[fieldName].(string); ok && fieldValue != "" { diff --git a/internal/inbox/models/models.go b/internal/inbox/models/models.go index 763fc0a8..1f30a482 100644 --- a/internal/inbox/models/models.go +++ b/internal/inbox/models/models.go @@ -34,11 +34,12 @@ type Inbox struct { // Config holds the email inbox configuration with multiple SMTP servers and IMAP clients. type Config struct { - AuthType string `json:"auth_type"` // AuthTypePassword or AuthTypeOAuth2 - OAuth *OAuthConfig `json:"oauth"` // OAuth config when auth_type is "oauth2" - SMTP []SMTPConfig `json:"smtp"` - IMAP []IMAPConfig `json:"imap"` - From string `json:"from"` + AuthType string `json:"auth_type"` // AuthTypePassword or AuthTypeOAuth2 + OAuth *OAuthConfig `json:"oauth"` // OAuth config when auth_type is "oauth2" + SMTP []SMTPConfig `json:"smtp"` + IMAP []IMAPConfig `json:"imap"` + From string `json:"from"` + EnablePlusAddressing bool `json:"enable_plus_addressing"` // Enable plus-addressing in Reply-To header for conversation matching } // OAuthConfig holds OAuth 2.0 authentication details. diff --git a/internal/migrations/v1.0.1.go b/internal/migrations/v1.0.1.go new file mode 100644 index 00000000..c12ecc16 --- /dev/null +++ b/internal/migrations/v1.0.1.go @@ -0,0 +1,20 @@ +package migrations + +import ( + "github.com/jmoiron/sqlx" + "github.com/knadh/koanf/v2" + "github.com/knadh/stuffbin" +) + +// V1_0_1 updates the database schema to v1.0.1. +func V1_0_1(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error { + // Backfill enable_plus_addressing to true for existing email inboxes + // that don't have this field in their config JSON. + _, err := db.Exec(` + UPDATE inboxes + SET config = jsonb_set(config, '{enable_plus_addressing}', 'true'::jsonb, true) + WHERE channel = 'email' + AND NOT (config ? 'enable_plus_addressing'); + `) + return err +} diff --git a/internal/migrations/v0.12.0.go b/internal/migrations/v2.0.0.go similarity index 97% rename from internal/migrations/v0.12.0.go rename to internal/migrations/v2.0.0.go index 3e74f83f..4f83e92e 100644 --- a/internal/migrations/v0.12.0.go +++ b/internal/migrations/v2.0.0.go @@ -6,8 +6,8 @@ import ( "github.com/knadh/stuffbin" ) -// V0_12_0 updates the database schema to v0.12.0 (Live Chat feature). -func V0_12_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error { +// V2_0_0 updates the database schema to v2.0.0 (Live Chat feature). +func V2_0_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error { // Add 'livechat' to the channels enum if not already present var exists bool err := db.Get(&exists, `