mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:29:32 -05:00 
			
		
		
		
	fix(web): FormatMessage development keys (#10536)
This commit is contained in:
		
							parent
							
								
									4cb165304b
								
							
						
					
					
						commit
						6164640575
					
				@ -5,7 +5,7 @@
 | 
				
			|||||||
  import { serverConfig } from '$lib/stores/server-config.store';
 | 
					  import { serverConfig } from '$lib/stores/server-config.store';
 | 
				
			||||||
  import { createEventDispatcher } from 'svelte';
 | 
					  import { createEventDispatcher } from 'svelte';
 | 
				
			||||||
  import Checkbox from '$lib/components/elements/checkbox.svelte';
 | 
					  import Checkbox from '$lib/components/elements/checkbox.svelte';
 | 
				
			||||||
  import { json, t } from 'svelte-i18n';
 | 
					  import { t } from 'svelte-i18n';
 | 
				
			||||||
  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
					  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let user: UserResponseDto;
 | 
					  export let user: UserResponseDto;
 | 
				
			||||||
@ -55,14 +55,14 @@
 | 
				
			|||||||
    <div class="flex flex-col gap-4">
 | 
					    <div class="flex flex-col gap-4">
 | 
				
			||||||
      {#if forceDelete}
 | 
					      {#if forceDelete}
 | 
				
			||||||
        <p>
 | 
					        <p>
 | 
				
			||||||
          <FormatMessage message={$json('admin.user_delete_immediately')} values={{ user: user.name }} let:message>
 | 
					          <FormatMessage key="admin.user_delete_immediately" values={{ user: user.name }} let:message>
 | 
				
			||||||
            <b>{message}</b>
 | 
					            <b>{message}</b>
 | 
				
			||||||
          </FormatMessage>
 | 
					          </FormatMessage>
 | 
				
			||||||
        </p>
 | 
					        </p>
 | 
				
			||||||
      {:else}
 | 
					      {:else}
 | 
				
			||||||
        <p>
 | 
					        <p>
 | 
				
			||||||
          <FormatMessage
 | 
					          <FormatMessage
 | 
				
			||||||
            message={$json('admin.user_delete_delay')}
 | 
					            key="admin.user_delete_delay"
 | 
				
			||||||
            values={{ user: user.name, delay: $serverConfig.userDeleteDelay }}
 | 
					            values={{ user: user.name, delay: $serverConfig.userDeleteDelay }}
 | 
				
			||||||
            let:message
 | 
					            let:message
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,11 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
					  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
				
			||||||
  import { AppRoute, OpenSettingQueryParameterValue, QueryParameter } from '$lib/constants';
 | 
					  import { AppRoute, OpenSettingQueryParameterValue, QueryParameter } from '$lib/constants';
 | 
				
			||||||
  import { json, t } from 'svelte-i18n';
 | 
					  import { t } from 'svelte-i18n';
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<FormatMessage
 | 
					<FormatMessage
 | 
				
			||||||
  message={$json('admin.storage_template_migration_description')}
 | 
					  key="admin.storage_template_migration_description"
 | 
				
			||||||
  values={{ template: $t('admin.storage_template_settings') }}
 | 
					  values={{ template: $t('admin.storage_template_settings') }}
 | 
				
			||||||
  let:message
 | 
					  let:message
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@
 | 
				
			|||||||
  import { handleError } from '$lib/utils/handle-error';
 | 
					  import { handleError } from '$lib/utils/handle-error';
 | 
				
			||||||
  import { restoreUserAdmin, type UserResponseDto } from '@immich/sdk';
 | 
					  import { restoreUserAdmin, type UserResponseDto } from '@immich/sdk';
 | 
				
			||||||
  import { createEventDispatcher } from 'svelte';
 | 
					  import { createEventDispatcher } from 'svelte';
 | 
				
			||||||
  import { json, t } from 'svelte-i18n';
 | 
					  import { t } from 'svelte-i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let user: UserResponseDto;
 | 
					  export let user: UserResponseDto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -38,7 +38,7 @@
 | 
				
			|||||||
>
 | 
					>
 | 
				
			||||||
  <svelte:fragment slot="prompt">
 | 
					  <svelte:fragment slot="prompt">
 | 
				
			||||||
    <p>
 | 
					    <p>
 | 
				
			||||||
      <FormatMessage message={$json('admin.user_restore_description')} values={{ user: user.name }} let:message>
 | 
					      <FormatMessage key="admin.user_restore_description" values={{ user: user.name }} let:message>
 | 
				
			||||||
        <b>{message}</b>
 | 
					        <b>{message}</b>
 | 
				
			||||||
      </FormatMessage>
 | 
					      </FormatMessage>
 | 
				
			||||||
    </p>
 | 
					    </p>
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@
 | 
				
			|||||||
  import { createEventDispatcher } from 'svelte';
 | 
					  import { createEventDispatcher } from 'svelte';
 | 
				
			||||||
  import { fade } from 'svelte/transition';
 | 
					  import { fade } from 'svelte/transition';
 | 
				
			||||||
  import type { SettingsEventType } from '../admin-settings';
 | 
					  import type { SettingsEventType } from '../admin-settings';
 | 
				
			||||||
  import { json, t } from 'svelte-i18n';
 | 
					  import { t } from 'svelte-i18n';
 | 
				
			||||||
  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
					  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let savedConfig: SystemConfigDto;
 | 
					  export let savedConfig: SystemConfigDto;
 | 
				
			||||||
@ -53,7 +53,7 @@
 | 
				
			|||||||
      <div class="flex flex-col gap-4">
 | 
					      <div class="flex flex-col gap-4">
 | 
				
			||||||
        <p>Are you sure you want to disable all login methods? Login will be completely disabled.</p>
 | 
					        <p>Are you sure you want to disable all login methods? Login will be completely disabled.</p>
 | 
				
			||||||
        <p>
 | 
					        <p>
 | 
				
			||||||
          <FormatMessage message={$json('admin.authentication_settings_reenable')} let:message>
 | 
					          <FormatMessage key="admin.authentication_settings_reenable" let:message>
 | 
				
			||||||
            <a
 | 
					            <a
 | 
				
			||||||
              href="https://immich.app/docs/administration/server-commands"
 | 
					              href="https://immich.app/docs/administration/server-commands"
 | 
				
			||||||
              rel="noreferrer"
 | 
					              rel="noreferrer"
 | 
				
			||||||
@ -80,7 +80,7 @@
 | 
				
			|||||||
        >
 | 
					        >
 | 
				
			||||||
          <div class="ml-4 mt-4 flex flex-col gap-4">
 | 
					          <div class="ml-4 mt-4 flex flex-col gap-4">
 | 
				
			||||||
            <p class="text-sm dark:text-immich-dark-fg">
 | 
					            <p class="text-sm dark:text-immich-dark-fg">
 | 
				
			||||||
              <FormatMessage message={$json('admin.oauth_settings_more_details')} let:message>
 | 
					              <FormatMessage key="admin.oauth_settings_more_details" let:message>
 | 
				
			||||||
                <a
 | 
					                <a
 | 
				
			||||||
                  href="https://immich.app/docs/administration/oauth"
 | 
					                  href="https://immich.app/docs/administration/oauth"
 | 
				
			||||||
                  class="underline"
 | 
					                  class="underline"
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,7 @@
 | 
				
			|||||||
  import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
 | 
					  import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
 | 
				
			||||||
  import SettingCheckboxes from '$lib/components/shared-components/settings/setting-checkboxes.svelte';
 | 
					  import SettingCheckboxes from '$lib/components/shared-components/settings/setting-checkboxes.svelte';
 | 
				
			||||||
  import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte';
 | 
					  import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte';
 | 
				
			||||||
  import { json, t } from 'svelte-i18n';
 | 
					  import { t } from 'svelte-i18n';
 | 
				
			||||||
  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
					  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let savedConfig: SystemConfigDto;
 | 
					  export let savedConfig: SystemConfigDto;
 | 
				
			||||||
@ -39,7 +39,7 @@
 | 
				
			|||||||
      <div class="ml-4 mt-4 flex flex-col gap-4">
 | 
					      <div class="ml-4 mt-4 flex flex-col gap-4">
 | 
				
			||||||
        <p class="text-sm dark:text-immich-dark-fg">
 | 
					        <p class="text-sm dark:text-immich-dark-fg">
 | 
				
			||||||
          <Icon path={mdiHelpCircleOutline} class="inline" size="15" />
 | 
					          <Icon path={mdiHelpCircleOutline} class="inline" size="15" />
 | 
				
			||||||
          <FormatMessage message={$json('admin.transcoding_codecs_learn_more')} let:tag let:message>
 | 
					          <FormatMessage key="admin.transcoding_codecs_learn_more" let:tag let:message>
 | 
				
			||||||
            {#if tag === 'h264-link'}
 | 
					            {#if tag === 'h264-link'}
 | 
				
			||||||
              <a href="https://trac.ffmpeg.org/wiki/Encode/H.264" class="underline" target="_blank" rel="noreferrer">
 | 
					              <a href="https://trac.ffmpeg.org/wiki/Encode/H.264" class="underline" target="_blank" rel="noreferrer">
 | 
				
			||||||
                {message}
 | 
					                {message}
 | 
				
			||||||
 | 
				
			|||||||
@ -10,7 +10,7 @@
 | 
				
			|||||||
  } from '$lib/components/shared-components/settings/setting-input-field.svelte';
 | 
					  } from '$lib/components/shared-components/settings/setting-input-field.svelte';
 | 
				
			||||||
  import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
 | 
					  import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
 | 
				
			||||||
  import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte';
 | 
					  import SettingButtonsRow from '$lib/components/shared-components/settings/setting-buttons-row.svelte';
 | 
				
			||||||
  import { json, t } from 'svelte-i18n';
 | 
					  import { t } from 'svelte-i18n';
 | 
				
			||||||
  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
					  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let savedConfig: SystemConfigDto;
 | 
					  export let savedConfig: SystemConfigDto;
 | 
				
			||||||
@ -100,7 +100,7 @@
 | 
				
			|||||||
          >
 | 
					          >
 | 
				
			||||||
            <svelte:fragment slot="desc">
 | 
					            <svelte:fragment slot="desc">
 | 
				
			||||||
              <p class="text-sm dark:text-immich-dark-fg">
 | 
					              <p class="text-sm dark:text-immich-dark-fg">
 | 
				
			||||||
                <FormatMessage message={$json('admin.library_cron_expression_description')} let:message>
 | 
					                <FormatMessage key="admin.library_cron_expression_description" let:message>
 | 
				
			||||||
                  <a href="https://crontab.guru" class="underline" target="_blank" rel="noreferrer">
 | 
					                  <a href="https://crontab.guru" class="underline" target="_blank" rel="noreferrer">
 | 
				
			||||||
                    {message}
 | 
					                    {message}
 | 
				
			||||||
                  </a>
 | 
					                  </a>
 | 
				
			||||||
 | 
				
			|||||||
@ -12,7 +12,7 @@
 | 
				
			|||||||
  import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
 | 
					  import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
 | 
				
			||||||
  import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
 | 
					  import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
 | 
				
			||||||
  import { featureFlags } from '$lib/stores/server-config.store';
 | 
					  import { featureFlags } from '$lib/stores/server-config.store';
 | 
				
			||||||
  import { json, t } from 'svelte-i18n';
 | 
					  import { t } from 'svelte-i18n';
 | 
				
			||||||
  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
					  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let savedConfig: SystemConfigDto;
 | 
					  export let savedConfig: SystemConfigDto;
 | 
				
			||||||
@ -71,7 +71,7 @@
 | 
				
			|||||||
            isEdited={config.machineLearning.clip.modelName !== savedConfig.machineLearning.clip.modelName}
 | 
					            isEdited={config.machineLearning.clip.modelName !== savedConfig.machineLearning.clip.modelName}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <p slot="desc" class="immich-form-label pb-2 text-sm">
 | 
					            <p slot="desc" class="immich-form-label pb-2 text-sm">
 | 
				
			||||||
              <FormatMessage message={$json('admin.machine_learning_clip_model_description')} let:message>
 | 
					              <FormatMessage key="admin.machine_learning_clip_model_description" let:message>
 | 
				
			||||||
                <a href="https://huggingface.co/immich-app"><u>{message}</u></a>
 | 
					                <a href="https://huggingface.co/immich-app"><u>{message}</u></a>
 | 
				
			||||||
              </FormatMessage>
 | 
					              </FormatMessage>
 | 
				
			||||||
            </p>
 | 
					            </p>
 | 
				
			||||||
 | 
				
			|||||||
@ -20,7 +20,7 @@
 | 
				
			|||||||
    SettingInputFieldType,
 | 
					    SettingInputFieldType,
 | 
				
			||||||
  } from '$lib/components/shared-components/settings/setting-input-field.svelte';
 | 
					  } from '$lib/components/shared-components/settings/setting-input-field.svelte';
 | 
				
			||||||
  import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
 | 
					  import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
 | 
				
			||||||
  import { json, t } from 'svelte-i18n';
 | 
					  import { t } from 'svelte-i18n';
 | 
				
			||||||
  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
					  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let savedConfig: SystemConfigDto;
 | 
					  export let savedConfig: SystemConfigDto;
 | 
				
			||||||
@ -89,7 +89,7 @@
 | 
				
			|||||||
<section class="dark:text-immich-dark-fg mt-2">
 | 
					<section class="dark:text-immich-dark-fg mt-2">
 | 
				
			||||||
  <div in:fade={{ duration: 500 }} class="mx-4 flex flex-col gap-4 py-4">
 | 
					  <div in:fade={{ duration: 500 }} class="mx-4 flex flex-col gap-4 py-4">
 | 
				
			||||||
    <p class="text-sm dark:text-immich-dark-fg">
 | 
					    <p class="text-sm dark:text-immich-dark-fg">
 | 
				
			||||||
      <FormatMessage message={$json('admin.storage_template_more_details')} let:tag let:message>
 | 
					      <FormatMessage key="admin.storage_template_more_details" let:tag let:message>
 | 
				
			||||||
        {#if tag === 'template-link'}
 | 
					        {#if tag === 'template-link'}
 | 
				
			||||||
          <a
 | 
					          <a
 | 
				
			||||||
            href="https://immich.app/docs/administration/storage-template"
 | 
					            href="https://immich.app/docs/administration/storage-template"
 | 
				
			||||||
@ -161,7 +161,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
          <p class="text-sm">
 | 
					          <p class="text-sm">
 | 
				
			||||||
            <FormatMessage
 | 
					            <FormatMessage
 | 
				
			||||||
              message={$json('admin.storage_template_path_length')}
 | 
					              key="admin.storage_template_path_length"
 | 
				
			||||||
              values={{ length: parsedTemplate().length + $user.id.length + 'UPLOAD_LOCATION'.length, limit: 260 }}
 | 
					              values={{ length: parsedTemplate().length + $user.id.length + 'UPLOAD_LOCATION'.length, limit: 260 }}
 | 
				
			||||||
              let:message
 | 
					              let:message
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
@ -171,7 +171,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
          <p class="text-sm">
 | 
					          <p class="text-sm">
 | 
				
			||||||
            <FormatMessage
 | 
					            <FormatMessage
 | 
				
			||||||
              message={$json('admin.storage_template_user_label')}
 | 
					              key="admin.storage_template_user_label"
 | 
				
			||||||
              values={{ label: $user.storageLabel || $user.id }}
 | 
					              values={{ label: $user.storageLabel || $user.id }}
 | 
				
			||||||
              let:message
 | 
					              let:message
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
@ -229,7 +229,7 @@
 | 
				
			|||||||
                <section class="flex flex-col gap-2">
 | 
					                <section class="flex flex-col gap-2">
 | 
				
			||||||
                  <p>
 | 
					                  <p>
 | 
				
			||||||
                    <FormatMessage
 | 
					                    <FormatMessage
 | 
				
			||||||
                      message={$json('admin.storage_template_migration_info')}
 | 
					                      key="admin.storage_template_migration_info"
 | 
				
			||||||
                      values={{ job: $t('admin.storage_template_migration_job') }}
 | 
					                      values={{ job: $t('admin.storage_template_migration_job') }}
 | 
				
			||||||
                      let:message
 | 
					                      let:message
 | 
				
			||||||
                    >
 | 
					                    >
 | 
				
			||||||
 | 
				
			|||||||
@ -2,13 +2,10 @@ import FormatTagB from '$lib/components/i18n/__test__/format-tag-b.svelte';
 | 
				
			|||||||
import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
					import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
				
			||||||
import '@testing-library/jest-dom';
 | 
					import '@testing-library/jest-dom';
 | 
				
			||||||
import { render, screen } from '@testing-library/svelte';
 | 
					import { render, screen } from '@testing-library/svelte';
 | 
				
			||||||
import { init, json, locale, register, waitLocale } from 'svelte-i18n';
 | 
					import { init, locale, register, waitLocale } from 'svelte-i18n';
 | 
				
			||||||
import { get } from 'svelte/store';
 | 
					 | 
				
			||||||
import { describe } from 'vitest';
 | 
					import { describe } from 'vitest';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('FormatMessage component', () => {
 | 
					describe('FormatMessage component', () => {
 | 
				
			||||||
  let $json: (id: string, locale?: string | undefined) => unknown;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  beforeAll(async () => {
 | 
					  beforeAll(async () => {
 | 
				
			||||||
    register('en', () =>
 | 
					    register('en', () =>
 | 
				
			||||||
      Promise.resolve({
 | 
					      Promise.resolve({
 | 
				
			||||||
@ -21,12 +18,11 @@ describe('FormatMessage component', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    await init({ fallbackLocale: 'en' });
 | 
					    await init({ fallbackLocale: 'en' });
 | 
				
			||||||
    await waitLocale('en');
 | 
					    await waitLocale('en');
 | 
				
			||||||
    $json = get(json);
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('formats a plain text message', () => {
 | 
					  it('formats a plain text message', () => {
 | 
				
			||||||
    render(FormatMessage, {
 | 
					    render(FormatMessage, {
 | 
				
			||||||
      message: $json('hello'),
 | 
					      key: 'hello',
 | 
				
			||||||
      values: { name: 'test' },
 | 
					      values: { name: 'test' },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    expect(screen.getByText('Hello test')).toBeInTheDocument();
 | 
					    expect(screen.getByText('Hello test')).toBeInTheDocument();
 | 
				
			||||||
@ -34,20 +30,20 @@ describe('FormatMessage component', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  it('throws an error when locale is empty', async () => {
 | 
					  it('throws an error when locale is empty', async () => {
 | 
				
			||||||
    await locale.set(undefined);
 | 
					    await locale.set(undefined);
 | 
				
			||||||
    expect(() => render(FormatMessage, { message: undefined })).toThrowError();
 | 
					    expect(() => render(FormatMessage, { key: '' })).toThrowError();
 | 
				
			||||||
    await locale.set('en');
 | 
					    await locale.set('en');
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('shows raw message when value is empty', () => {
 | 
					  it('shows raw message when value is empty', () => {
 | 
				
			||||||
    render(FormatMessage, {
 | 
					    render(FormatMessage, {
 | 
				
			||||||
      message: $json('hello'),
 | 
					      key: 'hello',
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    expect(screen.getByText('Hello {name}')).toBeInTheDocument();
 | 
					    expect(screen.getByText('Hello {name}')).toBeInTheDocument();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('shows message when slot is empty', () => {
 | 
					  it('shows message when slot is empty', () => {
 | 
				
			||||||
    render(FormatMessage, {
 | 
					    render(FormatMessage, {
 | 
				
			||||||
      message: $json('html'),
 | 
					      key: 'html',
 | 
				
			||||||
      values: { name: 'test' },
 | 
					      values: { name: 'test' },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    expect(screen.getByText('Hello test')).toBeInTheDocument();
 | 
					    expect(screen.getByText('Hello test')).toBeInTheDocument();
 | 
				
			||||||
@ -55,7 +51,7 @@ describe('FormatMessage component', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  it('renders a message with html', () => {
 | 
					  it('renders a message with html', () => {
 | 
				
			||||||
    const { container } = render(FormatTagB, {
 | 
					    const { container } = render(FormatTagB, {
 | 
				
			||||||
      message: $json('html'),
 | 
					      key: 'html',
 | 
				
			||||||
      values: { name: 'test' },
 | 
					      values: { name: 'test' },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    expect(container.innerHTML).toBe('Hello <strong>test</strong>');
 | 
					    expect(container.innerHTML).toBe('Hello <strong>test</strong>');
 | 
				
			||||||
@ -63,7 +59,7 @@ describe('FormatMessage component', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  it('renders a message with html and plural', () => {
 | 
					  it('renders a message with html and plural', () => {
 | 
				
			||||||
    const { container } = render(FormatTagB, {
 | 
					    const { container } = render(FormatTagB, {
 | 
				
			||||||
      message: $json('plural'),
 | 
					      key: 'plural',
 | 
				
			||||||
      values: { count: 1 },
 | 
					      values: { count: 1 },
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    expect(container.innerHTML).toBe('You have <strong>1 item</strong>');
 | 
					    expect(container.innerHTML).toBe('You have <strong>1 item</strong>');
 | 
				
			||||||
@ -71,8 +67,13 @@ describe('FormatMessage component', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  it('protects agains XSS injection', () => {
 | 
					  it('protects agains XSS injection', () => {
 | 
				
			||||||
    render(FormatMessage, {
 | 
					    render(FormatMessage, {
 | 
				
			||||||
      message: $json('xss'),
 | 
					      key: 'xss',
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    expect(screen.getByText('<image/src/onerror=prompt(8)>')).toBeInTheDocument();
 | 
					    expect(screen.getByText('<image/src/onerror=prompt(8)>')).toBeInTheDocument();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('displays the message key when not found', () => {
 | 
				
			||||||
 | 
					    render(FormatMessage, { key: 'invalid.key' });
 | 
				
			||||||
 | 
					    expect(screen.getByText('invalid.key')).toBeInTheDocument();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -2,11 +2,11 @@
 | 
				
			|||||||
  import FormatMessage from '../format-message.svelte';
 | 
					  import FormatMessage from '../format-message.svelte';
 | 
				
			||||||
  import type { ComponentProps } from 'svelte';
 | 
					  import type { ComponentProps } from 'svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let message: unknown;
 | 
					  export let key: string;
 | 
				
			||||||
  export let values: ComponentProps<FormatMessage>['values'];
 | 
					  export let values: ComponentProps<FormatMessage>['values'];
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<FormatMessage {message} {values} let:tag let:message>
 | 
					<FormatMessage {key} {values} let:tag let:message>
 | 
				
			||||||
  {#if tag === 'b'}
 | 
					  {#if tag === 'b'}
 | 
				
			||||||
    <strong>{message}</strong>
 | 
					    <strong>{message}</strong>
 | 
				
			||||||
  {/if}
 | 
					  {/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,11 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import { IntlMessageFormat, type FormatXMLElementFn, type PrimitiveType } from 'intl-messageformat';
 | 
					  import { IntlMessageFormat, type FormatXMLElementFn, type PrimitiveType } from 'intl-messageformat';
 | 
				
			||||||
  import { TYPE, type MessageFormatElement } from '@formatjs/icu-messageformat-parser';
 | 
					  import { TYPE, type MessageFormatElement } from '@formatjs/icu-messageformat-parser';
 | 
				
			||||||
  import { locale as i18nLocale } from 'svelte-i18n';
 | 
					  import { locale as i18nLocale, json } from 'svelte-i18n';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  type InterpolationValues = Record<string, PrimitiveType | FormatXMLElementFn<unknown>>;
 | 
					  type InterpolationValues = Record<string, PrimitiveType | FormatXMLElementFn<unknown>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let message: unknown;
 | 
					  export let key: string;
 | 
				
			||||||
  export let values: InterpolationValues = {};
 | 
					  export let values: InterpolationValues = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getLocale = (locale?: string | null) => {
 | 
					  const getLocale = (locale?: string | null) => {
 | 
				
			||||||
@ -16,13 +16,13 @@
 | 
				
			|||||||
    return locale;
 | 
					    return locale;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getElements = (message: unknown, locale: string): MessageFormatElement[] => {
 | 
					  const getElements = (message: string, locale: string): MessageFormatElement[] => {
 | 
				
			||||||
    return new IntlMessageFormat(message as string, locale, undefined, {
 | 
					    return new IntlMessageFormat(message as string, locale, undefined, {
 | 
				
			||||||
      ignoreTag: false,
 | 
					      ignoreTag: false,
 | 
				
			||||||
    }).getAst();
 | 
					    }).getAst();
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const getParts = (message: unknown, locale: string) => {
 | 
					  const getParts = (message: string, locale: string) => {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const elements = getElements(message, locale);
 | 
					      const elements = getElements(message, locale);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -38,12 +38,13 @@
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
      if (error instanceof Error) {
 | 
					      if (error instanceof Error) {
 | 
				
			||||||
        console.warn(`Message "${message}" has syntax error:`, error.message);
 | 
					        console.warn(`Message "${key}" has syntax error:`, error.message);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return [{ message: message as string, tag: undefined }];
 | 
					      return [{ message: message as string, tag: undefined }];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $: message = ($json(key) as string) || key;
 | 
				
			||||||
  $: locale = getLocale($i18nLocale);
 | 
					  $: locale = getLocale($i18nLocale);
 | 
				
			||||||
  $: parts = getParts(message, locale);
 | 
					  $: parts = getParts(message, locale);
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@
 | 
				
			|||||||
  import Button from '../elements/buttons/button.svelte';
 | 
					  import Button from '../elements/buttons/button.svelte';
 | 
				
			||||||
  import Icon from '../elements/icon.svelte';
 | 
					  import Icon from '../elements/icon.svelte';
 | 
				
			||||||
  import OnboardingCard from './onboarding-card.svelte';
 | 
					  import OnboardingCard from './onboarding-card.svelte';
 | 
				
			||||||
  import { json, t } from 'svelte-i18n';
 | 
					  import { t } from 'svelte-i18n';
 | 
				
			||||||
  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
					  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const dispatch = createEventDispatcher<{
 | 
					  const dispatch = createEventDispatcher<{
 | 
				
			||||||
@ -30,7 +30,7 @@
 | 
				
			|||||||
  </p>
 | 
					  </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <p>
 | 
					  <p>
 | 
				
			||||||
    <FormatMessage message={$json('admin.storage_template_onboarding_description')} let:message>
 | 
					    <FormatMessage key="admin.storage_template_onboarding_description" let:message>
 | 
				
			||||||
      <a class="underline" href="https://immich.app/docs/administration/storage-template">{message}</a>
 | 
					      <a class="underline" href="https://immich.app/docs/administration/storage-template">{message}</a>
 | 
				
			||||||
    </FormatMessage>
 | 
					    </FormatMessage>
 | 
				
			||||||
  </p>
 | 
					  </p>
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@
 | 
				
			|||||||
  import type { ServerVersionResponseDto } from '@immich/sdk';
 | 
					  import type { ServerVersionResponseDto } from '@immich/sdk';
 | 
				
			||||||
  import Button from '../elements/buttons/button.svelte';
 | 
					  import Button from '../elements/buttons/button.svelte';
 | 
				
			||||||
  import FullScreenModal from './full-screen-modal.svelte';
 | 
					  import FullScreenModal from './full-screen-modal.svelte';
 | 
				
			||||||
  import { json, t } from 'svelte-i18n';
 | 
					  import { t } from 'svelte-i18n';
 | 
				
			||||||
  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
					  import FormatMessage from '$lib/components/i18n/format-message.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let showModal = false;
 | 
					  let showModal = false;
 | 
				
			||||||
@ -37,7 +37,7 @@
 | 
				
			|||||||
{#if showModal}
 | 
					{#if showModal}
 | 
				
			||||||
  <FullScreenModal title="🎉 NEW VERSION AVAILABLE" onClose={() => (showModal = false)}>
 | 
					  <FullScreenModal title="🎉 NEW VERSION AVAILABLE" onClose={() => (showModal = false)}>
 | 
				
			||||||
    <div>
 | 
					    <div>
 | 
				
			||||||
      <FormatMessage message={$json('version_announcement_message')} let:tag let:message>
 | 
					      <FormatMessage key="version_announcement_message" let:tag let:message>
 | 
				
			||||||
        {#if tag === 'link'}
 | 
					        {#if tag === 'link'}
 | 
				
			||||||
          <span class="font-medium underline">
 | 
					          <span class="font-medium underline">
 | 
				
			||||||
            <a href="https://github.com/immich-app/immich/releases/latest" target="_blank" rel="noopener noreferrer">
 | 
					            <a href="https://github.com/immich-app/immich/releases/latest" target="_blank" rel="noopener noreferrer">
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user