0
Fork 0
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:
dragongoose 2023-06-13 12:08:43 -04:00
parent 14585e0144
commit fcac40082b
No known key found for this signature in database
GPG key ID: 50DB99B921579009
25 changed files with 2781 additions and 194 deletions

10
.env
View file

@ -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
View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -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
View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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"
} }
} }

View file

@ -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
}) })
} }

View file

@ -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>

View file

@ -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>

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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,

View file

@ -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
View 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,
}
})

View file

@ -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://')

View file

@ -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">

View file

@ -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"

View file

@ -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>

View file

@ -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

View file

@ -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>

View file

@ -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_',
}) })