mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-02-15 14:00:05 +00:00
feat: enhance language selection message and add translation contribution link
This commit is contained in:
@@ -312,7 +312,8 @@
|
|||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"reset_to_default": "Reset to default",
|
"reset_to_default": "Reset to default",
|
||||||
"profile_picture_has_been_reset": "Profile picture has been reset. It may take a few minutes to update.",
|
"profile_picture_has_been_reset": "Profile picture has been reset. It may take a few minutes to update.",
|
||||||
"select_the_language_you_want_to_use": "Select the language you want to use. Some languages may not be fully translated.",
|
"select_the_language_you_want_to_use": "Select the language you want to use. Please note that some text may be automatically translated and could be inaccurate.",
|
||||||
|
"contribute_to_translation": "If you find an issue you're welcome to contribute to the translation on <link href='https://crowdin.com/project/pocket-id'>Crowdin</link>.",
|
||||||
"personal": "Personal",
|
"personal": "Personal",
|
||||||
"global": "Global",
|
"global": "Global",
|
||||||
"all_users": "All Users",
|
"all_users": "All Users",
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
{description}
|
{description}
|
||||||
{#if docsLink}
|
{#if docsLink}
|
||||||
<a
|
<a
|
||||||
class="relative text-white after:absolute after:bottom-0 after:left-0 after:h-px after:w-full after:translate-y-[-1px] after:bg-white"
|
class="relative text-black after:absolute after:bottom-0 after:left-0 after:h-px after:w-full after:translate-y-[-1px] after:bg-white dark:text-white"
|
||||||
href={docsLink}
|
href={docsLink}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if input?.error}
|
{#if input?.error}
|
||||||
<p class="text-destructive mt-1 text-xs text-start">{input.error}</p>
|
<p class="text-destructive mt-1 text-start text-xs">{input.error}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
83
frontend/src/lib/components/formatted-message.svelte
Normal file
83
frontend/src/lib/components/formatted-message.svelte
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<!-- Component to display messages from Paraglide with support for links in the format <link href="url">text</link>. -->
|
||||||
|
<!-- This gets redundant in the future, because the library will support this natively. https://github.com/opral/inlang-sdk/issues/240 -->
|
||||||
|
<script lang="ts">
|
||||||
|
let {
|
||||||
|
m
|
||||||
|
}: {
|
||||||
|
m: string;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
interface MessagePart {
|
||||||
|
type: 'text' | 'link';
|
||||||
|
content: string;
|
||||||
|
href?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseMessage(content: string): MessagePart[] | string {
|
||||||
|
// Regex to match only <link href="url">text</link> format
|
||||||
|
const linkRegex = /<link\s+href=(['"])(.*?)\1>(.*?)<\/link>/g;
|
||||||
|
|
||||||
|
if (!linkRegex.test(content)) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset regex lastIndex for reuse
|
||||||
|
linkRegex.lastIndex = 0;
|
||||||
|
|
||||||
|
const parts: MessagePart[] = [];
|
||||||
|
let lastIndex = 0;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = linkRegex.exec(content)) !== null) {
|
||||||
|
// Add text before the link
|
||||||
|
if (match.index > lastIndex) {
|
||||||
|
const textContent = content.slice(lastIndex, match.index);
|
||||||
|
if (textContent) {
|
||||||
|
parts.push({ type: 'text', content: textContent });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const href = match[2];
|
||||||
|
const linkText = match[3];
|
||||||
|
|
||||||
|
parts.push({
|
||||||
|
type: 'link',
|
||||||
|
content: linkText,
|
||||||
|
href: href
|
||||||
|
});
|
||||||
|
|
||||||
|
lastIndex = match.index + match[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add remaining text after the last link
|
||||||
|
if (lastIndex < content.length) {
|
||||||
|
const remainingText = content.slice(lastIndex);
|
||||||
|
if (remainingText) {
|
||||||
|
parts.push({ type: 'text', content: remainingText });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedContent = parseMessage(m);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if typeof parsedContent === 'string'}
|
||||||
|
{parsedContent}
|
||||||
|
{:else}
|
||||||
|
{#each parsedContent as part}
|
||||||
|
{#if part.type === 'text'}
|
||||||
|
{part.content}
|
||||||
|
{:else if part.type === 'link'}
|
||||||
|
<a
|
||||||
|
class="text-black underline dark:text-white"
|
||||||
|
href={part.href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{part.content}
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<p
|
<p
|
||||||
bind:this={ref}
|
bind:this={ref}
|
||||||
data-slot="card-description"
|
data-slot="card-description"
|
||||||
class={cn('text-muted-foreground text-sm', className)}
|
class={cn('text-muted-foreground text-sm mt-1', className)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import FormattedMessage from '$lib/components/formatted-message.svelte';
|
||||||
import * as Alert from '$lib/components/ui/alert';
|
import * as Alert from '$lib/components/ui/alert';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import * as Card from '$lib/components/ui/card';
|
import * as Card from '$lib/components/ui/card';
|
||||||
@@ -9,7 +10,6 @@
|
|||||||
import type { Passkey } from '$lib/types/passkey.type';
|
import type { Passkey } from '$lib/types/passkey.type';
|
||||||
import type { UserCreate } from '$lib/types/user.type';
|
import type { UserCreate } from '$lib/types/user.type';
|
||||||
import { axiosErrorToast, getWebauthnErrorMessage } from '$lib/utils/error-util';
|
import { axiosErrorToast, getWebauthnErrorMessage } from '$lib/utils/error-util';
|
||||||
import { startRegistration } from '@simplewebauthn/browser';
|
|
||||||
import {
|
import {
|
||||||
KeyRound,
|
KeyRound,
|
||||||
Languages,
|
Languages,
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
RectangleEllipsis,
|
RectangleEllipsis,
|
||||||
UserCog
|
UserCog
|
||||||
} from '@lucide/svelte';
|
} from '@lucide/svelte';
|
||||||
|
import { startRegistration } from '@simplewebauthn/browser';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import AccountForm from './account-form.svelte';
|
import AccountForm from './account-form.svelte';
|
||||||
import LocalePicker from './locale-picker.svelte';
|
import LocalePicker from './locale-picker.svelte';
|
||||||
@@ -141,7 +142,7 @@
|
|||||||
|
|
||||||
<!-- Passkey management card -->
|
<!-- Passkey management card -->
|
||||||
<div>
|
<div>
|
||||||
<Card.Root>
|
<Card.Root class="gap-3">
|
||||||
<Card.Header>
|
<Card.Header>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -200,6 +201,8 @@
|
|||||||
</Card.Title>
|
</Card.Title>
|
||||||
<Card.Description>
|
<Card.Description>
|
||||||
{m.select_the_language_you_want_to_use()}
|
{m.select_the_language_you_want_to_use()}
|
||||||
|
<br />
|
||||||
|
<FormattedMessage m={m.contribute_to_translation()} />
|
||||||
</Card.Description>
|
</Card.Description>
|
||||||
</div>
|
</div>
|
||||||
<LocalePicker />
|
<LocalePicker />
|
||||||
|
|||||||
Reference in New Issue
Block a user