Add translation support

This commit is contained in:
dragongoose
2023-06-13 12:08:43 -04:00
parent 14585e0144
commit fcac40082b
25 changed files with 2781 additions and 194 deletions

View File

@ -3,6 +3,7 @@
import videojs from 'video.js'
import 'videojs-contrib-quality-levels'
import type { QualityLevelList, QualityLevel } from 'videojs-contrib-quality-levels'
import { useI18n } from 'vue-i18n'
export const createQualitySelector = (player: any) => {
const qualityLevels: QualityLevelList = player.qualityLevels()
@ -10,6 +11,8 @@ export const createQualitySelector = (player: any) => {
const MenuItem = videojs.getComponent('MenuItem')
let formatedQualities: { name: string; index: number; id: string }[]
let t = useI18n()
const setQuality = (id: string) => {
const found = formatedQualities.find((i) => i.id === id)
for (const quality of qualityLevels.levels_) {
@ -56,7 +59,7 @@ export const createQualitySelector = (player: any) => {
const updateLevels = (items: { name: string; index: number; id: string; }[]) => {
player.controlBar.removeChild('CustomMenuButton')
player.controlBar.addChild('CustomMenuButton', {
title: 'Qualities',
title: t("player.quality"),
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"
>
<div class="mb-6">
<h1 class="font-bold text-5xl">oops...</h1>
<p class="font-bold text-3xl">this wasn't supposed to happen</p>
<h1 class="font-bold text-5xl">{{ $t("error.oops") }}</h1>
<p class="font-bold text-3xl">{{ $t("error.notsupposedtohappen") }}</p>
</div>
<p class="text-xl">
the server was encountered an error while retriving the data, and now we're here :3
{{ $t("error.serverexplain") }}
</p>
</div>
</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"
>
<v-icon name="bi-heart-fill" scale="0.85"></v-icon>
<span v-if="isFollowing"> Unfollow </span>
<span v-else> Follow </span>
<span v-if="isFollowing"> {{ $t("streamer.unfollow") }} </span>
<span v-else> {{ $t("streamer.follow") }} </span>
</button>
</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>
<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">
<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>
</div>
</div>

View File

@ -1,8 +1,10 @@
<script lang="ts">
import SearchBar from './SearchBar.vue'
import LanguageSwitcher from './LanguageSwitcher.vue'
export default {
components: {
SearchBar
SearchBar,
LanguageSwitcher
}
}
</script>
@ -18,8 +20,9 @@ export default {
</div>
<ul class="inline-flex space-x-6 font-medium">
<a href="https://codeberg.org/dragongoose/safetwitch">Code</a>
<router-link to="/privacy">Privacy</router-link>
<a href="https://codeberg.org/dragongoose/safetwitch">{{ $t("nav.code") }}</a>
<router-link to="/privacy">{{ $t("nav.privacy") }}</router-link>
<language-switcher></language-switcher>
</ul>
</div>
</template>

View File

@ -16,9 +16,9 @@ export default {
<template>
<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>
<input type="text" placeholder="Search"
<input type="text" :placeholder="$t('main.search')"
@keyup.enter=redirectToSearch v-model="searchInput"
class="rounded-md p-1 pl-8 text-black" ref="searchInput" />
</div>

View File

@ -26,7 +26,7 @@ export default {
let streamData: Stream | null = null
if (!props.stream && props.name) {
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
@ -38,7 +38,7 @@ export default {
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 {
frontend_url,

View File

@ -21,11 +21,11 @@ export default {
let messages: Ref<ParsedMessage[]> = ref([])
const protocol = inject('protocol')
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
return {
ws: new WebSocket(`${wsProtocol}${import.meta.env.VITE_BACKEND_DOMAIN}`),
ws: new WebSocket(`${wsProtocol}${import.meta.env.SAFETWITCH_BACKEND_DOMAIN}`),
messages,
badges,
props,
@ -37,7 +37,7 @@ export default {
this.ws.onmessage = (message) => {
if (message.data == 'OK') {
chatStatusMessage.textContent = `Connected to ${this.channelName}`
chatStatusMessage.textContent = this.$t("chat.connected", {username: this.channelName})
} else {
this.messages.push(parseMessage(message.data, this.badges))
this.clearMessages()
@ -92,7 +92,7 @@ export default {
>
<li>
<p ref="initConnectingStatus" class="text-gray-500 text-sm italic">
Connecting to {{ channelName }}.
{{ $t("chat.connecting", { username: channelName }) }}
</p>
</li>
<li v-for="message in getChat()" :key="messages.indexOf(message)">
@ -116,11 +116,11 @@ export default {
</div>
<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 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 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 router from './router'
import './assets/index.css'
import i18n from "./i18n"
const app = createApp(App)
const app = createApp(App).use(i18n)
// Add protocol variable
// For some reason, import.meta.env.VITE_HTTPS === "true"
// returns false, even if it is true.
// 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://')

View File

@ -20,7 +20,7 @@ export default {
async mounted() {
try {
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()
if (rawData.status === "ok") {
@ -46,7 +46,7 @@ export default {
const cursor = streams[streams.length - 1].cursor
if (!cursor) return
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) {
throw new Error('Failed to fetch data')
@ -89,8 +89,8 @@ export default {
<div class="hidden md:block">
<div>
<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">Viewers: {{ abbreviate(data.viewers) }}</p>
<p class="font-bold text-white text-lg">{{ $t("main.followers") }}: {{ abbreviate(data.followers) }}</p>
<p class="font-bold text-white text-lg">{{ $t("main.viewers") }}: {{ abbreviate(data.viewers) }}</p>
</div>
<ul class="mb-5">
@ -109,8 +109,8 @@ export default {
<div class="md:hidden">
<div>
<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">Viewers: {{ abbreviate(data.viewers) }}</p>
<p class="font-bold text-white text-lg">{{ $t("main.followers") }}: {{ abbreviate(data.followers) }}</p>
<p class="font-bold text-white text-lg">{{ $t("main.viewers") }}: {{ abbreviate(data.viewers) }}</p>
</div>
<ul class="mb-5">

View File

@ -51,7 +51,7 @@ export default {
const cursor = this.data[this.data.length - 1].cursor
if (!cursor) return
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) {
throw new Error('Failed to fetch data')
@ -76,7 +76,7 @@ export default {
}
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()
if (rawData.status === 'ok') {
@ -118,19 +118,19 @@ export default {
</div>
<div class="p-2">
<h1 class="font-bold text-5xl text-white">Discover</h1>
<p class="text-xl text-white">Sort through popular categories</p>
<h1 class="font-bold text-5xl text-white">{{ $t("home.discover") }}</h1>
<p class="text-xl text-white">{{ $t("home.discoverDescription") }}</p>
<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">
<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>
<input
type="text"
id="searchBar"
name="searchBar"
placeholder="Search"
:placeholder="$t('main.search')"
v-model="filterTags"
@keyup="filterSearches(filterTags)"
class="rounded-md p-1 pl-8 text-black bg-neutral-500 placeholder:text-white"

View File

@ -4,8 +4,8 @@ export default {}
<template>
<div class="flex flex-col items-center pt-10 font-bold text-5xl text-white">
<h1>oops....</h1>
<h1>this page wasn't found()</h1>
<h1>{{ $t("error.oops") }}</h1>
<h1>{{ $t("error.notfound") }}</h1>
<h2 class="text-4xl">maybe go <RouterLink to="/" class="text-gray-500">home</RouterLink>?</h2>
</div>
</template>

View File

@ -16,7 +16,7 @@ export default {
},
async mounted() {
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()
this.data = rawData.data

View File

@ -19,7 +19,7 @@ export default {
sources: [
{
src: `${protocol}${
import.meta.env.VITE_BACKEND_DOMAIN
import.meta.env.SAFETWITCH_BACKEND_DOMAIN
}/proxy/stream/${username}/hls.m3u8`,
type: 'application/vnd.apple.mpegurl'
}
@ -45,7 +45,7 @@ export default {
try {
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()
if (rawData.status === 'ok') {
@ -104,14 +104,14 @@ export default {
<span
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"
>LIVE</span
>{{ $t("main.live") }}</span
>
</div>
<div class="ml-3 content-between">
<h1 class="text-2xl md:text-4xl font-bold">{{ data.username }}</h1>
<h1 v-if="!data.stream" class="font-bold text-md self-end">
{{ abbreviate(data.followers) }} Followers
{{ abbreviate(data.followers) }} {{ $t("main.live") }}
</h1>
<div v-else class="w-[14rem] md:w-[17rem]">
<p class="text-sm font-bold text-gray-200 self-end">
@ -144,13 +144,13 @@ export default {
<div class="pt-2 inline-flex">
<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 class="bg-ctp-mantle mt-1 p-5 pt-3 rounded-lg w-full space-y-3">
<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>
<p class="mb-5">{{ data.about }}</p>