mirror of
https://codeberg.org/SafeTwitch/safetwitch.git
synced 2024-12-22 13:22:58 -05:00
Add translation support
This commit is contained in:
parent
14585e0144
commit
fcac40082b
25 changed files with 2781 additions and 194 deletions
10
.env
10
.env
|
@ -1,3 +1,7 @@
|
||||||
VITE_BACKEND_DOMAIN=localhost:7000
|
SAFETWITCH_BACKEND_DOMAIN=localhost:7000
|
||||||
VITE_INSTANCE_DOMAIN=localhost:5173
|
SAFETWITCH_INSTANCE_DOMAIN=localhost:5173
|
||||||
VITE_HTTPS=false
|
SAFETWITCH_HTTPS=false
|
||||||
|
SAFETWITCH_DEFAULT_LOCALE=en
|
||||||
|
SAFETWITCH_FALLBACK_LOCALE=ja
|
||||||
|
VUE_APP_I18N_LOCALE=en
|
||||||
|
VUE_APP_I18N_FALLBACK_LOCALE=ja
|
||||||
|
|
6
.gitmodules
vendored
6
.gitmodules
vendored
|
@ -1,3 +1,3 @@
|
||||||
[submodule "safetwitch-translations"]
|
[submodule "src/locales"]
|
||||||
path = src/locales
|
path = src/locales
|
||||||
url = https://codeberg.org/dragongoose/safetwitch-translations
|
url = https://codeberg.org/dragongoose/safetwitch-translations
|
||||||
|
|
|
@ -81,9 +81,9 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "8080:80"
|
||||||
environment:
|
environment:
|
||||||
- VITE_BACKEND_DOMAIN=localhost:7000
|
- SAFETWITCH_BACKEND_DOMAIN=localhost:7000
|
||||||
- VITE_INSTANCE_DOMAIN=localhost:80
|
- SAFETWITCH_INSTANCE_DOMAIN=localhost:80
|
||||||
- VITE_HTTPS=false
|
- SAFETWITCH_HTTPS=false
|
||||||
backend:
|
backend:
|
||||||
image: codeberg.org/dragongoose/safetwitch-backend
|
image: codeberg.org/dragongoose/safetwitch-backend
|
||||||
ports:
|
ports:
|
||||||
|
|
|
@ -5,9 +5,11 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "8080:80"
|
||||||
environment:
|
environment:
|
||||||
- VITE_BACKEND_DOMAIN=localhost:7000
|
- SAFETWITCH_BACKEND_DOMAIN=localhost:7000
|
||||||
- VITE_INSTANCE_DOMAIN=localhost:80
|
- SAFETWITCH_INSTANCE_DOMAIN=localhost:80
|
||||||
- VITE_HTTPS=false
|
- SAFETWITCH_HTTPS=false
|
||||||
|
- SAFETWITCH_DEFAULT_LOCALE=en
|
||||||
|
- SAFETWITCH_FALLBACK_LOCALE=ja
|
||||||
backend:
|
backend:
|
||||||
image: codeberg.org/dragongoose/safetwitch-backend
|
image: codeberg.org/dragongoose/safetwitch-backend
|
||||||
ports:
|
ports:
|
||||||
|
|
|
@ -7,7 +7,9 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "8080:80"
|
- "8080:80"
|
||||||
environment:
|
environment:
|
||||||
- VITE_BACKEND_DOMAIN=localhost:7000
|
- SAFETWITCH_BACKEND_DOMAIN=localhost:7000
|
||||||
- VITE_INSTANCE_DOMAIN=localhost:80
|
- SAFETWITCH_INSTANCE_DOMAIN=localhost:80
|
||||||
- VITE_HTTPS=false
|
- SAFETWITCH_HTTPS=false
|
||||||
|
- SAFETWITCH_DEFAULT_LOCALE=en
|
||||||
|
- SAFETWITCH_FALLBACK_LOCALE=ja
|
||||||
|
|
||||||
|
|
8
env.d.ts
vendored
8
env.d.ts
vendored
|
@ -1,9 +1,11 @@
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_BACKEND_DOMAIN: string
|
readonly SAFETWITCH_BACKEND_DOMAIN: string
|
||||||
readonly VITE_INSTANCE_DOMAIN: string
|
readonly SAFETWITCH_INSTANCE_DOMAIN: string
|
||||||
readonly VITE_HTTPS: string
|
readonly SAFETWITCH_HTTPS: string
|
||||||
|
readonly SAFETWITCH_DEFAULT_LOCALE: string
|
||||||
|
readonly SAFETWITCH_FALLBACK_LOCALE: string
|
||||||
// more env variables...
|
// more env variables...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2774
package-lock.json
generated
2774
package-lock.json
generated
File diff suppressed because it is too large
Load diff
16
package.json
16
package.json
|
@ -3,26 +3,31 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
|
||||||
"build": "run-p type-check build-only",
|
"build": "run-p type-check build-only",
|
||||||
"preview": "vite preview",
|
|
||||||
"build-only": "vite build",
|
|
||||||
"type-check": "vue-tsc --noEmit",
|
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||||
"format": "prettier --write src/"
|
"build-only": "vite build",
|
||||||
|
"dev": "vite",
|
||||||
|
"format": "prettier --write src/",
|
||||||
|
"i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\"",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"type-check": "vue-tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dragongoose/streamlink": "^1.0.2",
|
"@dragongoose/streamlink": "^1.0.2",
|
||||||
|
"@intlify/unplugin-vue-i18n": "^0.11.0",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
|
"@vue/cli-shared-utils": "^5.0.8",
|
||||||
"oh-vue-icons": "^1.0.0-rc3",
|
"oh-vue-icons": "^1.0.0-rc3",
|
||||||
"video.js": "^8.0.4",
|
"video.js": "^8.0.4",
|
||||||
"videojs-contrib-quality-levels": "^3.0.0",
|
"videojs-contrib-quality-levels": "^3.0.0",
|
||||||
"videojs-hls-quality-selector": "^1.1.4",
|
"videojs-hls-quality-selector": "^1.1.4",
|
||||||
"vue": "^3.2.47",
|
"vue": "^3.2.47",
|
||||||
|
"vue-i18n": "^9.2.2",
|
||||||
"vue-router": "^4.1.6"
|
"vue-router": "^4.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@catppuccin/tailwindcss": "^0.1.1",
|
"@catppuccin/tailwindcss": "^0.1.1",
|
||||||
|
"@intlify/vue-i18n-loader": "^3.0.0",
|
||||||
"@rushstack/eslint-patch": "^1.2.0",
|
"@rushstack/eslint-patch": "^1.2.0",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@types/node": "^18.14.2",
|
"@types/node": "^18.14.2",
|
||||||
|
@ -42,6 +47,7 @@
|
||||||
"tailwindcss": "^3.2.7",
|
"tailwindcss": "^3.2.7",
|
||||||
"typescript": "~4.8.4",
|
"typescript": "~4.8.4",
|
||||||
"vite": "^4.1.4",
|
"vite": "^4.1.4",
|
||||||
|
"vue-cli-plugin-i18n": "~2.3.2",
|
||||||
"vue-tsc": "^1.2.0"
|
"vue-tsc": "^1.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import videojs from 'video.js'
|
import videojs from 'video.js'
|
||||||
import 'videojs-contrib-quality-levels'
|
import 'videojs-contrib-quality-levels'
|
||||||
import type { QualityLevelList, QualityLevel } from 'videojs-contrib-quality-levels'
|
import type { QualityLevelList, QualityLevel } from 'videojs-contrib-quality-levels'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
export const createQualitySelector = (player: any) => {
|
export const createQualitySelector = (player: any) => {
|
||||||
const qualityLevels: QualityLevelList = player.qualityLevels()
|
const qualityLevels: QualityLevelList = player.qualityLevels()
|
||||||
|
@ -10,6 +11,8 @@ export const createQualitySelector = (player: any) => {
|
||||||
const MenuItem = videojs.getComponent('MenuItem')
|
const MenuItem = videojs.getComponent('MenuItem')
|
||||||
let formatedQualities: { name: string; index: number; id: string }[]
|
let formatedQualities: { name: string; index: number; id: string }[]
|
||||||
|
|
||||||
|
let t = useI18n()
|
||||||
|
|
||||||
const setQuality = (id: string) => {
|
const setQuality = (id: string) => {
|
||||||
const found = formatedQualities.find((i) => i.id === id)
|
const found = formatedQualities.find((i) => i.id === id)
|
||||||
for (const quality of qualityLevels.levels_) {
|
for (const quality of qualityLevels.levels_) {
|
||||||
|
@ -56,7 +59,7 @@ export const createQualitySelector = (player: any) => {
|
||||||
const updateLevels = (items: { name: string; index: number; id: string; }[]) => {
|
const updateLevels = (items: { name: string; index: number; id: string; }[]) => {
|
||||||
player.controlBar.removeChild('CustomMenuButton')
|
player.controlBar.removeChild('CustomMenuButton')
|
||||||
player.controlBar.addChild('CustomMenuButton', {
|
player.controlBar.addChild('CustomMenuButton', {
|
||||||
title: 'Qualities',
|
title: t("player.quality"),
|
||||||
items: formatedQualities
|
items: formatedQualities
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,12 @@ export default {}
|
||||||
class="flex flex-col max-w-prose justify-center text-center mx-auto p-6 bg-ctp-crust rounded-lg text-white"
|
class="flex flex-col max-w-prose justify-center text-center mx-auto p-6 bg-ctp-crust rounded-lg text-white"
|
||||||
>
|
>
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="font-bold text-5xl">oops...</h1>
|
<h1 class="font-bold text-5xl">{{ $t("error.oops") }}</h1>
|
||||||
<p class="font-bold text-3xl">this wasn't supposed to happen</p>
|
<p class="font-bold text-3xl">{{ $t("error.notsupposedtohappen") }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-xl">
|
<p class="text-xl">
|
||||||
the server was encountered an error while retriving the data, and now we're here :3
|
{{ $t("error.serverexplain") }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -56,7 +56,7 @@ export default {
|
||||||
class="text-white text-sm font-bold p-2 py-1 rounded-md bg-purple-600"
|
class="text-white text-sm font-bold p-2 py-1 rounded-md bg-purple-600"
|
||||||
>
|
>
|
||||||
<v-icon name="bi-heart-fill" scale="0.85"></v-icon>
|
<v-icon name="bi-heart-fill" scale="0.85"></v-icon>
|
||||||
<span v-if="isFollowing"> Unfollow </span>
|
<span v-if="isFollowing"> {{ $t("streamer.unfollow") }} </span>
|
||||||
<span v-else> Follow </span>
|
<span v-else> {{ $t("streamer.follow") }} </span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
20
src/components/LanguageSwitcher.vue
Normal file
20
src/components/LanguageSwitcher.vue
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex">
|
||||||
|
<select v-model="$i18n.locale" class="my-auto p-0 pr-9 bg-transparent border-0" :selected="$i18n.locale">
|
||||||
|
<option v-for="(lang, i) in langs" :key="`Lang${i}`" :value="lang">
|
||||||
|
{{ names[i] }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
langs: ['en'],
|
||||||
|
names: ['English']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="flex mx-auto justify-center bg-ctp-crust rounded-lg w-2/3 p-2 text-white">
|
<div class="flex mx-auto justify-center bg-ctp-crust rounded-lg w-2/3 p-2 text-white">
|
||||||
<div class="flex space-x-3">
|
<div class="flex space-x-3">
|
||||||
<h1 class="text-4xl font-bold">Searching...</h1>
|
<h1 class="text-4xl font-bold">{{ $t("main.searching") }}</h1>
|
||||||
<v-icon name="fa-circle-notch" class="animate-spin w-10 h-10"></v-icon>
|
<v-icon name="fa-circle-notch" class="animate-spin w-10 h-10"></v-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SearchBar from './SearchBar.vue'
|
import SearchBar from './SearchBar.vue'
|
||||||
|
import LanguageSwitcher from './LanguageSwitcher.vue'
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
SearchBar
|
SearchBar,
|
||||||
|
LanguageSwitcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -18,8 +20,9 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="inline-flex space-x-6 font-medium">
|
<ul class="inline-flex space-x-6 font-medium">
|
||||||
<a href="https://codeberg.org/dragongoose/safetwitch">Code</a>
|
<a href="https://codeberg.org/dragongoose/safetwitch">{{ $t("nav.code") }}</a>
|
||||||
<router-link to="/privacy">Privacy</router-link>
|
<router-link to="/privacy">{{ $t("nav.privacy") }}</router-link>
|
||||||
|
<language-switcher></language-switcher>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -16,9 +16,9 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative hidden md:block">
|
<div class="relative hidden md:block">
|
||||||
<label for="searchBar" class="hidden">Search</label>
|
<label for="searchBar" class="hidden">{{ $t("main.search") }}</label>
|
||||||
<v-icon name="io-search-outline" class="text-black absolute my-auto inset-y-0 left-2"></v-icon>
|
<v-icon name="io-search-outline" class="text-black absolute my-auto inset-y-0 left-2"></v-icon>
|
||||||
<input type="text" placeholder="Search"
|
<input type="text" :placeholder="$t('main.search')"
|
||||||
@keyup.enter=redirectToSearch v-model="searchInput"
|
@keyup.enter=redirectToSearch v-model="searchInput"
|
||||||
class="rounded-md p-1 pl-8 text-black" ref="searchInput" />
|
class="rounded-md p-1 pl-8 text-black" ref="searchInput" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default {
|
||||||
let streamData: Stream | null = null
|
let streamData: Stream | null = null
|
||||||
if (!props.stream && props.name) {
|
if (!props.stream && props.name) {
|
||||||
const streamDataFetch = await fetch(
|
const streamDataFetch = await fetch(
|
||||||
`${protocol}${import.meta.env.VITE_BACKEND_DOMAIN}/api/users/${props.name}`
|
`${protocol}${import.meta.env.SAFETWITCH_BACKEND_DOMAIN}/api/users/${props.name}`
|
||||||
)
|
)
|
||||||
const data = (await streamDataFetch.json()).data
|
const data = (await streamDataFetch.json()).data
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ export default {
|
||||||
streamData = props.stream as Stream
|
streamData = props.stream as Stream
|
||||||
}
|
}
|
||||||
|
|
||||||
const frontend_url = protocol + import.meta.env.VITE_INSTANCE_DOMAIN
|
const frontend_url = protocol + import.meta.env.SAFETWITCH_INSTANCE_DOMAIN
|
||||||
|
|
||||||
return {
|
return {
|
||||||
frontend_url,
|
frontend_url,
|
||||||
|
|
|
@ -21,11 +21,11 @@ export default {
|
||||||
let messages: Ref<ParsedMessage[]> = ref([])
|
let messages: Ref<ParsedMessage[]> = ref([])
|
||||||
const protocol = inject('protocol')
|
const protocol = inject('protocol')
|
||||||
const wsProtocol = protocol === 'https://' ? 'wss://' : 'ws://'
|
const wsProtocol = protocol === 'https://' ? 'wss://' : 'ws://'
|
||||||
const badgesFetch = await fetch(`${protocol}${import.meta.env.VITE_BACKEND_DOMAIN}/api/badges?channelName=${props.channelName}`)
|
const badgesFetch = await fetch(`${protocol}${import.meta.env.SAFETWITCH_BACKEND_DOMAIN}/api/badges?channelName=${props.channelName}`)
|
||||||
let badges: Badge[] = (await badgesFetch.json()).data
|
let badges: Badge[] = (await badgesFetch.json()).data
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ws: new WebSocket(`${wsProtocol}${import.meta.env.VITE_BACKEND_DOMAIN}`),
|
ws: new WebSocket(`${wsProtocol}${import.meta.env.SAFETWITCH_BACKEND_DOMAIN}`),
|
||||||
messages,
|
messages,
|
||||||
badges,
|
badges,
|
||||||
props,
|
props,
|
||||||
|
@ -37,7 +37,7 @@ export default {
|
||||||
|
|
||||||
this.ws.onmessage = (message) => {
|
this.ws.onmessage = (message) => {
|
||||||
if (message.data == 'OK') {
|
if (message.data == 'OK') {
|
||||||
chatStatusMessage.textContent = `Connected to ${this.channelName}`
|
chatStatusMessage.textContent = this.$t("chat.connected", {username: this.channelName})
|
||||||
} else {
|
} else {
|
||||||
this.messages.push(parseMessage(message.data, this.badges))
|
this.messages.push(parseMessage(message.data, this.badges))
|
||||||
this.clearMessages()
|
this.clearMessages()
|
||||||
|
@ -92,7 +92,7 @@ export default {
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
<p ref="initConnectingStatus" class="text-gray-500 text-sm italic">
|
<p ref="initConnectingStatus" class="text-gray-500 text-sm italic">
|
||||||
Connecting to {{ channelName }}.
|
{{ $t("chat.connecting", { username: channelName }) }}
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<li v-for="message in getChat()" :key="messages.indexOf(message)">
|
<li v-for="message in getChat()" :key="messages.indexOf(message)">
|
||||||
|
@ -116,11 +116,11 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="message.type === 'CLEARMSG'" class="text-white inline-flex">
|
<div v-else-if="message.type === 'CLEARMSG'" class="text-white inline-flex">
|
||||||
<p class="text-sm text-gray-500 italic"> Message by {{ message.data.username }} removed </p>
|
<p class="text-sm text-gray-500 italic"> {{ $t("chat.removed", { username: message.data.username }) }} </p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="message.type === 'USERNOTICE'" class="text-white inline-flex bg-ctp-pink bg-opacity-50 p-1 rounded-md">
|
<div v-else-if="message.type === 'USERNOTICE'" class="text-white inline-flex bg-ctp-pink bg-opacity-50 p-1 rounded-md">
|
||||||
<p> <strong>{{ message.data.username }}</strong> has just resubbed for {{ message.data.months }} months!</p>
|
<p> {{ $t("chat.resub", { username: message.data.username, duration : message.data.months }) }} </p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="text-white">
|
<div v-else class="text-white">
|
||||||
|
|
12
src/i18n.ts
Normal file
12
src/i18n.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { createI18n } from 'vue-i18n'
|
||||||
|
import en from '@/locales/en.json'
|
||||||
|
|
||||||
|
export default createI18n({
|
||||||
|
legacy: false,
|
||||||
|
locale: import.meta.env.VUE_APP_I18N_LOCALE || 'en',
|
||||||
|
fallbackLocale: import.meta.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
|
||||||
|
globalInjection: true,
|
||||||
|
messages: {
|
||||||
|
'en': en,
|
||||||
|
}
|
||||||
|
})
|
|
@ -2,14 +2,15 @@ import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import './assets/index.css'
|
import './assets/index.css'
|
||||||
|
import i18n from "./i18n"
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App).use(i18n)
|
||||||
|
|
||||||
// Add protocol variable
|
// Add protocol variable
|
||||||
// For some reason, import.meta.env.VITE_HTTPS === "true"
|
// For some reason, import.meta.env.VITE_HTTPS === "true"
|
||||||
// returns false, even if it is true.
|
// returns false, even if it is true.
|
||||||
// Making a copy of the variable seems to work
|
// Making a copy of the variable seems to work
|
||||||
const https = (import.meta.env.VITE_HTTPS.slice() === "true")
|
const https = (import.meta.env.SAFETWITCH_HTTPS.slice() === "true")
|
||||||
app.provide('protocol', https ? 'https://' : 'http://')
|
app.provide('protocol', https ? 'https://' : 'http://')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ export default {
|
||||||
async mounted() {
|
async mounted() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${this.protocol}${import.meta.env.VITE_BACKEND_DOMAIN}/api/discover/${this.game}`
|
`${this.protocol}${import.meta.env.SAFETWITCH_BACKEND_DOMAIN}/api/discover/${this.game}`
|
||||||
)
|
)
|
||||||
const rawData = await res.json()
|
const rawData = await res.json()
|
||||||
if (rawData.status === "ok") {
|
if (rawData.status === "ok") {
|
||||||
|
@ -46,7 +46,7 @@ export default {
|
||||||
const cursor = streams[streams.length - 1].cursor
|
const cursor = streams[streams.length - 1].cursor
|
||||||
if (!cursor) return
|
if (!cursor) return
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${this.protocol}${import.meta.env.VITE_BACKEND_DOMAIN}/api/discover/${this.game}/?cursor=${cursor}`
|
`${this.protocol}${import.meta.env.SAFETWITCH_BACKEND_DOMAIN}/api/discover/${this.game}/?cursor=${cursor}`
|
||||||
)
|
)
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error('Failed to fetch data')
|
throw new Error('Failed to fetch data')
|
||||||
|
@ -89,8 +89,8 @@ export default {
|
||||||
<div class="hidden md:block">
|
<div class="hidden md:block">
|
||||||
<div>
|
<div>
|
||||||
<div class="inline-flex my-1 space-x-3">
|
<div class="inline-flex my-1 space-x-3">
|
||||||
<p class="font-bold text-white text-lg">Followers: {{ abbreviate(data.followers) }}</p>
|
<p class="font-bold text-white text-lg">{{ $t("main.followers") }}: {{ abbreviate(data.followers) }}</p>
|
||||||
<p class="font-bold text-white text-lg">Viewers: {{ abbreviate(data.viewers) }}</p>
|
<p class="font-bold text-white text-lg">{{ $t("main.viewers") }}: {{ abbreviate(data.viewers) }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="mb-5">
|
<ul class="mb-5">
|
||||||
|
@ -109,8 +109,8 @@ export default {
|
||||||
<div class="md:hidden">
|
<div class="md:hidden">
|
||||||
<div>
|
<div>
|
||||||
<div class="inline-flex my-1 space-x-3">
|
<div class="inline-flex my-1 space-x-3">
|
||||||
<p class="font-bold text-white text-lg">Followers: {{ abbreviate(data.followers) }}</p>
|
<p class="font-bold text-white text-lg">{{ $t("main.followers") }}: {{ abbreviate(data.followers) }}</p>
|
||||||
<p class="font-bold text-white text-lg">Viewers: {{ abbreviate(data.viewers) }}</p>
|
<p class="font-bold text-white text-lg">{{ $t("main.viewers") }}: {{ abbreviate(data.viewers) }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="mb-5">
|
<ul class="mb-5">
|
||||||
|
|
|
@ -51,7 +51,7 @@ export default {
|
||||||
const cursor = this.data[this.data.length - 1].cursor
|
const cursor = this.data[this.data.length - 1].cursor
|
||||||
if (!cursor) return
|
if (!cursor) return
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${this.protocol}${import.meta.env.VITE_BACKEND_DOMAIN}/api/discover/?cursor=${cursor}`
|
`${this.protocol}${import.meta.env.SAFETWITCH_BACKEND_DOMAIN}/api/discover/?cursor=${cursor}`
|
||||||
)
|
)
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error('Failed to fetch data')
|
throw new Error('Failed to fetch data')
|
||||||
|
@ -76,7 +76,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${this.protocol}${import.meta.env.VITE_BACKEND_DOMAIN}/api/discover`)
|
const res = await fetch(`${this.protocol}${import.meta.env.SAFETWITCH_BACKEND_DOMAIN}/api/discover`)
|
||||||
|
|
||||||
const rawData = await res.json()
|
const rawData = await res.json()
|
||||||
if (rawData.status === 'ok') {
|
if (rawData.status === 'ok') {
|
||||||
|
@ -118,19 +118,19 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<h1 class="font-bold text-5xl text-white">Discover</h1>
|
<h1 class="font-bold text-5xl text-white">{{ $t("home.discover") }}</h1>
|
||||||
<p class="text-xl text-white">Sort through popular categories</p>
|
<p class="text-xl text-white">{{ $t("home.discoverDescription") }}</p>
|
||||||
|
|
||||||
<div class="pt-5 inline-flex text-white">
|
<div class="pt-5 inline-flex text-white">
|
||||||
<p class="mr-2 font-bold text-white">Filter by tag</p>
|
<p class="mr-2 font-bold text-white">{{ $t("home.tagDescription") }}</p>
|
||||||
<form class="relative">
|
<form class="relative">
|
||||||
<label for="searchBar" class="hidden">Search</label>
|
<label for="searchBar" class="hidden">{{ $t("main.search") }}</label>
|
||||||
<v-icon name="io-search-outline" class="absolute my-auto inset-y-0 left-2"></v-icon>
|
<v-icon name="io-search-outline" class="absolute my-auto inset-y-0 left-2"></v-icon>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="searchBar"
|
id="searchBar"
|
||||||
name="searchBar"
|
name="searchBar"
|
||||||
placeholder="Search"
|
:placeholder="$t('main.search')"
|
||||||
v-model="filterTags"
|
v-model="filterTags"
|
||||||
@keyup="filterSearches(filterTags)"
|
@keyup="filterSearches(filterTags)"
|
||||||
class="rounded-md p-1 pl-8 text-black bg-neutral-500 placeholder:text-white"
|
class="rounded-md p-1 pl-8 text-black bg-neutral-500 placeholder:text-white"
|
||||||
|
|
|
@ -4,8 +4,8 @@ export default {}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col items-center pt-10 font-bold text-5xl text-white">
|
<div class="flex flex-col items-center pt-10 font-bold text-5xl text-white">
|
||||||
<h1>oops....</h1>
|
<h1>{{ $t("error.oops") }}</h1>
|
||||||
<h1>this page wasn't found(◞‸◟;)</h1>
|
<h1>{{ $t("error.notfound") }}</h1>
|
||||||
<h2 class="text-4xl">maybe go <RouterLink to="/" class="text-gray-500">home</RouterLink>?</h2>
|
<h2 class="text-4xl">maybe go <RouterLink to="/" class="text-gray-500">home</RouterLink>?</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -16,7 +16,7 @@ export default {
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${this.protocol}${import.meta.env.VITE_BACKEND_DOMAIN}/api/search/?query=${this.$route.query.query}`)
|
const res = await fetch(`${this.protocol}${import.meta.env.SAFETWITCH_BACKEND_DOMAIN}/api/search/?query=${this.$route.query.query}`)
|
||||||
const rawData = await res.json()
|
const rawData = await res.json()
|
||||||
|
|
||||||
this.data = rawData.data
|
this.data = rawData.data
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default {
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
src: `${protocol}${
|
src: `${protocol}${
|
||||||
import.meta.env.VITE_BACKEND_DOMAIN
|
import.meta.env.SAFETWITCH_BACKEND_DOMAIN
|
||||||
}/proxy/stream/${username}/hls.m3u8`,
|
}/proxy/stream/${username}/hls.m3u8`,
|
||||||
type: 'application/vnd.apple.mpegurl'
|
type: 'application/vnd.apple.mpegurl'
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ export default {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${this.protocol}${import.meta.env.VITE_BACKEND_DOMAIN}/api/users/${username}`
|
`${this.protocol}${import.meta.env.SAFETWITCH_BACKEND_DOMAIN}/api/users/${username}`
|
||||||
)
|
)
|
||||||
const rawData = await res.json()
|
const rawData = await res.json()
|
||||||
if (rawData.status === 'ok') {
|
if (rawData.status === 'ok') {
|
||||||
|
@ -104,14 +104,14 @@ export default {
|
||||||
<span
|
<span
|
||||||
v-if="data.isLive"
|
v-if="data.isLive"
|
||||||
class="absolute top-16 right-[1.2rem] bg-ctp-red font-bold text-sm p-1.5 py-0.5 rounded-md"
|
class="absolute top-16 right-[1.2rem] bg-ctp-red font-bold text-sm p-1.5 py-0.5 rounded-md"
|
||||||
>LIVE</span
|
>{{ $t("main.live") }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ml-3 content-between">
|
<div class="ml-3 content-between">
|
||||||
<h1 class="text-2xl md:text-4xl font-bold">{{ data.username }}</h1>
|
<h1 class="text-2xl md:text-4xl font-bold">{{ data.username }}</h1>
|
||||||
<h1 v-if="!data.stream" class="font-bold text-md self-end">
|
<h1 v-if="!data.stream" class="font-bold text-md self-end">
|
||||||
{{ abbreviate(data.followers) }} Followers
|
{{ abbreviate(data.followers) }} {{ $t("main.live") }}
|
||||||
</h1>
|
</h1>
|
||||||
<div v-else class="w-[14rem] md:w-[17rem]">
|
<div v-else class="w-[14rem] md:w-[17rem]">
|
||||||
<p class="text-sm font-bold text-gray-200 self-end">
|
<p class="text-sm font-bold text-gray-200 self-end">
|
||||||
|
@ -144,13 +144,13 @@ export default {
|
||||||
|
|
||||||
<div class="pt-2 inline-flex">
|
<div class="pt-2 inline-flex">
|
||||||
<follow-button :username="data.username"></follow-button>
|
<follow-button :username="data.username"></follow-button>
|
||||||
<p class="align-baseline font-bold ml-3">{{ abbreviate(data.followers) }} Followers</p>
|
<p class="align-baseline font-bold ml-3">{{ abbreviate(data.followers) }} {{ $t("main.followers") }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-ctp-mantle mt-1 p-5 pt-3 rounded-lg w-full space-y-3">
|
<div class="bg-ctp-mantle mt-1 p-5 pt-3 rounded-lg w-full space-y-3">
|
||||||
<div class="inline-flex w-full">
|
<div class="inline-flex w-full">
|
||||||
<span class="pr-3 font-bold text-3xl">About</span>
|
<span class="pr-3 font-bold text-3xl">{{ $t("streamer.about") }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="mb-5">{{ data.about }}</p>
|
<p class="mb-5">{{ data.about }}</p>
|
||||||
|
|
|
@ -2,10 +2,17 @@ import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
|
||||||
|
import { dirname, resolve } from 'node:path'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
VueI18nPlugin({
|
||||||
|
include: resolve(dirname(fileURLToPath(import.meta.url)), './src/locales/**'),
|
||||||
|
})
|
||||||
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
@ -21,5 +28,6 @@ export default defineConfig({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
envPrefix: 'SAFETWITCH_',
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue