Skip to content

Commit 72b92d6

Browse files
committed
feat: translate email notifications
1 parent 497b54f commit 72b92d6

File tree

5 files changed

+79
-87
lines changed

5 files changed

+79
-87
lines changed

cmd/settings.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ func handleUpdateGeneralSettings(r *fastglue.Request) error {
5353
}
5454
// Reload the settings and templates.
5555
if err := reloadSettings(app); err != nil {
56-
return envelope.NewError(envelope.GeneralError, app.i18n.Ts("app.couldNotReload", "name", app.i18n.T("globals.entities.setting")), nil)
56+
return envelope.NewError(envelope.GeneralError, app.i18n.Ts("globals.messages.couldNotReload", "name", app.i18n.T("globals.entities.setting")), nil)
5757
}
5858
if err := reloadTemplates(app); err != nil {
59-
return envelope.NewError(envelope.GeneralError, app.i18n.Ts("app.couldNotReload", "name", app.i18n.T("globals.entities.setting")), nil)
59+
return envelope.NewError(envelope.GeneralError, app.i18n.Ts("globals.messages.couldNotReload", "name", app.i18n.T("globals.entities.setting")), nil)
6060
}
6161
return r.SendEnvelope(true)
6262
}

frontend/src/features/admin/notification/NotificationSetting.vue

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@
2121
import { ref, onMounted } from 'vue'
2222
import api from '@/api'
2323
import AdminPageWithHelp from '@/layouts/admin/AdminPageWithHelp.vue'
24-
24+
import { useI18n } from 'vue-i18n'
2525
import NotificationsForm from './NotificationSettingForm.vue'
2626
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
2727
import { useEmitter } from '@/composables/useEmitter'
2828
import { handleHTTPError } from '@/utils/http'
2929
import { Spinner } from '@/components/ui/spinner'
3030
3131
const initialValues = ref({})
32+
const { t } = useI18n()
3233
const isLoading = ref(false)
3334
const emitter = useEmitter()
3435
@@ -48,7 +49,6 @@ const getNotificationSettings = async () => {
4849
)
4950
} catch (error) {
5051
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
51-
title: 'Error',
5252
variant: 'destructive',
5353
description: handleHTTPError(error).message
5454
})
@@ -69,14 +69,11 @@ const submitForm = async (values) => {
6969
)
7070
await api.updateEmailNotificationSettings(updatedValues)
7171
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
72-
title: 'Success',
73-
description:
74-
'Settings updated successfully, Please restart the app for changes to take effect.'
72+
description: t('admin.notifications.restartApp')
7573
})
7674
await getNotificationSettings()
7775
} catch (error) {
7876
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
79-
title: 'Error',
8077
variant: 'destructive',
8178
description: handleHTTPError(error).message
8279
})

frontend/src/features/admin/notification/NotificationSettingForm.vue

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<FormControl>
77
<div class="flex items-center space-x-2">
88
<Checkbox :checked="value" @update:checked="handleChange" />
9-
<Label>Enabled</Label>
9+
<Label>{{ $t('form.field.enabled') }}</Label>
1010
</div>
1111
</FormControl>
1212
<FormMessage />
@@ -16,7 +16,7 @@
1616
<!-- SMTP Host Field -->
1717
<FormField v-slot="{ componentField }" name="host">
1818
<FormItem>
19-
<FormLabel>SMTP Host</FormLabel>
19+
<FormLabel>{{ $t('form.field.smtpHost') }}</FormLabel>
2020
<FormControl>
2121
<Input type="text" placeholder="smtp.gmail.com" v-bind="componentField" />
2222
</FormControl>
@@ -27,7 +27,7 @@
2727
<!-- SMTP Port Field -->
2828
<FormField v-slot="{ componentField }" name="port">
2929
<FormItem>
30-
<FormLabel>SMTP Port</FormLabel>
30+
<FormLabel>{{ $t('form.field.smtpPort') }}</FormLabel>
3131
<FormControl>
3232
<Input type="number" placeholder="587" v-bind="componentField" />
3333
</FormControl>
@@ -38,7 +38,7 @@
3838
<!-- Username Field -->
3939
<FormField v-slot="{ componentField }" name="username">
4040
<FormItem>
41-
<FormLabel>Username</FormLabel>
41+
<FormLabel>{{ $t('form.field.username') }}</FormLabel>
4242
<FormControl>
4343
<Input type="text" placeholder="[email protected]" v-bind="componentField" />
4444
</FormControl>
@@ -49,9 +49,9 @@
4949
<!-- Password Field -->
5050
<FormField v-slot="{ componentField }" name="password">
5151
<FormItem>
52-
<FormLabel>Password</FormLabel>
52+
<FormLabel>{{ $t('form.field.password') }}</FormLabel>
5353
<FormControl>
54-
<Input type="password" placeholder="Enter your password" v-bind="componentField" />
54+
<Input type="password" placeholder="" v-bind="componentField" />
5555
</FormControl>
5656
<FormMessage />
5757
</FormItem>
@@ -60,53 +60,51 @@
6060
<!-- Max Connections Field -->
6161
<FormField v-slot="{ componentField }" name="max_conns">
6262
<FormItem>
63-
<FormLabel>Max Connections</FormLabel>
63+
<FormLabel>{{ $t('admin.inbox.max_connections') }}</FormLabel>
6464
<FormControl>
6565
<Input type="number" placeholder="2" v-bind="componentField" />
6666
</FormControl>
6767
<FormMessage />
68-
<FormDescription> Maximum concurrent connections to the server. </FormDescription>
68+
<FormDescription>{{ $t('admin.inbox.max_connections.description') }} </FormDescription>
6969
</FormItem>
7070
</FormField>
7171

7272
<!-- Idle Timeout Field -->
7373
<FormField v-slot="{ componentField }" name="idle_timeout">
7474
<FormItem>
75-
<FormLabel>Idle Timeout</FormLabel>
75+
<FormLabel>{{ $t('admin.inbox.idle_timeout') }}</FormLabel>
7676
<FormControl>
7777
<Input type="text" placeholder="15s" v-bind="componentField" />
7878
</FormControl>
7979
<FormMessage />
8080
<FormDescription>
81-
Time to wait for new activity on a connection before closing it and removing it from the
82-
pool (s for second, m for minute)
81+
{{ $t('admin.inbox.idle_timeout.description') }}
8382
</FormDescription>
8483
</FormItem>
8584
</FormField>
8685

8786
<!-- Wait Timeout Field -->
8887
<FormField v-slot="{ componentField }" name="wait_timeout">
8988
<FormItem>
90-
<FormLabel>Wait Timeout</FormLabel>
89+
<FormLabel>{{ $t('admin.inbox.wait_timeout') }}</FormLabel>
9190
<FormControl>
9291
<Input type="text" placeholder="5s" v-bind="componentField" />
9392
</FormControl>
9493
<FormMessage />
9594
<FormDescription>
96-
Time to wait for new activity on a connection before closing it and removing it from the
97-
pool (s for second, m for minute, h for hour).
95+
{{ $t('admin.inbox.wait_timeout.description') }}
9896
</FormDescription>
9997
</FormItem>
10098
</FormField>
10199

102100
<!-- Authentication Protocol Field -->
103101
<FormField v-slot="{ componentField }" name="auth_protocol">
104102
<FormItem>
105-
<FormLabel>Authentication Protocol</FormLabel>
103+
<FormLabel>{{ $t('admin.inbox.auth_protocol') }}</FormLabel>
106104
<FormControl>
107105
<Select v-bind="componentField" v-model="componentField.modelValue">
108106
<SelectTrigger>
109-
<SelectValue placeholder="Select an authentication protocol" />
107+
<SelectValue :placeholder="t('admin.inbox.auth_protocol.description')" />
110108
</SelectTrigger>
111109
<SelectContent>
112110
<SelectGroup>
@@ -125,42 +123,40 @@
125123
<!-- Email Address Field -->
126124
<FormField v-slot="{ componentField }" name="email_address">
127125
<FormItem>
128-
<FormLabel>From Email Address</FormLabel>
126+
<FormLabel>{{ $t('form.field.from_email_address') }}</FormLabel>
129127
<FormControl>
130128
<Input
131129
type="text"
132-
placeholder="From email address. e.g. My Support <[email protected]>"
130+
:placeholder="t('admin.inbox.from_email_address.placeholder')"
133131
v-bind="componentField"
134132
/>
135133
</FormControl>
136134
<FormMessage />
137-
<FormDescription
138-
>From email address. e.g. My Support &lt;[email protected]&gt;</FormDescription
139-
>
135+
<FormDescription> {{ $t('admin.inbox.from_email_address.description') }}</FormDescription>
140136
</FormItem>
141137
</FormField>
142138

143139
<!-- Max Message Retries Field -->
144140
<FormField v-slot="{ componentField }" name="max_msg_retries">
145141
<FormItem>
146-
<FormLabel>Max Message Retries</FormLabel>
142+
<FormLabel>{{ $t('admin.inbox.max_retries') }}</FormLabel>
147143
<FormControl>
148-
<Input type="number" placeholder="2" v-bind="componentField" />
144+
<Input type="number" placeholder="3" v-bind="componentField" />
149145
</FormControl>
150146
<FormMessage />
151-
<FormDescription> Number of times to retry when a message fails. </FormDescription>
147+
<FormDescription> {{ $t('admin.inbox.max_retries.description') }} </FormDescription>
152148
</FormItem>
153149
</FormField>
154150

155151
<!-- HELO Hostname Field -->
156152
<FormField v-slot="{ componentField }" name="hello_hostname">
157153
<FormItem>
158-
<FormLabel>HELO Hostname</FormLabel>
154+
<FormLabel>{{ $t('admin.inbox.helo_hostname') }}</FormLabel>
159155
<FormControl>
160-
<Input type="text" placeholder="smtp.example.com" v-bind="componentField" />
156+
<Input type="text" placeholder="" v-bind="componentField" />
161157
</FormControl>
162158
<FormDescription>
163-
The hostname to use in the HELO/EHLO command. If not set, defaults to localhost.
159+
{{ $t('admin.inbox.helo_hostname.description') }}
164160
</FormDescription>
165161
<FormMessage />
166162
</FormItem>
@@ -173,7 +169,7 @@
173169
<FormControl>
174170
<Select v-bind="componentField" v-model="componentField.modelValue">
175171
<SelectTrigger>
176-
<SelectValue placeholder="Select a TLS type" />
172+
<SelectValue :placeholder="t('form.field.selectTLS')" />
177173
</SelectTrigger>
178174
<SelectContent>
179175
<SelectGroup>
@@ -192,8 +188,8 @@
192188
<FormField v-slot="{ componentField, handleChange }" name="tls_skip_verify">
193189
<FormItem class="flex flex-row items-center justify-between box p-4">
194190
<div class="space-y-0.5">
195-
<FormLabel class="text-base">Skip TLS Verification</FormLabel>
196-
<FormDescription> Skip hostname check on the TLS certificate. </FormDescription>
191+
<FormLabel class="text-base">{{ $t('admin.inbox.skipTLSVerification') }}</FormLabel>
192+
<FormDescription>{{ $t('admin.inbox.skipTLSVerification.description') }}</FormDescription>
197193
</div>
198194
<FormControl>
199195
<Switch :checked="componentField.modelValue" @update:checked="handleChange" />
@@ -206,11 +202,11 @@
206202
</template>
207203

208204
<script setup>
209-
import { watch, ref } from 'vue'
205+
import { watch, ref, computed } from 'vue'
210206
import { Button } from '@/components/ui/button'
211207
import { useForm } from 'vee-validate'
212208
import { toTypedSchema } from '@vee-validate/zod'
213-
import { smtpConfigSchema } from './formSchema.js'
209+
import { createFormSchema } from './formSchema.js'
214210
import {
215211
FormControl,
216212
FormField,
@@ -231,8 +227,10 @@ import { Checkbox } from '@/components/ui/checkbox'
231227
import { Switch } from '@/components/ui/switch'
232228
import { Label } from '@/components/ui/label'
233229
import { Input } from '@/components/ui/input'
230+
import { useI18n } from 'vue-i18n'
234231
235232
const isLoading = ref(false)
233+
const { t } = useI18n()
236234
const props = defineProps({
237235
initialValues: {
238236
type: Object,
@@ -245,12 +243,19 @@ const props = defineProps({
245243
submitLabel: {
246244
type: String,
247245
required: false,
248-
default: () => 'Save'
246+
default: () => ''
247+
}
248+
})
249+
250+
const submitLabel = computed(() => {
251+
if (props.submitLabel) {
252+
return props.submitLabel
249253
}
254+
return t('globals.buttons.save')
250255
})
251256
252257
const smtpForm = useForm({
253-
validationSchema: toTypedSchema(smtpConfigSchema)
258+
validationSchema: toTypedSchema(createFormSchema(t))
254259
})
255260
256261
const onSmtpSubmit = smtpForm.handleSubmit(async (values) => {

frontend/src/features/admin/notification/formSchema.js

Lines changed: 25 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,55 @@
11
import * as z from 'zod';
22
import { isGoDuration } from '@/utils/strings';
33

4-
export const smtpConfigSchema = z.object({
5-
enabled: z.boolean().describe('Enabled status').default(false),
6-
username: z.string().describe('SMTP username').nonempty({
7-
message: "SMTP username is required"
4+
export const createFormSchema = (t) => z.object({
5+
enabled: z.boolean().default(false),
6+
username: z.string().nonempty({
7+
message: t('globals.messages.required')
88
}),
9-
host: z.string().describe('SMTP host').nonempty({
10-
message: "SMTP host is required"
9+
host: z.string().nonempty({
10+
message: t('globals.messages.required')
1111
}),
1212
port: z
1313
.number({
14-
invalid_type_error: 'Port must be a number.',
15-
required_error: 'Port is required.'
14+
invalid_type_error: t('globals.messages.invalidPortNumber'),
15+
required_error: t('globals.messages.required')
1616
})
17-
.min(1, {
18-
message: 'Port must be at least 1.'
19-
})
20-
.max(65535, {
21-
message: 'Port must be at most 65535.'
22-
})
23-
.describe('SMTP port')
17+
.min(1, { message: t('form.error.minmaxNumber', { min: 1, max: 65535 }) })
18+
.max(65535, { message: t('form.error.minmaxNumber', { min: 1, max: 65535 }) })
2419
.default(587),
25-
password: z.string().describe('SMTP password').nonempty({
26-
message: "SMTP password is required"
20+
password: z.string().nonempty({
21+
message: t('globals.messages.required')
2722
}),
2823
max_conns: z
2924
.number({
30-
invalid_type_error: 'Must be a number.',
31-
required_error: 'Maximum connections is required.'
32-
})
33-
.min(1, {
34-
message: 'Maximum connections must be at least 1.'
25+
invalid_type_error: t('globals.messages.mustBeNumber'),
26+
required_error: t('globals.messages.required')
3527
})
36-
.describe('Maximum concurrent connections'),
28+
.min(1, { message: t('form.error.minmaxNumber', { min: 1, max: 1000 }) })
29+
.max(1000, { message: t('form.error.minmaxNumber', { min: 1, max: 1000 }) }),
3730
idle_timeout: z
3831
.string()
39-
.describe('Idle timeout duration')
4032
.refine(isGoDuration, {
41-
message: 'Invalid duration format. Should be a number followed by s (seconds), m (minutes), or h (hours).'
33+
message: t('globals.messages.goDuration')
4234
})
4335
.default('15s'),
4436
wait_timeout: z
4537
.string()
46-
.describe('Wait timeout duration')
4738
.refine(isGoDuration, {
48-
message: 'Invalid duration format. Should be a number followed by s (seconds), m (minutes), or h (hours).'
39+
message: t('globals.messages.goDuration')
4940
})
5041
.default('5s'),
51-
auth_protocol: z
52-
.enum(['plain', 'login', 'cram', 'none'])
53-
.describe('Authentication protocol'),
54-
email_address: z.string().describe('From email address with name (e.g., "Name <[email protected]>")').nonempty({
55-
message: "From email address is required"
42+
auth_protocol: z.enum(['plain', 'login', 'cram', 'none']),
43+
email_address: z.string().nonempty({
44+
message: t('globals.messages.required')
5645
}),
5746
max_msg_retries: z
5847
.number({
59-
invalid_type_error: 'Must be a number.',
60-
required_error: 'Maximum message retries is required.'
61-
})
62-
.min(0, {
63-
message: 'Max message retries must be at least 0.'
64-
})
65-
.max(100, {
66-
message: 'Max message retries must be at most 100.'
48+
invalid_type_error: t('globals.messages.mustBeNumber'),
49+
required_error: t('globals.messages.required')
6750
})
68-
.describe('Maximum message retries')
51+
.min(0, { message: t('form.error.minmaxNumber', { min: 0, max: 1000 }) })
52+
.max(1000, { message: t('form.error.minmaxNumber', { min: 0, max: 1000 }) })
6953
.default(2),
7054
hello_hostname: z.string().optional(),
7155
tls_type: z.enum(['none', 'starttls', 'tls']),

0 commit comments

Comments
 (0)