mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-21 01:11:33 +03: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:
@@ -11,6 +11,7 @@
|
||||
import { ChevronDown } from 'lucide-svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
import Button from './ui/button/button.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
|
||||
let {
|
||||
items,
|
||||
@@ -93,7 +94,7 @@
|
||||
'relative z-50 mb-4 max-w-sm',
|
||||
items.data.length == 0 && searchValue == '' && 'hidden'
|
||||
)}
|
||||
placeholder={'Search...'}
|
||||
placeholder={m.search()}
|
||||
type="text"
|
||||
oninput={(e) => onSearch((e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
@@ -102,7 +103,7 @@
|
||||
{#if items.data.length === 0 && searchValue === ''}
|
||||
<div class="my-5 flex flex-col items-center">
|
||||
<Empty class="text-muted-foreground h-20" />
|
||||
<p class="text-muted-foreground mt-3 text-sm">No items found</p>
|
||||
<p class="text-muted-foreground mt-3 text-sm">{m.no_items_found()}</p>
|
||||
</div>
|
||||
{:else}
|
||||
<Table.Root class="min-w-full table-auto overflow-x-auto">
|
||||
@@ -166,7 +167,7 @@
|
||||
|
||||
<div class="mt-5 flex flex-col-reverse items-center justify-between gap-3 sm:flex-row">
|
||||
<div class="flex items-center space-x-2">
|
||||
<p class="text-sm font-medium">Items per page</p>
|
||||
<p class="text-sm font-medium">{m.items_per_page()}</p>
|
||||
<Select.Root
|
||||
selected={{
|
||||
label: items.pagination.itemsPerPage.toString(),
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { slide } from 'svelte/transition';
|
||||
import { Button } from './ui/button';
|
||||
import * as Card from './ui/card';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
|
||||
let {
|
||||
id,
|
||||
@@ -55,7 +56,7 @@
|
||||
<Card.Description>{description}</Card.Description>
|
||||
{/if}
|
||||
</div>
|
||||
<Button class="ml-10 h-8 p-3" variant="ghost" aria-label="Expand card">
|
||||
<Button class="ml-10 h-8 p-3" variant="ghost" aria-label={m.expand_card()}>
|
||||
<LucideChevronDown
|
||||
class={cn(
|
||||
'h-5 w-5 transition-transform duration-200',
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import ConfirmDialog from './confirm-dialog.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
|
||||
export const confirmDialogStore = writable({
|
||||
open: false,
|
||||
title: '',
|
||||
message: '',
|
||||
confirm: {
|
||||
label: 'Confirm',
|
||||
label: m.confirm(),
|
||||
destructive: false,
|
||||
action: () => {}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import { LucideCheck } from 'lucide-svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
@@ -31,9 +32,9 @@
|
||||
<Tooltip.Trigger class="text-start" tabindex={-1} onclick={onClick}>{@render children()}</Tooltip.Trigger>
|
||||
<Tooltip.Content onclick={copyToClipboard}>
|
||||
{#if copied}
|
||||
<span class="flex items-center"><LucideCheck class="mr-1 h-4 w-4" /> Copied</span>
|
||||
<span class="flex items-center"><LucideCheck class="mr-1 h-4 w-4" /> {m.copied()}</span>
|
||||
{:else}
|
||||
<span>Click to copy</span>
|
||||
<span>{m.click_to_copy()}</span>
|
||||
{/if}
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import { LucideXCircle } from 'lucide-svelte';
|
||||
|
||||
let { message, showButton = true }: { message: string; showButton?: boolean } = $props();
|
||||
@@ -7,9 +8,9 @@
|
||||
|
||||
<div class="mt-[20%] flex flex-col items-center">
|
||||
<LucideXCircle class="h-12 w-12 text-muted-foreground" />
|
||||
<h1 class="mt-3 text-2xl font-semibold">Something went wrong</h1>
|
||||
<h1 class="mt-3 text-2xl font-semibold">{m.something_went_wrong()}</h1>
|
||||
<p class="text-muted-foreground">{message}</p>
|
||||
{#if showButton}
|
||||
<Button size="sm" class="mt-5" href="/">Go back to home</Button>
|
||||
<Button size="sm" class="mt-5" href="/">{m.go_back_to_home()}</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { onMount, type Snippet } from 'svelte';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import AutoCompleteInput from './auto-complete-input.svelte';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
|
||||
let {
|
||||
customClaims = $bindable(),
|
||||
@@ -41,15 +42,15 @@
|
||||
{#each customClaims as _, i}
|
||||
<div class="flex gap-x-2">
|
||||
<AutoCompleteInput
|
||||
placeholder="Key"
|
||||
placeholder={m.key()}
|
||||
suggestions={filteredSuggestions}
|
||||
bind:value={customClaims[i].key}
|
||||
/>
|
||||
<Input placeholder="Value" bind:value={customClaims[i].value} />
|
||||
<Input placeholder={m.value()} bind:value={customClaims[i].value} />
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
aria-label="Remove custom claim"
|
||||
aria-label={m.remove_custom_claim()}
|
||||
on:click={() => (customClaims = customClaims.filter((_, index) => index !== i))}
|
||||
>
|
||||
<LucideMinus class="h-4 w-4" />
|
||||
@@ -69,7 +70,7 @@
|
||||
on:click={() => (customClaims = [...customClaims, { key: '', value: '' }])}
|
||||
>
|
||||
<LucidePlus class="mr-1 h-4 w-4" />
|
||||
{customClaims.length === 0 ? 'Add custom claim' : 'Add another'}
|
||||
{customClaims.length === 0 ? m.add_custom_claim() : m.add_another()}
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Calendar } from '$lib/components/ui/calendar';
|
||||
import * as Popover from '$lib/components/ui/popover';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import { getLocale } from '$lib/paraglide/runtime';
|
||||
import { cn } from '$lib/utils/style';
|
||||
import {
|
||||
CalendarDate,
|
||||
@@ -30,7 +32,7 @@
|
||||
open = false;
|
||||
}
|
||||
|
||||
const df = new DateFormatter('en-US', {
|
||||
const df = new DateFormatter(getLocale(), {
|
||||
dateStyle: 'long'
|
||||
});
|
||||
</script>
|
||||
@@ -44,7 +46,7 @@
|
||||
builders={[builder]}
|
||||
>
|
||||
<CalendarIcon class="mr-2 h-4 w-4" />
|
||||
{date ? df.format(date.toDate(getLocalTimeZone())) : 'Select a date'}
|
||||
{date ? df.format(date.toDate(getLocalTimeZone())) : m.select_a_date()}
|
||||
</Button>
|
||||
</Popover.Trigger>
|
||||
<Popover.Content class="w-auto p-0" align="start">
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { HTMLInputAttributes } from 'svelte/elements';
|
||||
import type { VariantProps } from 'tailwind-variants';
|
||||
import type { buttonVariants } from '$lib/components/ui/button';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
|
||||
let {
|
||||
id,
|
||||
@@ -21,7 +22,7 @@
|
||||
{#if restProps.children}
|
||||
{@render restProps.children()}
|
||||
{:else}
|
||||
Select File
|
||||
{m.select_file()}
|
||||
{/if}
|
||||
</button>
|
||||
<input {id} {...restProps} type="file" class="hidden" />
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import Button from '$lib/components/ui/button/button.svelte';
|
||||
import { LucideLoader, LucideRefreshCw, LucideUpload } from 'lucide-svelte';
|
||||
import { openConfirmDialog } from '../confirm-dialog';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
|
||||
let {
|
||||
userId,
|
||||
@@ -40,11 +41,10 @@
|
||||
|
||||
function onReset() {
|
||||
openConfirmDialog({
|
||||
title: 'Reset profile picture?',
|
||||
message:
|
||||
'This will remove the uploaded image, and reset the profile picture to default. Do you want to continue?',
|
||||
title: m.reset_profile_picture_question(),
|
||||
message: m.this_will_remove_the_uploaded_image_and_reset_the_profile_picture_to_default(),
|
||||
confirm: {
|
||||
label: 'Reset',
|
||||
label: m.reset(),
|
||||
action: async () => {
|
||||
isLoading = true;
|
||||
await resetCallback().catch();
|
||||
@@ -58,16 +58,16 @@
|
||||
<div class="flex gap-5">
|
||||
<div class="flex w-full flex-col justify-between gap-5 sm:flex-row">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold">Profile Picture</h3>
|
||||
<h3 class="text-xl font-semibold">{m.profile_picture()}</h3>
|
||||
{#if isLdapUser}
|
||||
<p class="text-muted-foreground mt-1 text-sm">
|
||||
The profile picture is managed by the LDAP server and cannot be changed here.
|
||||
{m.profile_picture_is_managed_by_ldap_server()}
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-muted-foreground mt-1 text-sm">
|
||||
Click on the profile picture to upload a custom one from your files.
|
||||
{m.click_profile_picture_to_upload_custom()}
|
||||
</p>
|
||||
<p class="text-muted-foreground mt-1 text-sm">The image should be in PNG or JPEG format.</p>
|
||||
<p class="text-muted-foreground mt-1 text-sm">{m.image_should_be_in_format()}</p>
|
||||
{/if}
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -77,7 +77,7 @@
|
||||
disabled={isLoading || isLdapUser}
|
||||
>
|
||||
<LucideRefreshCw class="mr-2 h-4 w-4" />
|
||||
Reset to default
|
||||
{m.reset_to_default()}
|
||||
</Button>
|
||||
</div>
|
||||
{#if isLdapUser}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import * as Avatar from '$lib/components/ui/avatar';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import WebAuthnService from '$lib/services/webauthn-service';
|
||||
import userStore from '$lib/stores/user-store';
|
||||
import { LucideLogOut, LucideUser } from 'lucide-svelte';
|
||||
@@ -32,10 +33,10 @@
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Group>
|
||||
<DropdownMenu.Item href="/settings/account"
|
||||
><LucideUser class="mr-2 h-4 w-4" /> My Account</DropdownMenu.Item
|
||||
><LucideUser class="mr-2 h-4 w-4" /> {m.my_account()}</DropdownMenu.Item
|
||||
>
|
||||
<DropdownMenu.Item on:click={logout}
|
||||
><LucideLogOut class="mr-2 h-4 w-4" /> Logout</DropdownMenu.Item
|
||||
><LucideLogOut class="mr-2 h-4 w-4" /> {m.logout()}</DropdownMenu.Item
|
||||
>
|
||||
</DropdownMenu.Group>
|
||||
</DropdownMenu.Content>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { page } from '$app/state';
|
||||
import appConfigStore from '$lib/stores/application-configuration-store';
|
||||
import userStore from '$lib/stores/user-store';
|
||||
import Logo from '../logo.svelte';
|
||||
@@ -8,7 +8,7 @@
|
||||
const authUrls = [/^\/authorize$/, /^\/login(?:\/.*)?$/, /^\/logout$/];
|
||||
|
||||
let isAuthPage = $derived(
|
||||
!$page.error && authUrls.some((pattern) => pattern.test($page.url.pathname))
|
||||
!page.error && authUrls.some((pattern) => pattern.test(page.url.pathname))
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -26,8 +26,10 @@
|
||||
</h1>
|
||||
{/if}
|
||||
</div>
|
||||
{#if $userStore?.id}
|
||||
<HeaderAvatar />
|
||||
{/if}
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
{#if $userStore?.id}
|
||||
<HeaderAvatar />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { page } from '$app/state';
|
||||
import type { Snippet } from 'svelte';
|
||||
import * as Card from './ui/card';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
|
||||
let {
|
||||
children,
|
||||
@@ -29,7 +30,7 @@
|
||||
)}`}
|
||||
class="text-muted-foreground text-xs"
|
||||
>
|
||||
Don't have access to your passkey?
|
||||
{m.dont_have_access_to_your_passkey()}
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -38,7 +39,7 @@
|
||||
<img
|
||||
src="/api/application-configuration/background-image"
|
||||
class="h-screen w-[calc(100vw-650px)] rounded-l-[60px] object-cover"
|
||||
alt="Login background"
|
||||
alt={m.login_background()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -60,7 +61,7 @@
|
||||
)}`}
|
||||
class="text-muted-foreground mt-7 flex justify-center text-xs"
|
||||
>
|
||||
Don't have access to your passkey?
|
||||
{m.dont_have_access_to_your_passkey()}
|
||||
</a>
|
||||
{/if}
|
||||
</Card.CardContent>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import { mode } from 'mode-watcher';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
@@ -7,4 +8,4 @@
|
||||
const isDarkMode = $derived($mode === 'dark');
|
||||
</script>
|
||||
|
||||
<img {...props} src="/api/application-configuration/logo?light={!isDarkMode}" alt="Logo" />
|
||||
<img {...props} src="/api/application-configuration/logo?light={!isDarkMode}" alt={m.logo()} />
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import Input from '$lib/components/ui/input/input.svelte';
|
||||
import Label from '$lib/components/ui/label/label.svelte';
|
||||
import * as Select from '$lib/components/ui/select/index.js';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserService from '$lib/services/user-service';
|
||||
import { axiosErrorToast } from '$lib/utils/error-util';
|
||||
|
||||
@@ -17,14 +18,14 @@
|
||||
const userService = new UserService();
|
||||
|
||||
let oneTimeLink: string | null = $state(null);
|
||||
let selectedExpiration: keyof typeof availableExpirations = $state('1 hour');
|
||||
let selectedExpiration: keyof typeof availableExpirations = $state(m.one_hour());
|
||||
|
||||
let availableExpirations = {
|
||||
'1 hour': 60 * 60,
|
||||
'12 hours': 60 * 60 * 12,
|
||||
'1 day': 60 * 60 * 24,
|
||||
'1 week': 60 * 60 * 24 * 7,
|
||||
'1 month': 60 * 60 * 24 * 30
|
||||
[m.one_hour()]: 60 * 60,
|
||||
[m.twelve_hours()]: 60 * 60 * 12,
|
||||
[m.one_day()]: 60 * 60 * 24,
|
||||
[m.one_week()]: 60 * 60 * 24 * 7,
|
||||
[m.one_month()]: 60 * 60 * 24 * 30
|
||||
};
|
||||
|
||||
async function createOneTimeAccessToken() {
|
||||
@@ -48,14 +49,14 @@
|
||||
<Dialog.Root open={!!userId} {onOpenChange}>
|
||||
<Dialog.Content class="max-w-md">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Login Code</Dialog.Title>
|
||||
<Dialog.Title>{m.login_code()}</Dialog.Title>
|
||||
<Dialog.Description
|
||||
>Create a login code that the user can use to sign in without a passkey once.</Dialog.Description
|
||||
>{m.create_a_login_code_to_sign_in_without_a_passkey_once()}</Dialog.Description
|
||||
>
|
||||
</Dialog.Header>
|
||||
{#if oneTimeLink === null}
|
||||
<div>
|
||||
<Label for="expiration">Expiration</Label>
|
||||
<Label for="expiration">{m.expiration()}</Label>
|
||||
<Select.Root
|
||||
selected={{
|
||||
label: Object.keys(availableExpirations)[0],
|
||||
@@ -75,10 +76,10 @@
|
||||
</Select.Root>
|
||||
</div>
|
||||
<Button onclick={() => createOneTimeAccessToken()} disabled={!selectedExpiration}>
|
||||
Generate Code
|
||||
{m.generate_code()}
|
||||
</Button>
|
||||
{:else}
|
||||
<Label for="login-code" class="sr-only">Login Code</Label>
|
||||
<Label for="login-code" class="sr-only">{m.login_code()}</Label>
|
||||
<Input id="login-code" value={oneTimeLink} readonly />
|
||||
{/if}
|
||||
</Dialog.Content>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import AdvancedTable from '$lib/components/advanced-table.svelte';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import UserGroupService from '$lib/services/user-group-service';
|
||||
import type { Paginated, SearchPaginationSortRequest } from '$lib/types/pagination.type';
|
||||
import type { UserGroup } from '$lib/types/user-group.type';
|
||||
@@ -34,7 +35,7 @@
|
||||
items={groups}
|
||||
{requestOptions}
|
||||
onRefresh={async (o) => (groups = await userGroupService.list(o))}
|
||||
columns={[{ label: 'Name', sortColumn: 'friendlyName' }]}
|
||||
columns={[{ label: m.name(), sortColumn: 'friendlyName' }]}
|
||||
bind:selectedIds={selectedGroupIds}
|
||||
{selectionDisabled}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { m } from '$lib/paraglide/messages';
|
||||
import Logo from './logo.svelte';
|
||||
</script>
|
||||
|
||||
@@ -6,8 +7,8 @@
|
||||
<div class="bg-muted mx-auto rounded-2xl p-3">
|
||||
<Logo class="h-10 w-10" />
|
||||
</div>
|
||||
<p class="font-playfair mt-5 text-3xl font-bold sm:text-4xl">Browser unsupported</p>
|
||||
<p class="font-playfair mt-5 text-3xl font-bold sm:text-4xl">{m.browser_unsupported()}</p>
|
||||
<p class="text-muted-foreground mt-3">
|
||||
This browser doesn't support passkeys. Please or use a alternative sign in method.
|
||||
{m.this_browser_does_not_support_passkeys()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user