Skip to content

feat(language): language switcher #172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jun 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions assets/icons/de-DE.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/icons/en-GB.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<template>
<CurrencySwitcherInner>
<LayoutCurrencySwitcherInner>
<template #currency-switcher-trigger>
<slot name="currency-switcher-trigger" />
</template>
<template #currency-switcher-content>
<slot name="currency-switcher-content" />
</template>
</CurrencySwitcherInner>
</LayoutCurrencySwitcherInner>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@ const selectedCurrencyId: Ref<undefined | AcceptableValue> = ref(undefined);

onMounted(() => {
selectedCurrencyId.value = currency.value.id;
console.log('onmount', availableCurrencies.value);
});

const { data } = await getAvailableCurrencies();
if (data.value) {
// Remove fetchedAt & server, which is returned due to async fetch
const { fetchedAt, server, ...currencies } = data.value;
availableCurrencies.value = currencies;
console.log('danach', availableCurrencies);
}

const onUpdate = async (selectedId: AcceptableValue) => {
Expand All @@ -34,7 +32,7 @@ const onUpdate = async (selectedId: AcceptableValue) => {
<template>
<UiSelect :model-value="selectedCurrencyId" @update:model-value="onUpdate">
<slot name="currency-switcher-trigger">
<UiSelectTrigger class="border-none shadow-none">
<UiSelectTrigger class="border-none shadow-none p-0">
<UiSelectValue />
</UiSelectTrigger>
</slot>
Expand Down
10 changes: 10 additions & 0 deletions components/layout/LayoutLanguageSwitcher.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<template>
<LayoutLanguageSwitcherInner>
<template #language-switcher-trigger>
<slot name="language-switcher-trigger" />
</template>
<template #language-switcher-content>
<slot name="language-switcher-content" />
</template>
</LayoutLanguageSwitcherInner>
</template>
69 changes: 69 additions & 0 deletions components/layout/LayoutLanguageSwitcherInner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<script setup lang="ts">
import type { Schemas } from '@shopware/api-client/api-types';
import type { AcceptableValue } from 'reka-ui';

const { languages, changeLanguage, replaceToDevStorefront, getAvailableLanguages } =
useInternationalization();
const { languageIdChain } = useSessionContext();
const { handleError } = usePondHandleError();

const selectedLanguage: Ref<undefined | Schemas['Language']> = ref(undefined);
const selectedLanguageId = ref(languageIdChain);

onMounted(async () => {
await getAvailableLanguages();
if(selectedLanguageId.value) {
selectedLanguage.value = languages.value.find(language => language.id === selectedLanguageId.value);
}
});

const onUpdate = async (selectedValue: AcceptableValue): Promise<void> => {
if (typeof selectedValue === 'string') {
try {
const response = await changeLanguage(selectedValue);
// Update language
selectedLanguage.value = languages.value.find(language => language.id === selectedValue);
const redirectUrl = response.redirectUrl;
window.location.replace(replaceToDevStorefront(redirectUrl));
} catch {
handleError('[Pond][LayoutLanguageSwitchInner] Language switch failed');
}
}
};
</script>

<template>
<UiSelect :model-value="selectedLanguageId" @update:model-value="onUpdate">
<slot name="language-switcher-trigger">
<UiSelectTrigger class="border-none shadow-none p-0">
<UiSelectValue>
<template v-if="selectedLanguage">
<div class="flex items-center gap-1">
<Icon :name="`custom-icons:${selectedLanguage?.translationCode?.code}`" />
{{ selectedLanguage?.name }}
</div>
</template>
</UiSelectValue>
</UiSelectTrigger>
</slot>

<slot name="language-switcher-content">
<UiSelectContent>
<UiSelectGroup>
<UiSelectItem
v-for="language in languages"
:key="language.id"
:value="language.id"
>
<div class="flex items-center gap-1">
<template v-if="language.translationCode.code">
<Icon :name="`custom-icons:${language.translationCode.code}`" />
</template>
{{ language.name }}
</div>
</UiSelectItem>
</UiSelectGroup>
</UiSelectContent>
</slot>
</UiSelect>
</template>
11 changes: 8 additions & 3 deletions components/layout/footer/LayoutFooterSericeNavigationInner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@ const shopName = configStore.get('core.basicInformation.shopName') as string|nul

<template>
<div class="mt-4 grid justify-between gap-6 border-t border-gray-500/70 pt-4 text-sm text-gray-700 md:mt-8 md:flex md:pt-8">
<div class="flex gap-2.5 items-center">
<div class="flex gap-2 md:gap-4 items-center">
<slot name="copyright">
<span class="order-2 md:order-[unset]">
{{ $t('general.copyright', { 'companyName': shopName }) }}
</span>
</slot>
<slot name="currency-switcher">
<slot name="language-switcher">
<div class="order-3 md:order-[unset]">
<CurrencySwitcher />
<LayoutLanguageSwitcher />
</div>
</slot>
<slot name="currency-switcher">
<div class="order-4 md:order-[unset]">
<LayoutCurrencySwitcher />
</div>
</slot>
</div>
Expand Down
7 changes: 7 additions & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ export default defineNuxtConfig({
serverBundle: {
collections: ['mdi'],
},
customCollections: [
{
prefix: 'custom-icons',
dir: './assets/icons',
normalizeIconName: false,
},
],
},

components: {
Expand Down
4 changes: 2 additions & 2 deletions pages/[...all].vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ const { $i18n } = useNuxtApp();
const { locale } = useI18n();
const defaultLocale = $i18n.defaultLocale;
const routePath =
locale.value !== defaultLocale
(locale.value !== defaultLocale
? route.path.replace(/^\/[^/]+/, '')
: route.path;
: route.path) || '/';

const { data: seoResult } = await usePondCacheAsyncData(`seoPath-${routePath}`, async () => {
// For client links if the history state contains seo url information we can omit the api call
Expand Down