generated from Seekra/repository-template
Merge branch 'main' into bug/colors
This commit is contained in:
@@ -364,6 +364,16 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|||||||
|
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
|
- @vue/devtools-api@6.6.4
|
||||||
|
|
||||||
|
This package contains the following license:
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
-----------
|
||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- json5@2.2.3
|
- json5@2.2.3
|
||||||
@@ -1371,6 +1381,67 @@ SOFTWARE.
|
|||||||
|
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
The following npm packages may be included in this product:
|
||||||
|
|
||||||
|
- @intlify/core-base@11.4.4
|
||||||
|
- @intlify/message-compiler@11.4.4
|
||||||
|
- @intlify/shared@11.4.4
|
||||||
|
- vue-i18n@11.4.4
|
||||||
|
|
||||||
|
These packages each contain the following license:
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2020 kazuya kawaguchi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
|
- @intlify/devtools-types@11.4.4
|
||||||
|
|
||||||
|
This package contains the following license:
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2024 kazuya kawaguchi
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
-----------
|
||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- hookable@5.5.3
|
- hookable@5.5.3
|
||||||
|
|||||||
Generated
+84
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"terser": "^5.47.1",
|
"terser": "^5.47.1",
|
||||||
"vue": "^3.5.32",
|
"vue": "^3.5.32",
|
||||||
|
"vue-i18n": "^11.4.4",
|
||||||
"vue-router": "^5.0.6"
|
"vue-router": "^5.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -106,6 +107,63 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@intlify/core-base": {
|
||||||
|
"version": "11.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.4.tgz",
|
||||||
|
"integrity": "sha512-w/vItlylrAmhebkIbVl5YY8XMCtj8Mb2g70ttxktMYuf5AuRahgEHL2iLgLIsZBIbTSgs4hkUo7ucCL0uTJvOg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/devtools-types": "11.4.4",
|
||||||
|
"@intlify/message-compiler": "11.4.4",
|
||||||
|
"@intlify/shared": "11.4.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/kazupon"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@intlify/devtools-types": {
|
||||||
|
"version": "11.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.4.4.tgz",
|
||||||
|
"integrity": "sha512-PcBLmGmDQsTSVV911P8upzpcLJO1CNVYi/IH6bGnLR2nA+0L963+kXN1ZrisTEnbtw2ewN6HMMSldqzjronA0Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/core-base": "11.4.4",
|
||||||
|
"@intlify/shared": "11.4.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/kazupon"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@intlify/message-compiler": {
|
||||||
|
"version": "11.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.4.4.tgz",
|
||||||
|
"integrity": "sha512-vn0OAV9pYkJlPPmgnsSm5eAG3mL0+9C/oaded2JY9jmxBbhmUXT3TcAUY8WRgLY9Hte7lkUJKpXrVlYjMXBD2w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/shared": "11.4.4",
|
||||||
|
"source-map-js": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/kazupon"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@intlify/shared": {
|
||||||
|
"version": "11.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.4.4.tgz",
|
||||||
|
"integrity": "sha512-QRUCHqda1U6aR14FR0vvXD4+4gj6+fm0AhAozvSuRCw0fCvrmCugWpgiR4xH2NI6s8am6N9p5OhirplsX8ZS3g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/kazupon"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
@@ -2105,6 +2163,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vue-i18n": {
|
||||||
|
"version": "11.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.4.tgz",
|
||||||
|
"integrity": "sha512-gIbXVSFQV4jcSJxfwdZ5zSZmZ+12CnX0K3vBkRSd6Zn+HSzCp+QwUgPwpD/uN0oKNKI9RzlUXPKVedEuMgNG0A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@intlify/core-base": "11.4.4",
|
||||||
|
"@intlify/devtools-types": "11.4.4",
|
||||||
|
"@intlify/shared": "11.4.4",
|
||||||
|
"@vue/devtools-api": "^6.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 22"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/kazupon"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vue-i18n/node_modules/@vue/devtools-api": {
|
||||||
|
"version": "6.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
|
||||||
|
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
|
||||||
|
},
|
||||||
"node_modules/vue-router": {
|
"node_modules/vue-router": {
|
||||||
"version": "5.0.6",
|
"version": "5.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.6.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"terser": "^5.47.1",
|
"terser": "^5.47.1",
|
||||||
"vue": "^3.5.32",
|
"vue": "^3.5.32",
|
||||||
|
"vue-i18n": "^11.4.4",
|
||||||
"vue-router": "^5.0.6"
|
"vue-router": "^5.0.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ limitations under the License.
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { inject } from 'vue';
|
import { inject } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const colorScheme = inject('colorScheme');
|
const colorScheme = inject('colorScheme');
|
||||||
|
|
||||||
@@ -25,24 +28,22 @@ const colorSchemeNextMapper = {
|
|||||||
'auto': 'light'
|
'auto': 'light'
|
||||||
};
|
};
|
||||||
|
|
||||||
const colorSchemeTextMapper = {
|
|
||||||
'light': 'Switch to light mode',
|
|
||||||
'dark': 'Switch to dark mode',
|
|
||||||
'auto': 'Switch to the system scheme'
|
|
||||||
}
|
|
||||||
|
|
||||||
const colorSchemeIconMapper = {
|
const colorSchemeIconMapper = {
|
||||||
'dark': '⏾',
|
'dark': '⏾',
|
||||||
'light': '☀',
|
'light': '☀',
|
||||||
'auto': '◐'
|
'auto': '◐'
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const getTooltipTranslation = function (colorScheme) {
|
||||||
|
return t(`preferences.colorScheme.switch.${colorSchemeNextMapper[colorScheme]}`);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button class="color-scheme-button"
|
<button class="color-scheme-button"
|
||||||
@click="colorScheme = colorSchemeNextMapper[colorScheme]"
|
@click="colorScheme = colorSchemeNextMapper[colorScheme]"
|
||||||
:aria-label="colorSchemeTextMapper[colorSchemeNextMapper[colorScheme]]"
|
:aria-label="getTooltipTranslation(colorScheme)"
|
||||||
:title="colorSchemeTextMapper[colorSchemeNextMapper[colorScheme]]"
|
:title="getTooltipTranslation(colorScheme)"
|
||||||
>
|
>
|
||||||
{{ colorSchemeIconMapper[colorSchemeNextMapper[colorScheme]] }}
|
{{ colorSchemeIconMapper[colorSchemeNextMapper[colorScheme]] }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2026 Seekra
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { loadLanguage, LANGUAGES_RTL, SUPPORTED_LANGUAGES } from '@/i18n';
|
||||||
|
|
||||||
|
const { t, locale } = useI18n();
|
||||||
|
|
||||||
|
const isOpen = ref(false);
|
||||||
|
const languageDropdown = ref(null);
|
||||||
|
|
||||||
|
async function selectLanguage(code) {
|
||||||
|
await loadLanguage(code);
|
||||||
|
localStorage.setItem('locale', code);
|
||||||
|
document.documentElement.lang = code;
|
||||||
|
document.documentElement.dir = LANGUAGES_RTL.includes(code) ? 'rtl' : 'ltr';
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = function () {
|
||||||
|
document.removeEventListener('click', closeWrapperOnClickOutsite);
|
||||||
|
isOpen.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeWrapperOnClickOutsite = function (e) {
|
||||||
|
if (languageDropdown.value) {
|
||||||
|
if (!languageDropdown.value.contains(e.target)) {
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const open = function () {
|
||||||
|
if (!isOpen.value) {
|
||||||
|
isOpen.value = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
document.addEventListener('click', closeWrapperOnClickOutsite);
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="language-switch" tabindex="-1">
|
||||||
|
<button
|
||||||
|
class="language-button button"
|
||||||
|
@click="open"
|
||||||
|
:aria-expanded="isOpen"
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
>
|
||||||
|
<span class="lang-code">{{ t(`preferences.locale.languages.${locale}`) }}</span>
|
||||||
|
<span class="chevron" :class="{ open: isOpen }">▾</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<ul v-if="isOpen" ref="languageDropdown" class="language-dropdown" role="listbox">
|
||||||
|
<li
|
||||||
|
v-for="lang in SUPPORTED_LANGUAGES"
|
||||||
|
:key="lang"
|
||||||
|
role="option"
|
||||||
|
:aria-selected="lang === locale"
|
||||||
|
:class="{ active: lang === locale }"
|
||||||
|
@click="selectLanguage(lang)"
|
||||||
|
>
|
||||||
|
<span class="lang-label">{{ t(`preferences.locale.languages.${lang}`) }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.language-switch {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--light-d-3);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-button:hover {
|
||||||
|
background-color: var(--light-d-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chevron {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chevron.open {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: calc(100% + 6px);
|
||||||
|
background-color: var(--light-bg);
|
||||||
|
border: 1px solid var(--light-d-3);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px 0;
|
||||||
|
min-width: 160px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-dropdown li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-dropdown li:hover {
|
||||||
|
background-color: var(--light-d-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-dropdown li.active {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -16,15 +16,19 @@ limitations under the License.
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ColorSchemeButton from '@/features/colorScheme/components/ColorSchemeButton.vue';
|
import ColorSchemeButton from '@/features/colorScheme/components/ColorSchemeButton.vue';
|
||||||
|
import LanguageSwitchButton from '@/features/i18n/components/LanguageSwitchButton.vue';
|
||||||
import logo from '@/assets/logo.svg';
|
import logo from '@/assets/logo.svg';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<nav class="global-nav">
|
<nav class="global-nav">
|
||||||
<RouterLink to="/" class="link button link">
|
<RouterLink to="/" class="link button link">
|
||||||
<img :src="logo" alt="Seekra" class="nav-logo" />
|
<img :src="logo" alt="Seekra" class="nav-logo" />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<ul class="right-links">
|
<ul class="right-links">
|
||||||
|
<li>
|
||||||
|
<LanguageSwitchButton />
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<ColorSchemeButton />
|
<ColorSchemeButton />
|
||||||
</li>
|
</li>
|
||||||
@@ -46,6 +50,7 @@ import logo from '@/assets/logo.svg';
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.global-nav .right-links a {
|
.global-nav .right-links a {
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ limitations under the License.
|
|||||||
const searchQuery = defineModel();
|
const searchQuery = defineModel();
|
||||||
|
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const props = defineProps(['autoSubmit'])
|
const props = defineProps(['autoSubmit'])
|
||||||
@@ -38,10 +41,10 @@ const submitSearch = function () {
|
|||||||
<input
|
<input
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="Search..."
|
:placeholder="t('search.searchBar.placeholder')"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<button type="submit" class="search-button">Search</button>
|
<button type="submit" class="search-button">{{ t('search.searchBar.submit') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ limitations under the License.
|
|||||||
<script setup>
|
<script setup>
|
||||||
import Searchbar from '@/features/search/components/Searchbar.vue';
|
import Searchbar from '@/features/search/components/Searchbar.vue';
|
||||||
|
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps(['searchQuery']);
|
const props = defineProps(['searchQuery']);
|
||||||
|
|
||||||
const searchQueryModel = defineModel();
|
const searchQueryModel = defineModel();
|
||||||
@@ -28,8 +32,8 @@ searchQueryModel.value = props.searchQuery;
|
|||||||
|
|
||||||
<div class="search-results-error-message-container">
|
<div class="search-results-error-message-container">
|
||||||
<div class="search-results-error-message">
|
<div class="search-results-error-message">
|
||||||
<p>Search is not available right now.</p>
|
<p>{{ t('search.error.searchNotAvailable') }}</p>
|
||||||
<p>Please try again to another time.</p>
|
<p>{{ t('error.tryAgainToAnotherTime') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-results-container">
|
<div class="search-results-container">
|
||||||
|
|||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Seekra
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createI18n } from 'vue-i18n';
|
||||||
|
import getCurrentLanguage from './utils/currentLanguage';
|
||||||
|
|
||||||
|
export const fallbackLocale = 'en';
|
||||||
|
|
||||||
|
|
||||||
|
export const LANGUAGES_RTL = [
|
||||||
|
'ar'
|
||||||
|
];
|
||||||
|
export const SUPPORTED_LANGUAGES = [
|
||||||
|
'en',
|
||||||
|
'de',
|
||||||
|
'fr',
|
||||||
|
'es',
|
||||||
|
'it',
|
||||||
|
'pt'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const i18n = createI18n({
|
||||||
|
legacy: false,
|
||||||
|
locale: getCurrentLanguage(),
|
||||||
|
fallbackLocale: fallbackLocale,
|
||||||
|
messages: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadedLanguages = new Set();
|
||||||
|
|
||||||
|
export async function loadLanguage (locale) {
|
||||||
|
if (!SUPPORTED_LANGUAGES.includes(locale)) {
|
||||||
|
locale = fallbackLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadedLanguages.has(locale)) {
|
||||||
|
i18n.global.locale.value = locale;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
const messages = (await import(`./locales/${locale}.json`)).default;
|
||||||
|
|
||||||
|
i18n.global.setLocaleMessage(locale, messages);
|
||||||
|
i18n.global.locale.value = locale;
|
||||||
|
|
||||||
|
loadedLanguages.add(locale);
|
||||||
|
};
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"search": {
|
||||||
|
"searchBar": {
|
||||||
|
"submit": "Suchen",
|
||||||
|
"placeholder": "Suchen..."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"searchNotAvailable": "Die Suche ist momentan nicht verfügbar."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"tryAgainToAnotherTime": "Bitte versuchen Sie es zu einem anderen Zeitpunkt erneut.",
|
||||||
|
"pageNotFound": "Die gesuchte Seite existiert nicht. Bitte überprüfen Sie die URL oder kehren Sie zur Suchseite zurück."
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"back": {
|
||||||
|
"search": "Zurück zur Suche"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preferences": {
|
||||||
|
"colorScheme": {
|
||||||
|
"switch": {
|
||||||
|
"light": "Zum hellen Modus wechseln",
|
||||||
|
"dark": "Zum dunklen Modus wechseln",
|
||||||
|
"auto": "Zum System-Farbschema wechseln"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"locale": {
|
||||||
|
"languages": {
|
||||||
|
"en": "English",
|
||||||
|
"de": "Deutsch",
|
||||||
|
"fr": "Français",
|
||||||
|
"es": "Español",
|
||||||
|
"it": "Italiano",
|
||||||
|
"pt": "Português"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"slogan": "Gebaut zum Suchen."
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"search": {
|
||||||
|
"searchBar": {
|
||||||
|
"submit": "Search",
|
||||||
|
"placeholder": "Search..."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"searchNotAvailable": "Search is not available right now."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"tryAgainToAnotherTime": "Please try again to another time.",
|
||||||
|
"pageNotFound": "The page you are looking for does not exist. Please check the URL or return to the search page."
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"back": {
|
||||||
|
"search": "Back to Search"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preferences": {
|
||||||
|
"colorScheme": {
|
||||||
|
"switch": {
|
||||||
|
"light": "Switch to light mode",
|
||||||
|
"dark": "Switch to dark mode",
|
||||||
|
"auto": "Switch to the system color scheme"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"locale": {
|
||||||
|
"languages": {
|
||||||
|
"en": "English",
|
||||||
|
"de": "Deutsch",
|
||||||
|
"fr": "Français",
|
||||||
|
"es": "Español",
|
||||||
|
"it": "Italiano",
|
||||||
|
"pt": "Português"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"slogan": "Built to search."
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"search": {
|
||||||
|
"searchBar": {
|
||||||
|
"submit": "Buscar",
|
||||||
|
"placeholder": "Buscar..."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"searchNotAvailable": "La búsqueda no está disponible en este momento."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"tryAgainToAnotherTime": "Por favor, inténtalo de nuevo más tarde.",
|
||||||
|
"pageNotFound": "La página que buscas no existe. Comprueba la URL o vuelve a la página de búsqueda."
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"back": {
|
||||||
|
"search": "Volver a la búsqueda"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preferences": {
|
||||||
|
"colorScheme": {
|
||||||
|
"switch": {
|
||||||
|
"light": "Cambiar al modo claro",
|
||||||
|
"dark": "Cambiar al modo oscuro",
|
||||||
|
"auto": "Usar el esquema de color del sistema"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"locale": {
|
||||||
|
"languages": {
|
||||||
|
"en": "English",
|
||||||
|
"de": "Deutsch",
|
||||||
|
"fr": "Français",
|
||||||
|
"es": "Español",
|
||||||
|
"it": "Italiano",
|
||||||
|
"pt": "Português"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"slogan": "Hecho para buscar."
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"search": {
|
||||||
|
"searchBar": {
|
||||||
|
"submit": "Rechercher",
|
||||||
|
"placeholder": "Rechercher..."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"searchNotAvailable": "La recherche n'est pas disponible pour le moment."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"tryAgainToAnotherTime": "Veuillez réessayer ultérieurement.",
|
||||||
|
"pageNotFound": "La page que vous recherchez n'existe pas. Vérifiez l'URL ou retournez à la page de recherche."
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"back": {
|
||||||
|
"search": "Retour à la recherche"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preferences": {
|
||||||
|
"colorScheme": {
|
||||||
|
"switch": {
|
||||||
|
"light": "Passer en mode clair",
|
||||||
|
"dark": "Passer en mode sombre",
|
||||||
|
"auto": "Utiliser le thème système"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"locale": {
|
||||||
|
"languages": {
|
||||||
|
"en": "English",
|
||||||
|
"de": "Deutsch",
|
||||||
|
"fr": "Français",
|
||||||
|
"es": "Español",
|
||||||
|
"it": "Italiano",
|
||||||
|
"pt": "Português"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"slogan": "Conçu pour chercher."
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"search": {
|
||||||
|
"searchBar": {
|
||||||
|
"submit": "Cerca",
|
||||||
|
"placeholder": "Cerca..."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"searchNotAvailable": "La ricerca non è disponibile al momento."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"tryAgainToAnotherTime": "Per favore riprova più tardi.",
|
||||||
|
"pageNotFound": "La pagina che cerchi non esiste. Controlla l'URL o torna alla pagina di ricerca."
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"back": {
|
||||||
|
"search": "Torna alla ricerca"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preferences": {
|
||||||
|
"colorScheme": {
|
||||||
|
"switch": {
|
||||||
|
"light": "Passa alla modalità chiara",
|
||||||
|
"dark": "Passa alla modalità scura",
|
||||||
|
"auto": "Usa la combinazione colori di sistema"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"locale": {
|
||||||
|
"languages": {
|
||||||
|
"en": "English",
|
||||||
|
"de": "Deutsch",
|
||||||
|
"fr": "Français",
|
||||||
|
"es": "Español",
|
||||||
|
"it": "Italiano",
|
||||||
|
"pt": "Português"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"slogan": "Costruito per cercare."
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"search": {
|
||||||
|
"searchBar": {
|
||||||
|
"submit": "Pesquisar",
|
||||||
|
"placeholder": "Pesquisar..."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"searchNotAvailable": "A pesquisa não está disponível no momento."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"tryAgainToAnotherTime": "Por favor, tente novamente mais tarde.",
|
||||||
|
"pageNotFound": "A página que você procura não existe. Verifique o URL ou volte à página de pesquisa."
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"back": {
|
||||||
|
"search": "Voltar à pesquisa"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preferences": {
|
||||||
|
"colorScheme": {
|
||||||
|
"switch": {
|
||||||
|
"light": "Mudar para modo claro",
|
||||||
|
"dark": "Mudar para modo escuro",
|
||||||
|
"auto": "Usar esquema de cores do sistema"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"locale": {
|
||||||
|
"languages": {
|
||||||
|
"en": "English",
|
||||||
|
"de": "Deutsch",
|
||||||
|
"fr": "Français",
|
||||||
|
"es": "Español",
|
||||||
|
"it": "Italiano",
|
||||||
|
"pt": "Português"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"slogan": "Feito para pesquisar."
|
||||||
|
}
|
||||||
+8
-1
@@ -16,8 +16,15 @@ limitations under the License.
|
|||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import { i18n, loadLanguage } from './i18n';
|
||||||
|
import getCurrentLanguage from './utils/currentLanguage';
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import './styles/common.css'
|
import './styles/common.css'
|
||||||
import './styles/variables/colors.css'
|
import './styles/variables/colors.css'
|
||||||
|
|
||||||
createApp(App).use(router).mount('#app')
|
await loadLanguage(getCurrentLanguage());
|
||||||
|
|
||||||
|
createApp(App)
|
||||||
|
.use(router)
|
||||||
|
.use(i18n)
|
||||||
|
.mount('#app')
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2026 Seekra
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function getCurrentLanguage () {
|
||||||
|
const saved = localStorage.getItem('locale');
|
||||||
|
if (saved) return saved;
|
||||||
|
const locale = new Intl.Locale(navigator.language);
|
||||||
|
return locale.language;
|
||||||
|
};
|
||||||
+10
-2
@@ -14,12 +14,20 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="not-found">
|
<div class="not-found">
|
||||||
<span class="error-message">
|
<span class="error-message">
|
||||||
The page you are looking for does not exist. Please check the URL or return to the search page.
|
{{ t('error.pageNotFound') }}
|
||||||
</span>
|
</span>
|
||||||
<RouterLink to="/" id="link">Back to Search</RouterLink>
|
<RouterLink to="/" id="link">
|
||||||
|
{{ t('links.back.search') }}
|
||||||
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ limitations under the License.
|
|||||||
import Searchbar from '../features/search/components/Searchbar.vue';
|
import Searchbar from '../features/search/components/Searchbar.vue';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
import logo from '@/assets/logo.svg';
|
import logo from '@/assets/logo.svg';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const searchQuery = ref('');
|
const searchQuery = ref('');
|
||||||
@@ -34,7 +36,7 @@ const submitSearch = function () {
|
|||||||
<header class="global-header">
|
<header class="global-header">
|
||||||
<img :src="logo" alt="Seekra" class="header-logo" />
|
<img :src="logo" alt="Seekra" class="header-logo" />
|
||||||
<span class="slogan">
|
<span class="slogan">
|
||||||
Built to search.
|
{{ t('slogan') }}
|
||||||
</span>
|
</span>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user