PronounsPage/app/pages/langswitch.vue
Valentyne Stigloher 10180aa6a3 (refactor) use #shared alias instead of ~~/shared
the #shared alias used by Nuxt cannot be easily disabled and to prevent breackage with jiti, we make use of it
2025-08-17 18:56:02 +02:00

85 lines
2.5 KiB
Vue

<script setup lang="ts">
import { useRoute, useNuxtApp } from '#app';
import { clearUrl } from '#shared/helpers.ts';
import useConfig from '~/composables/useConfig.ts';
import useSimpleHead from '~/composables/useSimpleHead.ts';
import { getUrlForLocale } from '~/src/domain.ts';
const { $locales: locales, $t, $translator: translator } = useNuxtApp();
const config = useConfig();
const route = useRoute();
useSimpleHead({
title: translator.translate('macrolanguage.title'),
description: translator.translate('macrolanguage.prompt'),
}, translator);
definePageMeta({
translatedPaths: (config) => {
if (!config.macrolanguage?.enabled) {
return [];
}
return ['/langswitch'];
},
});
const getSanitizedReturnPath = (): string => {
const returnPath = route.query.return;
if (!returnPath || typeof returnPath !== 'string' || returnPath.length > 128) {
console.warn('Invalid return path:', returnPath);
return '/';
}
// Validate the return path:
// - must start with /
// - must only match Unicode word characters, at-signs, hyphens, slashes, dots, colons, and underscores
// - must not have any ".." segments
const segments = returnPath.split('/').filter(Boolean);
if (segments.some((segment) => !/^[\w\-/:.@]+$/u.test(segment) || segment === '..')) {
return '/';
}
return `/${segments.join('/')}`;
};
const returnPath = getSanitizedReturnPath();
const options = (config.macrolanguage?.languages || []).map(({ code }) => ({
key: code,
name: locales[code]?.name,
extra: locales[code]?.extra,
url: `${getUrlForLocale(code)}${returnPath}`,
domain: clearUrl(getUrlForLocale(code)),
}));
</script>
<template>
<div v-if="config.macrolanguage?.enabled" class="container">
<div class="mb-4 d-none d-lg-block">
&nbsp;
</div>
<p class="mb-3 fs-5">
{{ $t('macrolanguage.prompt') }}
</p>
<div class="list-group">
<a
v-for="option in options"
:key="option.key"
:href="option.url"
class="list-group-item list-group-item-action"
>
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">
{{ option.name }}
<small v-if="option.extra">({{ option.extra }})</small>
</h5>
</div>
<p class="mb-1">{{ option.domain }}</p>
</a>
</div>
</div>
</template>