PronounsPage/pages/langswitch.vue
2025-01-16 14:24:49 -08:00

85 lines
2.5 KiB
Vue

<script setup lang="ts">
import { clearUrl } from '../src/helpers.ts';
import { useRoute, useNuxtApp } from '#app';
import useConfig from '~/composables/useConfig.ts';
import useSimpleHead from '~/composables/useSimpleHead.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: `${locales[code]?.url}${returnPath}`,
domain: clearUrl(locales[code]?.url || ''),
}));
</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>