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:
Jonas Claes
2025-03-20 19:57:41 +01:00
committed by GitHub
parent 041c565dc1
commit 269b5a3c92
83 changed files with 1567 additions and 453 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>