ivanhofer/typesafe-i18n-demo-sveltekit

Hovering on LocaleSwitcher menu changes locale

Closed this issue · 3 comments

I've got a bit of a weird problem. I'm using svelte headlessui + tailwindui + typesafe i18n and I implemented a LocaleSwitcher.svelte like:

<script lang="ts">
  import type { Locales } from '$lib/i18n/i18n-types';
  import { setLocale, locale } from '$lib/i18n/i18n-svelte';
  import { loadLocaleAsync } from '$lib/i18n/i18n-util.async';
  import { replaceLocaleInUrl } from '$lib/utils';
  import { page } from '$app/stores';
  import { invalidateAll } from '$app/navigation';
  import { browser } from '$app/environment';
  import { Menu, MenuButton, Transition, MenuItems, MenuItem } from '@rgossiaux/svelte-headlessui';
  import {
    ChevronDownIcon,
    DocumentDuplicateIcon,
    GlobeIcon
  } from '@rgossiaux/svelte-heroicons/outline';
  import { baseLocale, locales } from '$lib/i18n/i18n-util';
  import { localeIcons } from '$lib/utils/locale-utils/locale-utils';

  // This lang value is used to implement the empty route = baseLocale logic
  // if $page.params.lang is null or undefined, it should be baseLocale
  $: paramLang = ($page.params.lang as Locales) ?? baseLocale;

  const switchLocale = async (newLocale: Locales, updateHistoryState = true) => {
    debugger;
    if (!newLocale || $locale === newLocale) return;

    // load new dictionary from server
    await loadLocaleAsync(newLocale);

    // select locale
    setLocale(newLocale);

    // update `lang` attribute
    document.querySelector('html')?.setAttribute('lang', newLocale);

    if (updateHistoryState) {
      // update url to reflect locale changes
      history.pushState({ locale: newLocale }, '', replaceLocaleInUrl($page.url, newLocale));
    }

    // run the `load` function again
    invalidateAll();
  };

  // update locale when navigating via browser back/forward buttons
  const handlePopStateEvent = async ({ state }: PopStateEvent) => switchLocale(state.locale, false);

  // update locale when page store changes
  $: if (browser) {
    const lang = paramLang;
    switchLocale(lang, false);
    history.replaceState(
      { ...history.state, locale: lang },
      '',
      replaceLocaleInUrl($page.url, lang)
    );
  }
</script>

<!-- This line handles back and next browser actions -->
<svelte:window on:popstate={handlePopStateEvent} />

<Menu as="div" class="relative inline-block text-left">
  <div>
    <MenuButton class="btn-outline btn-secondary btn">
      <GlobeIcon class="h-6 w-6" />
      <ChevronDownIcon class="-mr-1 ml-2 h-5 w-5" aria-hidden="true" />
    </MenuButton>
  </div>

  <Transition
    enter="transition ease-out duration-100"
    enterFrom="transform opacity-0 scale-95"
    enterTo="transform opacity-100 scale-100"
    leave="transition ease-in duration-75"
    leaveFrom="transform opacity-100 scale-100"
    leaveTo="transform opacity-0 scale-95"
  >
    <MenuItems
      class="absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
    >
      <div class="py-1">
        {#each locales as l}
          <MenuItem let:active>
            <a
              href={`${replaceLocaleInUrl($page.url, l)}`}
              class={`${
                active ? 'bg-gray-100 text-gray-900' : 'text-gray-700'
              } group flex items-center px-4 py-2 text-sm`}
            >
              <img class="mr-3 h-5 w-5" src={localeIcons[l]} alt="Icon for {l}" />
              {l}
            </a>
          </MenuItem>
        {/each}
      </div>
    </MenuItems>
  </Transition>
</Menu>

The problem is when I'm hovering over the <a> tags, it's switching the locale of the page. I just have no clue what might be the cause of this so I'm sorry if this is not the appropriate place. Any tips on how I might find what's happening here?

Adding data-sveltekit-preload-data='tap' to anchor tags fixed the issue.

I stumbled upon the same issue. This happens because preloaded page overrides current locale in the src/routes/+layout.ts as it's being re-run in browser.
I think I found an alternate way to resolve this. We just check if locale store is already set in browser and if yes, make so it overrides detected locale.

// src/routes/+layout.ts
import { locale as svelteLocale } from '$i18n/i18n-svelte.js';
import { browser } from '$app/environment';

export const load: LayoutLoad = async ({ data: { locale } }) => {
  // place at the top before everything else
  // prevent preloaded page locale from overriding current locale in browser
  if (browser) {
    const $svelteLocale = get(svelteLocale);
    if ($svelteLocale !== undefined) {
      locale = $svelteLocale;
    }
  }
  // resume normal behaviour

  // load dictionary into memory
  await loadLocaleAsync(locale);
  setLocale(locale);
}

This is no longer an issue in the latest repo update. See here for the change.