mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-03-30 19:26:37 +00:00
feat: add support for translations (#349)
Co-authored-by: Kyle Mendell <kmendell@outlook.com> Co-authored-by: Elias Schneider <login@eliasschneider.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
import AppConfigGeneralForm from './forms/app-config-general-form.svelte';
|
||||
import AppConfigLdapForm from './forms/app-config-ldap-form.svelte';
|
||||
import UpdateApplicationImages from './update-application-images.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
|
||||
let { data } = $props();
|
||||
let appConfig = $state(data.appConfig);
|
||||
@@ -46,36 +47,35 @@
|
||||
: Promise.resolve();
|
||||
|
||||
await Promise.all([lightLogoPromise, darkLogoPromise, backgroundImagePromise, faviconPromise])
|
||||
.then(() => toast.success('Images updated successfully'))
|
||||
.then(() => toast.success(m.images_updated_successfully()))
|
||||
.catch(axiosErrorToast);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Application Configuration</title>
|
||||
<title>{m.application_configuration()}</title>
|
||||
</svelte:head>
|
||||
|
||||
<CollapsibleCard id="application-configuration-general" title="General" defaultExpanded>
|
||||
<CollapsibleCard id="application-configuration-general" title={m.general()} defaultExpanded>
|
||||
<AppConfigGeneralForm {appConfig} callback={updateAppConfig} />
|
||||
</CollapsibleCard>
|
||||
|
||||
<CollapsibleCard
|
||||
id="application-configuration-email"
|
||||
title="Email"
|
||||
description="Enable email notifications to alert users when a login is detected from a new device or
|
||||
location."
|
||||
title={m.email()}
|
||||
description={m.enable_email_notifications_to_alert_users_when_a_login_is_detected_from_a_new_device_or_location()}
|
||||
>
|
||||
<AppConfigEmailForm {appConfig} callback={updateAppConfig} />
|
||||
</CollapsibleCard>
|
||||
|
||||
<CollapsibleCard
|
||||
id="application-configuration-ldap"
|
||||
title="LDAP"
|
||||
description="Configure LDAP settings to sync users and groups from an LDAP server."
|
||||
title={m.ldap()}
|
||||
description={m.configure_ldap_settings_to_sync_users_and_groups_from_an_ldap_server()}
|
||||
>
|
||||
<AppConfigLdapForm {appConfig} callback={updateAppConfig} />
|
||||
</CollapsibleCard>
|
||||
|
||||
<CollapsibleCard id="application-configuration-images" title="Images">
|
||||
<CollapsibleCard id="application-configuration-images" title={m.images()}>
|
||||
<UpdateApplicationImages callback={updateImages} />
|
||||
</CollapsibleCard>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import FileInput from '$lib/components/form/file-input.svelte';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import { cn } from '$lib/utils/style';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
@@ -60,7 +61,7 @@
|
||||
<span
|
||||
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform font-medium opacity-0 transition-opacity group-hover:opacity-100"
|
||||
>
|
||||
Update
|
||||
{m.update()}
|
||||
</span>
|
||||
</div>
|
||||
</FileInput>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import AppConfigService from '$lib/services/app-config-service';
|
||||
import type { AllAppConfig } from '$lib/types/application-configuration';
|
||||
import { createForm } from '$lib/utils/form-util';
|
||||
@@ -55,7 +56,7 @@
|
||||
appConfig[key] = value;
|
||||
});
|
||||
|
||||
toast.success('Email configuration updated successfully');
|
||||
toast.success(m.email_configuration_updated_successfully());
|
||||
return true;
|
||||
}
|
||||
async function onTestEmail() {
|
||||
@@ -64,11 +65,11 @@
|
||||
|
||||
if (hasChanges) {
|
||||
openConfirmDialog({
|
||||
title: 'Save changes?',
|
||||
title: m.save_changes_question(),
|
||||
message:
|
||||
'You have to save the changes before sending a test email. Do you want to save now?',
|
||||
m.you_have_to_save_the_changes_before_sending_a_test_email_do_you_want_to_save_now(),
|
||||
confirm: {
|
||||
label: 'Save and send',
|
||||
label: m.save_and_send(),
|
||||
action: async () => {
|
||||
const saved = await onSubmit();
|
||||
if (saved) {
|
||||
@@ -86,9 +87,9 @@
|
||||
isSendingTestEmail = true;
|
||||
await appConfigService
|
||||
.sendTestEmail()
|
||||
.then(() => toast.success('Test email sent successfully to your email address.'))
|
||||
.then(() => toast.success(m.test_email_sent_successfully()))
|
||||
.catch(() =>
|
||||
toast.error('Failed to send test email. Check the server logs for more information.')
|
||||
toast.error(m.failed_to_send_test_email())
|
||||
)
|
||||
.finally(() => (isSendingTestEmail = false));
|
||||
}
|
||||
@@ -96,21 +97,21 @@
|
||||
|
||||
<form onsubmit={onSubmit}>
|
||||
<fieldset disabled={uiConfigDisabled}>
|
||||
<h4 class="text-lg font-semibold">SMTP Configuration</h4>
|
||||
<h4 class="text-lg font-semibold">{m.smtp_configuration()}</h4>
|
||||
<div class="mt-4 grid grid-cols-1 items-end gap-5 md:grid-cols-2">
|
||||
<FormInput label="SMTP Host" bind:input={$inputs.smtpHost} />
|
||||
<FormInput label="SMTP Port" type="number" bind:input={$inputs.smtpPort} />
|
||||
<FormInput label="SMTP User" bind:input={$inputs.smtpUser} />
|
||||
<FormInput label="SMTP Password" type="password" bind:input={$inputs.smtpPassword} />
|
||||
<FormInput label="SMTP From" bind:input={$inputs.smtpFrom} />
|
||||
<FormInput label={m.smtp_host()} bind:input={$inputs.smtpHost} />
|
||||
<FormInput label={m.smtp_port()} type="number" bind:input={$inputs.smtpPort} />
|
||||
<FormInput label={m.smtp_user()} bind:input={$inputs.smtpUser} />
|
||||
<FormInput label={m.smtp_password()} type="password" bind:input={$inputs.smtpPassword} />
|
||||
<FormInput label={m.smtp_from()} bind:input={$inputs.smtpFrom} />
|
||||
<div class="grid gap-2">
|
||||
<Label class="mb-0" for="smtp-tls">SMTP TLS Option</Label>
|
||||
<Label class="mb-0" for="smtp-tls">{m.smtp_tls_option()}</Label>
|
||||
<Select.Root
|
||||
selected={{ value: $inputs.smtpTls.value, label: tlsOptions[$inputs.smtpTls.value] }}
|
||||
onSelectedChange={(v) => ($inputs.smtpTls.value = v!.value)}
|
||||
>
|
||||
<Select.Trigger>
|
||||
<Select.Value placeholder="Email TLS Option" />
|
||||
<Select.Value placeholder={m.email_tls_option()} />
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="none" label="None" />
|
||||
@@ -121,31 +122,31 @@
|
||||
</div>
|
||||
<CheckboxWithLabel
|
||||
id="skip-cert-verify"
|
||||
label="Skip Certificate Verification"
|
||||
description="This can be useful for self-signed certificates."
|
||||
label={m.skip_certificate_verification()}
|
||||
description={m.this_can_be_useful_for_selfsigned_certificates()}
|
||||
bind:checked={$inputs.smtpSkipCertVerify.value}
|
||||
/>
|
||||
</div>
|
||||
<h4 class="mt-10 text-lg font-semibold">Enabled Emails</h4>
|
||||
<h4 class="mt-10 text-lg font-semibold">{m.enabled_emails()}</h4>
|
||||
<div class="mt-4 flex flex-col gap-5">
|
||||
<CheckboxWithLabel
|
||||
id="email-login-notification"
|
||||
label="Email Login Notification"
|
||||
description="Send an email to the user when they log in from a new device."
|
||||
label={m.email_login_notification()}
|
||||
description={m.send_an_email_to_the_user_when_they_log_in_from_a_new_device()}
|
||||
bind:checked={$inputs.emailLoginNotificationEnabled.value}
|
||||
/>
|
||||
<CheckboxWithLabel
|
||||
id="email-login"
|
||||
label="Email Login"
|
||||
description="Allows users to sign in with a login code sent to their email. This reduces the security significantly as anyone with access to the user's email can gain entry."
|
||||
label={m.email_login()}
|
||||
description={m.allow_users_to_sign_in_with_a_login_code_sent_to_their_email()}
|
||||
bind:checked={$inputs.emailOneTimeAccessEnabled.value}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="mt-8 flex flex-wrap justify-end gap-3">
|
||||
<Button isLoading={isSendingTestEmail} variant="secondary" onclick={onTestEmail}
|
||||
>Send test email</Button
|
||||
>{m.send_test_email()}</Button
|
||||
>
|
||||
<Button type="submit" disabled={uiConfigDisabled}>Save</Button>
|
||||
<Button type="submit" disabled={uiConfigDisabled}>{m.save()}</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import CheckboxWithLabel from '$lib/components/form/checkbox-with-label.svelte';
|
||||
import FormInput from '$lib/components/form/form-input.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import type { AllAppConfig } from '$lib/types/application-configuration';
|
||||
import { createForm } from '$lib/utils/form-util';
|
||||
import { toast } from 'svelte-sonner';
|
||||
@@ -39,35 +40,35 @@
|
||||
if (!data) return;
|
||||
isLoading = true;
|
||||
await callback(data).finally(() => (isLoading = false));
|
||||
toast.success('Application configuration updated successfully');
|
||||
toast.success(m.application_configuration_updated_successfully());
|
||||
}
|
||||
</script>
|
||||
|
||||
<form onsubmit={onSubmit}>
|
||||
<fieldset class="flex flex-col gap-5" disabled={uiConfigDisabled}>
|
||||
<div class="flex flex-col gap-5">
|
||||
<FormInput label="Application Name" bind:input={$inputs.appName} />
|
||||
<FormInput label={m.application_name()} bind:input={$inputs.appName} />
|
||||
<FormInput
|
||||
label="Session Duration"
|
||||
label={m.session_duration()}
|
||||
type="number"
|
||||
description="The duration of a session in minutes before the user has to sign in again."
|
||||
description={m.the_duration_of_a_session_in_minutes_before_the_user_has_to_sign_in_again()}
|
||||
bind:input={$inputs.sessionDuration}
|
||||
/>
|
||||
<CheckboxWithLabel
|
||||
id="self-account-editing"
|
||||
label="Enable Self-Account Editing"
|
||||
description="Whether the users should be able to edit their own account details."
|
||||
label={m.enable_self_account_editing()}
|
||||
description={m.whether_the_users_should_be_able_to_edit_their_own_account_details()}
|
||||
bind:checked={$inputs.allowOwnAccountEdit.value}
|
||||
/>
|
||||
<CheckboxWithLabel
|
||||
id="emails-verified"
|
||||
label="Emails Verified"
|
||||
description="Whether the user's email should be marked as verified for the OIDC clients."
|
||||
label={m.emails_verified()}
|
||||
description={m.whether_the_users_email_should_be_marked_as_verified_for_the_oidc_clients()}
|
||||
bind:checked={$inputs.emailsVerified.value}
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-5 flex justify-end">
|
||||
<Button {isLoading} type="submit">Save</Button>
|
||||
<Button {isLoading} type="submit">{m.save()}</Button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import CheckboxWithLabel from '$lib/components/form/checkbox-with-label.svelte';
|
||||
import FormInput from '$lib/components/form/form-input.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import AppConfigService from '$lib/services/app-config-service';
|
||||
import type { AllAppConfig } from '$lib/types/application-configuration';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
@@ -74,14 +75,14 @@
|
||||
...data,
|
||||
ldapEnabled: true
|
||||
});
|
||||
toast.success('LDAP configuration updated successfully');
|
||||
toast.success(m.ldap_configuration_updated_successfully());
|
||||
return true;
|
||||
}
|
||||
|
||||
async function onDisable() {
|
||||
ldapEnabled = false;
|
||||
await callback({ ldapEnabled });
|
||||
toast.success('LDAP disabled successfully');
|
||||
toast.success(m.ldap_disabled_successfully());
|
||||
}
|
||||
|
||||
async function onEnable() {
|
||||
@@ -94,7 +95,7 @@
|
||||
ldapSyncing = true;
|
||||
await appConfigService
|
||||
.syncLdap()
|
||||
.then(() => toast.success('LDAP sync finished'))
|
||||
.then(() => toast.success(m.ldap_sync_finished()))
|
||||
.catch(axiosErrorToast);
|
||||
|
||||
ldapSyncing = false;
|
||||
@@ -102,98 +103,98 @@
|
||||
</script>
|
||||
|
||||
<form onsubmit={onSubmit}>
|
||||
<h4 class="text-lg font-semibold">Client Configuration</h4>
|
||||
<h4 class="text-lg font-semibold">{m.client_configuration()}</h4>
|
||||
<fieldset disabled={uiConfigDisabled}>
|
||||
<div class="mt-4 grid grid-cols-1 items-start gap-5 md:grid-cols-2">
|
||||
<FormInput
|
||||
label="LDAP URL"
|
||||
label={m.ldap_url()}
|
||||
placeholder="ldap://example.com:389"
|
||||
bind:input={$inputs.ldapUrl}
|
||||
/>
|
||||
<FormInput
|
||||
label="LDAP Bind DN"
|
||||
label={m.ldap_bind_dn()}
|
||||
placeholder="cn=people,dc=example,dc=com"
|
||||
bind:input={$inputs.ldapBindDn}
|
||||
/>
|
||||
<FormInput label="LDAP Bind Password" type="password" bind:input={$inputs.ldapBindPassword} />
|
||||
<FormInput label={m.ldap_bind_password()} type="password" bind:input={$inputs.ldapBindPassword} />
|
||||
<FormInput
|
||||
label="LDAP Base DN"
|
||||
label={m.ldap_base_dn()}
|
||||
placeholder="dc=example,dc=com"
|
||||
bind:input={$inputs.ldapBase}
|
||||
/>
|
||||
<FormInput
|
||||
label="User Search Filter"
|
||||
description="The Search filter to use to search/sync users."
|
||||
label={m.user_search_filter()}
|
||||
description={m.the_search_filter_to_use_to_search_or_sync_users()}
|
||||
placeholder="(objectClass=person)"
|
||||
bind:input={$inputs.ldapUserSearchFilter}
|
||||
/>
|
||||
<FormInput
|
||||
label="Groups Search Filter"
|
||||
description="The Search filter to use to search/sync groups."
|
||||
label={m.groups_search_filter()}
|
||||
description={m.the_search_filter_to_use_to_search_or_sync_groups()}
|
||||
placeholder="(objectClass=groupOfNames)"
|
||||
bind:input={$inputs.ldapUserGroupSearchFilter}
|
||||
/>
|
||||
<CheckboxWithLabel
|
||||
id="skip-cert-verify"
|
||||
label="Skip Certificate Verification"
|
||||
description="This can be useful for self-signed certificates."
|
||||
label={m.skip_certificate_verification()}
|
||||
description={m.this_can_be_useful_for_selfsigned_certificates()}
|
||||
bind:checked={$inputs.ldapSkipCertVerify.value}
|
||||
/>
|
||||
</div>
|
||||
<h4 class="mt-10 text-lg font-semibold">Attribute Mapping</h4>
|
||||
<h4 class="mt-10 text-lg font-semibold">{m.attribute_mapping()}</h4>
|
||||
<div class="mt-4 grid grid-cols-1 items-end gap-5 md:grid-cols-2">
|
||||
<FormInput
|
||||
label="User Unique Identifier Attribute"
|
||||
description="The value of this attribute should never change."
|
||||
label={m.user_unique_identifier_attribute()}
|
||||
description={m.the_value_of_this_attribute_should_never_change()}
|
||||
placeholder="uuid"
|
||||
bind:input={$inputs.ldapAttributeUserUniqueIdentifier}
|
||||
/>
|
||||
<FormInput
|
||||
label="Username Attribute"
|
||||
label={m.username_attribute()}
|
||||
placeholder="uid"
|
||||
bind:input={$inputs.ldapAttributeUserUsername}
|
||||
/>
|
||||
<FormInput
|
||||
label="User Mail Attribute"
|
||||
label={m.user_mail_attribute()}
|
||||
placeholder="mail"
|
||||
bind:input={$inputs.ldapAttributeUserEmail}
|
||||
/>
|
||||
<FormInput
|
||||
label="User First Name Attribute"
|
||||
label={m.user_first_name_attribute()}
|
||||
placeholder="givenName"
|
||||
bind:input={$inputs.ldapAttributeUserFirstName}
|
||||
/>
|
||||
<FormInput
|
||||
label="User Last Name Attribute"
|
||||
label={m.user_last_name_attribute()}
|
||||
placeholder="sn"
|
||||
bind:input={$inputs.ldapAttributeUserLastName}
|
||||
/>
|
||||
<FormInput
|
||||
label="User Profile Picture Attribute"
|
||||
description="The value of this attribute can either be a URL, a binary or a base64 encoded image."
|
||||
label={m.user_profile_picture_attribute()}
|
||||
description={m.the_value_of_this_attribute_can_either_be_a_url_binary_or_base64_encoded_image()}
|
||||
placeholder="jpegPhoto"
|
||||
bind:input={$inputs.ldapAttributeUserProfilePicture}
|
||||
/>
|
||||
<FormInput
|
||||
label="Group Members Attribute"
|
||||
description="The attribute to use for querying members of a group."
|
||||
label={m.group_members_attribute()}
|
||||
description={m.the_attribute_to_use_for_querying_members_of_a_group()}
|
||||
placeholder="member"
|
||||
bind:input={$inputs.ldapAttributeGroupMember}
|
||||
/>
|
||||
<FormInput
|
||||
label="Group Unique Identifier Attribute"
|
||||
description="The value of this attribute should never change."
|
||||
label={m.group_unique_identifier_attribute()}
|
||||
description={m.the_value_of_this_attribute_should_never_change()}
|
||||
placeholder="uuid"
|
||||
bind:input={$inputs.ldapAttributeGroupUniqueIdentifier}
|
||||
/>
|
||||
<FormInput
|
||||
label="Group Name Attribute"
|
||||
label={m.group_name_attribute()}
|
||||
placeholder="cn"
|
||||
bind:input={$inputs.ldapAttributeGroupName}
|
||||
/>
|
||||
<FormInput
|
||||
label="Admin Group Name"
|
||||
description="Members of this group will have Admin Privileges in Pocket ID."
|
||||
label={m.admin_group_name()}
|
||||
description={m.members_of_this_group_will_have_admin_privileges_in_pocketid()}
|
||||
placeholder="_admin_group_name"
|
||||
bind:input={$inputs.ldapAttributeAdminGroup}
|
||||
/>
|
||||
@@ -202,11 +203,11 @@
|
||||
|
||||
<div class="mt-8 flex flex-wrap justify-end gap-3">
|
||||
{#if ldapEnabled}
|
||||
<Button variant="secondary" onclick={onDisable} disabled={uiConfigDisabled}>Disable</Button>
|
||||
<Button variant="secondary" onclick={syncLdap} isLoading={ldapSyncing}>Sync now</Button>
|
||||
<Button type="submit" disabled={uiConfigDisabled}>Save</Button>
|
||||
<Button variant="secondary" onclick={onDisable} disabled={uiConfigDisabled}>{m.disable()}</Button>
|
||||
<Button variant="secondary" onclick={syncLdap} isLoading={ldapSyncing}>{m.sync_now()}</Button>
|
||||
<Button type="submit" disabled={uiConfigDisabled}>{m.save()}</Button>
|
||||
{:else}
|
||||
<Button onclick={onEnable} disabled={uiConfigDisabled}>Enable</Button>
|
||||
<Button onclick={onEnable} disabled={uiConfigDisabled}>{m.enable()}</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/ui/button/button.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import ApplicationImage from './application-image.svelte';
|
||||
|
||||
let {
|
||||
@@ -23,7 +24,7 @@
|
||||
<ApplicationImage
|
||||
id="favicon"
|
||||
imageClass="h-14 w-14 p-2"
|
||||
label="Favicon"
|
||||
label={m.favicon()}
|
||||
bind:image={favicon}
|
||||
imageURL="/api/application-configuration/favicon"
|
||||
accept="image/x-icon"
|
||||
@@ -31,7 +32,7 @@
|
||||
<ApplicationImage
|
||||
id="logo-light"
|
||||
imageClass="h-32 w-32"
|
||||
label="Light Mode Logo"
|
||||
label={m.light_mode_logo()}
|
||||
bind:image={logoLight}
|
||||
imageURL="/api/application-configuration/logo?light=true"
|
||||
forceColorScheme="light"
|
||||
@@ -39,7 +40,7 @@
|
||||
<ApplicationImage
|
||||
id="logo-dark"
|
||||
imageClass="h-32 w-32"
|
||||
label="Dark Mode Logo"
|
||||
label={m.dark_mode_logo()}
|
||||
bind:image={logoDark}
|
||||
imageURL="/api/application-configuration/logo?light=false"
|
||||
forceColorScheme="dark"
|
||||
@@ -47,13 +48,13 @@
|
||||
<ApplicationImage
|
||||
id="background-image"
|
||||
imageClass="h-[350px] max-w-[500px]"
|
||||
label="Background Image"
|
||||
label={m.background_image()}
|
||||
bind:image={backgroundImage}
|
||||
imageURL="/api/application-configuration/background-image"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<Button class="mt-5" onclick={() => callback(logoLight, logoDark, backgroundImage, favicon)}
|
||||
>Save</Button
|
||||
>{m.save()}</Button
|
||||
>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user