start multi language support

This commit is contained in:
jb-alvarado 2024-04-11 21:34:03 +02:00
parent 35705aa868
commit c9d8811203
11 changed files with 637 additions and 55 deletions

View File

@ -1,6 +1,6 @@
<template>
<div class="navbar bg-base-100 min-h-[52px] p-0 shadow">
<NuxtLink class="navbar-brand p-2" href="/">
<NuxtLink class="navbar-brand min-w-[46px] p-2" href="/">
<img src="~/assets/images/ffplayout-small.png" class="img-fluid" alt="Logo" width="30" height="30" />
</NuxtLink>
<div class="navbar-end w-1/5 grow">
@ -25,7 +25,7 @@
<details tabindex="0" @focusout="closeDropdown">
<summary>
<div class="h-[19px] text-base">
<span> Channels </span>
<span> {{ $t('button.channels') }} </span>
</div>
</summary>
<ul class="p-2">
@ -39,13 +39,13 @@
</li>
<li class="bg-base-100 rounded-md">
<button class="h-[27px] text-base" exactActiveClass="is-active" @click="logout()">
Logout
{{ $t('button.logout') }}
</button>
</li>
</ul>
</div>
</div>
<div class="navbar-end hidden md:flex w-4/5 min-w-[600px]">
<div class="navbar-end hidden md:flex w-4/5 min-w-[750px]">
<ul class="menu menu-sm menu-horizontal px-1">
<li v-for="item in menuItems" :key="item.name" class="bg-base-100 rounded-md p-0">
<NuxtLink
@ -62,7 +62,7 @@
<details tabindex="0" @focusout="closeDropdown">
<summary>
<div class="h-[19px] text-base">
<span> Channels </span>
<span> {{ $t('button.channels') }} </span>
</div>
</summary>
<ul class="p-2 bg-base-100 rounded-md !mt-1 w-36" tabindex="0">
@ -75,7 +75,9 @@
</details>
</li>
<li class="bg-base-100 rounded-md p-0">
<button class="h-[27px] pt-[4px] text-base" @click="logout()">Logout</button>
<button class="h-[27px] pt-[4px] text-base" @click="logout()">
{{ $t('button.logout') }}
</button>
</li>
<li class="p-0">
<label class="swap swap-rotate">
@ -91,18 +93,21 @@
<script setup lang="ts">
const colorMode = useColorMode()
const { t } = useI18n()
const localePath = useLocalePath()
const router = useRouter()
const authStore = useAuth()
const configStore = useConfig()
const indexStore = useIndex()
const router = useRouter()
const menuItems = ref([
{ name: 'Home', link: '/' },
{ name: 'Player', link: '/player' },
{ name: 'Media', link: '/media' },
{ name: 'Message', link: '/message' },
{ name: 'Logging', link: '/logging' },
{ name: 'Configure', link: '/configure' },
{ name: t('button.home'), link: localePath({ name: 'index' }) },
{ name: t('button.player'), link: localePath({ name: 'player' }) },
{ name: t('button.media'), link: localePath({ name: 'media' }) },
{ name: t('button.message'), link: localePath({ name: 'message' }) },
{ name: t('button.logging'), link: localePath({ name: 'logging' }) },
{ name: t('button.configure'), link: localePath({ name: 'configure' }) },
])
if (colorMode.value === 'dark') {
@ -117,7 +122,7 @@ function closeDropdown($event: any) {
function logout() {
authStore.removeToken()
router.push({ path: '/' })
router.push(localePath({ name: 'index' }))
}
function selectChannel(index: number) {

View File

@ -9,19 +9,19 @@
</div>
<div class="p-4 bg-base-100 flex items-center">
<span v-if="sysStat.system.ffp_version">
<strong>ffplayout version:</strong>
{{ sysStat.system.ffp_version }}
<strong>ffplayout:</strong>
v{{ sysStat.system.ffp_version }}
</span>
</div>
<div class="p-4 border border-primary">
<div class="text-xl">CPU</div>
<div class="text-xl">{{ $t('system.cpu') }}</div>
<div class="grid grid-cols-2">
<div><strong>Cores:</strong> {{ sysStat.cpu.cores }}</div>
<div><strong>Usage:</strong> {{ sysStat.cpu.usage.toFixed(2) }}%</div>
<div><strong>{{ $t('system.cores') }}:</strong> {{ sysStat.cpu.cores }}</div>
<div><strong>{{ $t('system.usage') }}:</strong> {{ sysStat.cpu.usage.toFixed(2) }}%</div>
</div>
</div>
<div class="p-4 border border-primary">
<div class="text-xl">Load</div>
<div class="text-xl">{{ $t('system.load') }}</div>
<div class="grid grid-cols-3">
<div>{{ sysStat.load.one }}</div>
<div>{{ sysStat.load.five }}</div>
@ -29,37 +29,37 @@
</div>
</div>
<div class="p-4 border border-primary">
<div class="text-xl">Memory</div>
<div class="text-xl">{{ $t('system.memory') }}</div>
<div class="grid grid-cols-2">
<div><strong>Total:</strong> {{ fileSize(sysStat.memory.total) }}</div>
<div><strong>Usage:</strong> {{ fileSize(sysStat.memory.used) }}</div>
<div><strong>{{ $t('system.total') }}:</strong> {{ fileSize(sysStat.memory.total) }}</div>
<div><strong>{{ $t('system.usage') }}:</strong> {{ fileSize(sysStat.memory.used) }}</div>
</div>
</div>
<div class="p-4 border border-primary">
<div class="text-xl">Swap</div>
<div class="text-xl">{{ $t('system.swap') }}</div>
<div class="grid grid-cols-2">
<div><strong>Total:</strong> {{ fileSize(sysStat.swap.total) }}</div>
<div><strong>Usage:</strong> {{ fileSize(sysStat.swap.used) }}</div>
<div><strong>{{ $t('system.total') }}:</strong> {{ fileSize(sysStat.swap.total) }}</div>
<div><strong>{{ $t('system.usage') }}:</strong> {{ fileSize(sysStat.swap.used) }}</div>
</div>
</div>
<div class="p-4 border border-primary">
<div class="text-xl">
Network <span v-if="sysStat.network" class="fs-6">{{ sysStat.network?.name }}</span>
{{ $t('system.network') }} <span v-if="sysStat.network" class="fs-6">{{ sysStat.network?.name }}</span>
</div>
<div class="grid grid-cols-2">
<div><strong>In:</strong> {{ fileSize(sysStat.network?.current_in) }}</div>
<div><strong>Out:</strong> {{ fileSize(sysStat.network?.current_out) }}</div>
<div><strong>{{ $t('system.in') }}:</strong> {{ fileSize(sysStat.network?.current_in) }}</div>
<div><strong>{{ $t('system.out') }}:</strong> {{ fileSize(sysStat.network?.current_out) }}</div>
<div>{{ fileSize(sysStat.network?.total_in) }}</div>
<div>{{ fileSize(sysStat.network?.total_out) }}</div>
</div>
</div>
<div v-if="sysStat.storage?.path" class="p-4 border border-primary">
<div class="text-xl">Storage</div>
<div class="text-xl">{{ $t('system.storage') }}</div>
<div v-if="sysStat.storage"><strong>Device:</strong> {{ sysStat.storage?.path }}</div>
<div class="grid grid-cols-2" v-if="sysStat.storage">
<div><strong>Size:</strong> {{ fileSize(sysStat.storage?.total) }}</div>
<div><strong>Used:</strong> {{ fileSize(sysStat.storage?.used) }}</div>
<div><strong>{{ $t('system.size') }}:</strong> {{ fileSize(sysStat.storage?.total) }}</div>
<div><strong>{{ $t('system.used') }}:</strong> {{ fileSize(sysStat.storage?.used) }}</div>
</div>
</div>
<div v-else class="col-6 bg-primary p-2 border" />

View File

@ -1,13 +1,17 @@
<template>
<NuxtLayout>
<div class="container d-flex align-items-center justify-content-center">
<div class="w-full h-full flex justify-center items-center mt-20">
<div v-if="props.error?.statusCode === 404">
<h1 class="display-1 text-center">404</h1>
<p class="text-center mt-10">Page not found</p>
<h1 class="text-center text-6xl">404</h1>
<p class="text-center font-bold mt-6">
{{ $t('error.notFound') }}
</p>
</div>
<div v-else-if="props.error?.statusCode === 500">
<h1 class="display-1 text-center">{{ props.error.statusCode }}</h1>
<p class="text-center mt-10">Internal server error</p>
<h1 class="text-center text-6xl">{{ props.error.statusCode }}</h1>
<p class="text-center font-bold mt-6">
{{ $t('error.serverError') }}
</p>
</div>
</div>
</NuxtLayout>
@ -15,6 +19,7 @@
<script setup lang="ts">
import type { NuxtError } from '#app'
const localePath = useLocalePath()
const props = defineProps({
error: Object as () => NuxtError,
@ -26,7 +31,7 @@ onMounted(() => {
if (statusCode >= 400) {
setTimeout(() => {
reloadNuxtApp({
path: '/',
path: localePath({ name: 'index' }),
ttl: 1000,
})
}, 3000)

44
lang/README.md Normal file
View File

@ -0,0 +1,44 @@
## Add Language
You are very welcome to add more languages! Just copy en-US.js to the correct target Country code and modify the content.
When you are done with the translation add the filename to [nuxt.config.ts](../nuxt.config.ts), in section:
```DIFF
i18n: {
locales: [
{
code: 'de',
name: 'Deutsch',
file: 'de-DE.js',
},
{
code: 'en',
name: 'English',
file: 'en-US.js',
},
+ {
+ code: '<SHORT CODE>',
+ name: '<NAME>',
+ file: '<CODE>.js',
+ },
],
...
```
And also add the new link paths:
```DIFF
i18n: {
locales: [...],
...
pages: {
'player': {
de: '/wiedergabe',
en: '/player',
+ <CODE>: /<PATH>,
},
...
```

40
lang/de-DE.js Normal file
View File

@ -0,0 +1,40 @@
export default {
alert: {
wrongLogin: 'Falsche Anmeldedaten!',
},
button: {
login: 'Anmelden',
home: 'Start',
player: 'Wiedergabe',
media: 'Medien',
message: 'Nachrichten',
logging: 'Protokollierung',
channels: 'Kanäle',
configure: 'Einstellungen',
logout: 'Abmelden',
},
error: {
notFound: 'Seite nicht gefunden',
serverError: 'Interner Server Fehler',
},
input: {
username: 'Benutzername',
password: 'Passwort',
},
system: {
cpu: 'CPU',
cores: 'Kerne',
load: 'Auslastung',
memory: 'Arbeitsspeicher',
swap: 'Auslagerungsspeicher',
total: 'Gesamt',
usage: 'Verwendung',
network: 'Netzwerk',
in: 'Eingehend',
out: 'Ausgehend',
storage: 'Speicher',
device: 'Gerät',
size: 'Größe',
used: 'Genutzt',
},
}

40
lang/en-US.js Normal file
View File

@ -0,0 +1,40 @@
export default {
alert: {
wrongLogin: 'Incorrect login data!',
},
button: {
login: 'Login',
home: 'Home',
player: 'Player',
media: 'Media',
message: 'Message',
logging: 'Logging',
channels: 'Channels',
configure: 'Configure',
logout: 'Logout',
},
error: {
notFound: 'Page not found',
serverError: 'Internal server error',
},
input: {
username: 'Username',
password: 'Password',
},
system: {
cpu: 'CPU',
cores: 'Cores',
load: 'Load',
memory: 'Memory',
swap: 'Swap',
total: 'Total',
usage: 'Usage',
network: 'Network',
in: 'In',
out: 'Out',
storage: 'Storage',
device: 'Device',
size: 'Size',
used: 'Used',
},
}

View File

@ -1,9 +1,10 @@
export default defineNuxtRouteMiddleware((to, from) => {
const auth = useAuth()
const localePath = useLocalePath()
auth.inspectToken()
if (!auth.isLogin && to.path !== '/') {
return navigateTo('/')
if (!auth.isLogin && !String(to.name).includes('index_')) {
return navigateTo(localePath({ name: 'index' }))
}
})

View File

@ -45,7 +45,7 @@ export default defineNuxtConfig({
},
},
modules: ['@nuxtjs/color-mode', '@pinia/nuxt', '@vueuse/nuxt', '@nuxtjs/tailwindcss'],
modules: ['@nuxtjs/color-mode', '@nuxtjs/i18n', '@pinia/nuxt', '@vueuse/nuxt', '@nuxtjs/tailwindcss'],
css: ['@/assets/scss/main.scss'],
colorMode: {
@ -60,6 +60,55 @@ export default defineNuxtConfig({
storageKey: 'theme',
},
i18n: {
locales: [
{
code: 'de',
name: 'Deutsch',
file: 'de-DE.js',
},
{
code: 'en',
name: 'English',
file: 'en-US.js',
},
],
customRoutes: 'config',
pages: {
'player': {
de: '/wiedergabe',
en: '/player',
},
'media': {
de: '/medien',
en: '/media',
},
'message': {
de: '/nachrichten',
en: '/message',
},
'logging': {
de: '/protokollierung',
en: '/logging',
},
'configure': {
de: '/einstellungen',
en: '/configure',
},
},
detectBrowserLanguage: {
useCookie: true,
alwaysRedirect: true,
},
// debug: true,
langDir: 'lang',
defaultLocale: 'en',
compilation: {
strictMessage: false,
},
},
vite: {
build: {
chunkSizeWarningLimit: 800000,

359
package-lock.json generated
View File

@ -31,6 +31,7 @@
},
"devDependencies": {
"@nuxtjs/eslint-config": "^12.0.0",
"@nuxtjs/i18n": "^8.3.0",
"@nuxtjs/tailwindcss": "^6.11.4",
"@types/lodash": "^4.17.0",
"@types/video.js": "^7.3.57",
@ -1477,6 +1478,180 @@
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@intlify/bundle-utils": {
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-7.5.1.tgz",
"integrity": "sha512-UovJl10oBIlmYEcWw+VIHdKY5Uv5sdPG0b/b6bOYxGLln3UwB75+2dlc0F3Fsa0RhoznQ5Rp589/BZpABpE4Xw==",
"dev": true,
"dependencies": {
"@intlify/message-compiler": "^9.4.0",
"@intlify/shared": "^9.4.0",
"acorn": "^8.8.2",
"escodegen": "^2.1.0",
"estree-walker": "^2.0.2",
"jsonc-eslint-parser": "^2.3.0",
"magic-string": "^0.30.0",
"mlly": "^1.2.0",
"source-map-js": "^1.0.1",
"yaml-eslint-parser": "^1.2.2"
},
"engines": {
"node": ">= 14.16"
},
"peerDependenciesMeta": {
"petite-vue-i18n": {
"optional": true
},
"vue-i18n": {
"optional": true
}
}
},
"node_modules/@intlify/bundle-utils/node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true
},
"node_modules/@intlify/core": {
"version": "9.11.1",
"resolved": "https://registry.npmjs.org/@intlify/core/-/core-9.11.1.tgz",
"integrity": "sha512-mAWVGArvUgY8jw2WVfBndW77VTqU/JMt36KhZtczYtyw6+W3J4fJ/QeoE2ZHXv14GqR2BCsLGaQfTvVHR2/a/w==",
"dev": true,
"dependencies": {
"@intlify/core-base": "9.11.1",
"@intlify/shared": "9.11.1"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/core-base": {
"version": "9.11.1",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.11.1.tgz",
"integrity": "sha512-qWXBBlEA+DC0CsHkfJiQK9ELm11c9I6lDpodY4FoOf99eMas1R6JR4woPhrfAcrtxFHp1UmXWdrQNKDegSW9IA==",
"dev": true,
"dependencies": {
"@intlify/message-compiler": "9.11.1",
"@intlify/shared": "9.11.1"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/h3": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@intlify/h3/-/h3-0.5.0.tgz",
"integrity": "sha512-cgfrtD3qu3BPJ47gfZ35J2LJpI64Riic0K8NGgid5ilyPXRQTNY7mXlT/B+HZYQg1hmBxKa5G5HJXyAZ4R2H5A==",
"dev": true,
"dependencies": {
"@intlify/core": "^9.8.0",
"@intlify/utils": "^0.12.0"
},
"engines": {
"node": ">= 18"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/message-compiler": {
"version": "9.11.1",
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.11.1.tgz",
"integrity": "sha512-y/aWx7DkaTKK2qWUw0hVbJpon8+urWXngeqh15DuIXZh6n/V/oPQiO/Ho1hUKbwap6MVMuz0OcnAJvqh3p9YPg==",
"dev": true,
"dependencies": {
"@intlify/shared": "9.11.1",
"source-map-js": "^1.0.2"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/shared": {
"version": "9.11.1",
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.11.1.tgz",
"integrity": "sha512-yuDG82vjgId2oasNRgZ0PKJrF65zlL33MNyITP5itbLcP4AYOR/NcIuD+/DiI+GHXdxASMKJU0ZiITLc6RC+qw==",
"dev": true,
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@intlify/unplugin-vue-i18n": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-3.0.1.tgz",
"integrity": "sha512-q1zJhA/WpoLBzAAuKA5/AEp0e+bMOM10ll/HxT4g1VAw/9JhC4TTobP9KobKH90JMZ4U2daLFlYQfKNd29lpqw==",
"dev": true,
"dependencies": {
"@intlify/bundle-utils": "^7.4.0",
"@intlify/shared": "^9.4.0",
"@rollup/pluginutils": "^5.1.0",
"@vue/compiler-sfc": "^3.2.47",
"debug": "^4.3.3",
"fast-glob": "^3.2.12",
"js-yaml": "^4.1.0",
"json5": "^2.2.3",
"pathe": "^1.0.0",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2",
"unplugin": "^1.1.0"
},
"engines": {
"node": ">= 14.16"
},
"peerDependencies": {
"petite-vue-i18n": "*",
"vue-i18n": "*",
"vue-i18n-bridge": "*"
},
"peerDependenciesMeta": {
"petite-vue-i18n": {
"optional": true
},
"vue-i18n": {
"optional": true
},
"vue-i18n-bridge": {
"optional": true
}
}
},
"node_modules/@intlify/unplugin-vue-i18n/node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@intlify/utils": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@intlify/utils/-/utils-0.12.0.tgz",
"integrity": "sha512-yCBNcuZQ49iInqmWC2xfW0rgEQyNtCM8C8KcWKTXxyscgUE1+48gjLgZZqP75MjhlApxwph7ZMWLqyABkSgxQA==",
"dev": true,
"engines": {
"node": ">= 18"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
}
},
"node_modules/@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
@ -1671,6 +1846,31 @@
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@miyaneee/rollup-plugin-json5": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@miyaneee/rollup-plugin-json5/-/rollup-plugin-json5-1.2.0.tgz",
"integrity": "sha512-JjTIaXZp9WzhUHpElrqPnl1AzBi/rvRs065F71+aTmlqvTMVkdbjZ8vfFl4nRlgJy+TPBw69ZK4pwFdmOAt4aA==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.1.0",
"json5": "^2.2.3"
},
"peerDependencies": {
"rollup": "^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0"
}
},
"node_modules/@miyaneee/rollup-plugin-json5/node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@netlify/functions": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-2.6.0.tgz",
@ -2532,6 +2732,39 @@
"eslint": "^8.23.0"
}
},
"node_modules/@nuxtjs/i18n": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/@nuxtjs/i18n/-/i18n-8.3.0.tgz",
"integrity": "sha512-/2g4zYwBwHwIVJitu/i5zP73G4F9xH394Uq0RbfOGc34YxscN+B2kMnuPL8XXM9zThdMVj9ctHInQXXtr62CLg==",
"dev": true,
"dependencies": {
"@intlify/h3": "^0.5.0",
"@intlify/shared": "^9.9.0",
"@intlify/unplugin-vue-i18n": "^3.0.1",
"@intlify/utils": "^0.12.0",
"@miyaneee/rollup-plugin-json5": "^1.1.2",
"@nuxt/kit": "^3.10.3",
"@rollup/plugin-yaml": "^4.1.2",
"@vue/compiler-sfc": "^3.3.4",
"debug": "^4.3.4",
"defu": "^6.1.2",
"estree-walker": "^3.0.3",
"is-https": "^4.0.0",
"knitwork": "^1.0.0",
"magic-string": "^0.30.4",
"mlly": "^1.4.2",
"pathe": "^1.1.1",
"scule": "^1.1.1",
"sucrase": "^3.34.0",
"ufo": "^1.3.1",
"unplugin": "^1.5.0",
"vue-i18n": "^9.9.0",
"vue-router": "^4.2.5"
},
"engines": {
"node": "^14.16.0 || >=16.11.0"
}
},
"node_modules/@nuxtjs/tailwindcss": {
"version": "6.11.4",
"resolved": "https://registry.npmjs.org/@nuxtjs/tailwindcss/-/tailwindcss-6.11.4.tgz",
@ -3077,6 +3310,28 @@
"node": ">=10"
}
},
"node_modules/@rollup/plugin-yaml": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@rollup/plugin-yaml/-/plugin-yaml-4.1.2.tgz",
"integrity": "sha512-RpupciIeZMUqhgFE97ba0s98mOFS7CWzN3EJNhJkqSv9XLlWYtwVdtE6cDw6ASOF/sZVFS7kRJXftaqM2Vakdw==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"js-yaml": "^4.1.0",
"tosource": "^2.0.0-alpha.3"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/pluginutils": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
@ -7337,6 +7592,27 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/escodegen": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
"dev": true,
"dependencies": {
"esprima": "^4.0.1",
"estraverse": "^5.2.0",
"esutils": "^2.0.2"
},
"bin": {
"escodegen": "bin/escodegen.js",
"esgenerate": "bin/esgenerate.js"
},
"engines": {
"node": ">=6.0"
},
"optionalDependencies": {
"source-map": "~0.6.1"
}
},
"node_modules/eslint": {
"version": "8.57.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
@ -7828,6 +8104,19 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true,
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/esquery": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
@ -9190,6 +9479,12 @@
"node": ">=0.10.0"
}
},
"node_modules/is-https": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-https/-/is-https-4.0.0.tgz",
"integrity": "sha512-FeMLiqf8E5g6SdiVJsPcNZX8k4h2fBs1wp5Bb6uaNxn58ufK1axBqQZdmAQsqh0t9BuwFObybrdVJh6MKyPlyg==",
"dev": true
},
"node_modules/is-inside-container": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
@ -9542,6 +9837,24 @@
"json5": "lib/cli.js"
}
},
"node_modules/jsonc-eslint-parser": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.0.tgz",
"integrity": "sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==",
"dev": true,
"dependencies": {
"acorn": "^8.5.0",
"eslint-visitor-keys": "^3.0.0",
"espree": "^9.0.0",
"semver": "^7.3.5"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ota-meshi"
}
},
"node_modules/jsonc-parser": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
@ -14669,6 +14982,15 @@
"node": ">=0.6"
}
},
"node_modules/tosource": {
"version": "2.0.0-alpha.3",
"resolved": "https://registry.npmjs.org/tosource/-/tosource-2.0.0-alpha.3.tgz",
"integrity": "sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/totalist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
@ -15566,6 +15888,26 @@
"eslint": ">=6.0.0"
}
},
"node_modules/vue-i18n": {
"version": "9.11.1",
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.11.1.tgz",
"integrity": "sha512-S7Xi8DkLQG4xnnbxkxzipJK6CdfLdZkmApn95st89HFGp8LTmTH0Tv+Zw6puhOCZJCFrH73PHo3Ylwd2+Bmdxg==",
"dev": true,
"dependencies": {
"@intlify/core-base": "9.11.1",
"@intlify/shared": "9.11.1",
"@vue/devtools-api": "^6.5.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/kazupon"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/vue-observe-visibility": {
"version": "2.0.0-alpha.1",
"resolved": "https://registry.npmjs.org/vue-observe-visibility/-/vue-observe-visibility-2.0.0-alpha.1.tgz",
@ -15794,6 +16136,23 @@
"node": ">= 14"
}
},
"node_modules/yaml-eslint-parser": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.2.2.tgz",
"integrity": "sha512-pEwzfsKbTrB8G3xc/sN7aw1v6A6c/pKxLAkjclnAyo5g5qOh6eL9WGu0o3cSDQZKrTNk4KL4lQSwZW+nBkANEg==",
"dev": true,
"dependencies": {
"eslint-visitor-keys": "^3.0.0",
"lodash": "^4.17.21",
"yaml": "^2.0.0"
},
"engines": {
"node": "^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ota-meshi"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",

View File

@ -35,6 +35,7 @@
},
"devDependencies": {
"@nuxtjs/eslint-config": "^12.0.0",
"@nuxtjs/i18n": "^8.3.0",
"@nuxtjs/tailwindcss": "^6.11.4",
"@types/lodash": "^4.17.0",
"@types/video.js": "^7.3.57",

View File

@ -1,20 +1,42 @@
<template>
<div class="w-full min-h-screen xs:h-full flex justify-center items-center">
<div v-if="authStore.isLogin" class="text-center w-full max-w-[700px] p-5">
<div v-if="authStore.isLogin" class="text-center w-full max-w-[800px] p-5">
<SystemStats v-if="configStore.configGui.length > 0" />
<div class="flex flex-wrap justify-center gap-1 xs:gap-0 xs:join mt-5">
<NuxtLink to="/player" class="btn join-item btn-primary">Player</NuxtLink>
<NuxtLink to="/media" class="btn join-item btn-primary">Media</NuxtLink>
<NuxtLink to="/message" class="btn join-item btn-primary">Message</NuxtLink>
<NuxtLink to="/logging" class="btn join-item btn-primary">Logging</NuxtLink>
<div class="flex flex-wrap justify-center gap-1 md:gap-0 md:join mt-5">
<NuxtLink :to="localePath({ name: 'player' })" class="btn join-item btn-primary px-2">
{{ $t('button.player') }}
</NuxtLink>
<NuxtLink :to="localePath({ name: 'media' })" class="btn join-item btn-primary px-2">
{{ $t('button.media') }}
</NuxtLink>
<NuxtLink :to="localePath({ name: 'message' })" class="btn join-item btn-primary px-2">
{{ $t('button.message') }}
</NuxtLink>
<NuxtLink :to="localePath({ name: 'logging' })" class="btn join-item btn-primary px-2">
{{ $t('button.logging') }}
</NuxtLink>
<NuxtLink
v-if="authStore.role.toLowerCase() == 'admin'"
to="/configure"
class="btn join-item btn-primary"
:to="localePath({ name: 'configure' })"
class="btn join-item btn-primary px-2"
>
Configure
{{ $t('button.configure') }}
</NuxtLink>
<button class="btn join-item btn-primary" @click="logout()">Logout</button>
<button class="btn join-item btn-primary px-2" @click="logout()">
{{ $t('button.logout') }}
</button>
<select
class="select select-primary select-bordered join-item max-w-xs ps-2"
v-model="selectedLang"
@change="changeLang(selectedLang)"
>
<option v-for="(loc, index) in locales" :key="index" :value="/* @ts-ignore */ loc.code">
{{
/* @ts-ignore */
loc.name
}}
</option>
</select>
<label class="join-item btn btn-primary swap swap-rotate me-2">
<input type="checkbox" @change="toggleDarkTheme" :checked="indexStore.darkMode" />
<SvgIcon name="swap-on" classes="w-5 h-5" />
@ -29,7 +51,7 @@
<input
type="text"
v-model="formUsername"
placeholder="Username"
:placeholder="$t('input.username')"
class="input input-bordered w-full"
required
/>
@ -37,14 +59,16 @@
<input
type="password"
v-model="formPassword"
placeholder="Password"
:placeholder="$t('input.password')"
class="input input-bordered w-full mt-5"
required
/>
<div class="w-full mt-4 grid grid-flow-row-dense grid-cols-12 grid-rows-1 gap-2">
<div class="col-span-3">
<button type="submit" class="btn btn-primary">Login</button>
<button type="submit" class="btn btn-primary">
{{ $t('button.login') }}
</button>
</div>
<div class="col-span-12 sm:col-span-9">
<div
@ -64,10 +88,16 @@
<script setup lang="ts">
const colorMode = useColorMode()
const { locale, locales, t } = useI18n()
const localePath = useLocalePath()
const switchLocalePath = useSwitchLocalePath()
const router = useRouter()
const authStore = useAuth()
const configStore = useConfig()
const indexStore = useIndex()
const selectedLang = ref(locale)
const formError = ref('')
const showLoginError = ref(false)
const formUsername = ref('')
@ -84,7 +114,7 @@ async function login() {
formError.value = ''
if (status === 401 || status === 400 || status === 403) {
formError.value = 'Wrong User/Password!'
formError.value = t('alert.wrongLogin')
showLoginError.value = true
setTimeout(() => {
@ -115,4 +145,12 @@ async function logout() {
formError.value = e as string
}
}
async function changeLang(code: string) {
const path = switchLocalePath(code)
const cookie = useCookie('i18n_redirected')
cookie.value = code
router.push(path)
}
</script>