mirror of
https://github.com/owncast/owncast.git
synced 2025-11-02 03:54:54 +08:00
Initial localization work (#3980)
* First pass at configuring localization * Add CI job for translations * Update CI job * Update default value * Update parser config * Update defaults again * try to fix the multiple parsing of a file * Update crowdlin config * Update configs * New Crowdin translations by GitHub Action (#3448) Co-authored-by: Crowdin Bot <support+bot@crowdin.com> * Point to updated translated files * Tooltip i18n * Run translation job when web components are updated * Commit updated translations * Translations update (#3453) * Update source file strings.json Updated translations * New translations strings.json (French) Updated translations * New translations strings.json (Spanish) Updated translations * New translations strings.json (German) Updated translations * New translations strings.json (English, United States) Updated translations * Commit updated translations * New Crowdin translations by GitHub Action (#3452) Co-authored-by: Owncast <owncast@owncast.online> * chore(deps): update to next config to address build errors * New Crowdin translations by GitHub Action (#3455) Co-authored-by: Crowdin Bot <support+bot@crowdin.com> * Translations update (#3456) * New translations strings.json (Arabic) Updated translations * New translations strings.json (German) Updated translations * New translations strings.json (Greek) Updated translations * New translations strings.json (Irish) Updated translations * New translations strings.json (Italian) Updated translations * New translations strings.json (Japanese) Updated translations * New translations strings.json (Korean) Updated translations * New translations strings.json (Dutch) Updated translations * New translations strings.json (Norwegian) Updated translations * New translations strings.json (Punjabi) Updated translations * New translations strings.json (Russian) Updated translations * New translations strings.json (Swedish) Updated translations * New translations strings.json (Chinese Traditional) Updated translations * New translations strings.json (Vietnamese) Updated translations * New translations strings.json (Bengali) Updated translations * New translations strings.json (Thai) Updated translations * New translations strings.json (Croatian) Updated translations * New translations strings.json (Hindi) Updated translations * New translations strings.json (Malay) Updated translations * New Crowdin translations by GitHub Action (#3457) * New translations strings.json (Arabic) Updated translations * New translations strings.json (German) Updated translations * New translations strings.json (Greek) Updated translations * New translations strings.json (Irish) Updated translations * New translations strings.json (Italian) Updated translations * New translations strings.json (Japanese) Updated translations * New translations strings.json (Korean) Updated translations * New translations strings.json (Dutch) Updated translations * New translations strings.json (Norwegian) Updated translations * New translations strings.json (Punjabi) Updated translations * New translations strings.json (Russian) Updated translations * New translations strings.json (Swedish) Updated translations * New translations strings.json (Chinese Traditional) Updated translations * New translations strings.json (Vietnamese) Updated translations * New translations strings.json (Bengali) Updated translations * New translations strings.json (Thai) Updated translations * New translations strings.json (Croatian) Updated translations * New translations strings.json (Hindi) Updated translations * New translations strings.json (Malay) Updated translations * New Crowdin translations by GitHub Action --------- Co-authored-by: Gabe Kangas <gabek@real-ity.com> Co-authored-by: Crowdin Bot <support+bot@crowdin.com> * Commit updated API documentation * Update translations job * New Crowdin translations by GitHub Action (#3698) Co-authored-by: Crowdin Bot <support+bot@crowdin.com> * Update Crowdin configuration file * Translations update (#3700) * New translations strings.json (French) Updated translations * New translations strings.json (Italian) Updated translations * Translations update (#3699) * New translations strings.json (French) Updated translations * New translations strings.json (Spanish) Updated translations * New translations strings.json (Italian) Updated translations * New translations strings.json (Japanese) Updated translations * New translations strings.json (Polish) Updated translations * New translations strings.json (Russian) Updated translations * New translations strings.json (Portuguese, Brazilian) Updated translations * Commit updated API documentation --------- Co-authored-by: Owncast <owncast@owncast.online> * New Crowdin translations by GitHub Action (#3701) * New translations strings.json (French) Updated translations * New translations strings.json (Spanish) Updated translations * New translations strings.json (Italian) Updated translations * New translations strings.json (Japanese) Updated translations * New translations strings.json (Polish) Updated translations * New translations strings.json (Russian) Updated translations * New translations strings.json (Portuguese, Brazilian) Updated translations * New Crowdin translations by GitHub Action --------- Co-authored-by: Gabe Kangas <gabek@real-ity.com> Co-authored-by: Crowdin Bot <support+bot@crowdin.com> * Draft: Mark strings for translation. (#3458) * Mark strings for translation. * Mark up strings for translation * fix(web): fix linter warnings --------- Co-authored-by: Le fractal <17422-fractal@users.noreply.framagit.org> Co-authored-by: Gabe Kangas <gabek@real-ity.com> * do not pull from cowdin via workflow * Commit updated translations * feat: add translations support to admin pages and components (#3977) * feat: add translations support to admin pages and components Added translations support admin main page and its components, help page, handware-info page. Added translations support for LogTable, NewsFeed and StreamHealthOverview components. * update package.json * fix rendering issue * Commit updated API documentation --------- Co-authored-by: Owncast <owncast@owncast.online> Co-authored-by: Gabe Kangas <gabek@real-ity.com> * Offline banner i18n formatting (#3997) * Fix "Last live ago" string formatting with i18n interpolation * Change some base translation jsons to use i18n interpolation * Linting fix * chore(js): ignore i18n pkgs in knip * fix(test): fix browser ui test * fix(js): remove unused var --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Crowdin Bot <support+bot@crowdin.com> Co-authored-by: Owncast <owncast@owncast.online> Co-authored-by: taintedcypher <119351153+taintedcypher@users.noreply.github.com> Co-authored-by: Le fractal <17422-fractal@users.noreply.framagit.org> Co-authored-by: Sufyaan Khateeb <81009832+SufyaanKhateeb@users.noreply.github.com> Co-authored-by: mahmed2000 <mahmad2000@protonmail.com>
This commit is contained in:
55
.github/workflows/translations.yml
vendored
Normal file
55
.github/workflows/translations.yml
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
name: Translation job
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'web/i18n/strings.json'
|
||||
- 'web/**/*.tsx'
|
||||
- 'web/**/*.js'
|
||||
- 'crowdin.yml'
|
||||
- '.github/workflows/translations.yml'
|
||||
- 'web/i18next-parser.config.mjs'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
generate-translations:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./web
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
if: ${{ github.actor != 'renovate[bot]' && github.actor != 'renovate' }}
|
||||
run: npm install
|
||||
|
||||
- name: Generate translation files
|
||||
run: npm run translate
|
||||
|
||||
- name: Crowdin upload sources/download translations
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
upload_sources: true
|
||||
download_translations: false
|
||||
localization_branch_name: translations
|
||||
config: crowdin.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
- name: Commit changes
|
||||
uses: EndBug/add-and-commit@v9
|
||||
with:
|
||||
author_name: Owncast
|
||||
author_email: owncast@owncast.online
|
||||
message: 'Commit updated translations'
|
||||
add: 'web/i18n/**'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
10
crowdin.yml
Normal file
10
crowdin.yml
Normal file
@ -0,0 +1,10 @@
|
||||
project_id_env: CROWDIN_PROJECT_ID
|
||||
api_token_env: CROWDIN_PERSONAL_TOKEN
|
||||
pull_request_title: Translations update
|
||||
pull_request_labels:
|
||||
- crowdin
|
||||
- i18n
|
||||
commit_message: Updated translations
|
||||
files:
|
||||
- source: /web/i18n/strings.json
|
||||
translation: /web/i18n/%two_letters_code%.json
|
||||
9066
docs/api/index.html
9066
docs/api/index.html
File diff suppressed because one or more lines are too long
@ -1,10 +1,10 @@
|
||||
// TODO: Fire API call to enable federation and set domain, and stream username.
|
||||
|
||||
import fetchData from '../../support/fetchData.js';
|
||||
import { setup } from '../../support/setup.js';
|
||||
setup();
|
||||
|
||||
describe('Fediverse tests', () => {
|
||||
// Enable Fediverse features.
|
||||
before(() => {
|
||||
it('Can visit the page', () => {
|
||||
fetchData('http://localhost:8080/api/admin/config/serverurl', {
|
||||
method: 'POST',
|
||||
data: { value: 'https://testing.biz' },
|
||||
@ -13,11 +13,10 @@ describe('Fediverse tests', () => {
|
||||
method: 'POST',
|
||||
data: { value: true },
|
||||
});
|
||||
});
|
||||
cy.wait(1500);
|
||||
|
||||
it('Can visit the page', () => {
|
||||
cy.visit('http://localhost:8080/');
|
||||
cy.reload(true);
|
||||
// cy.reload(true, { timeout: 10000 });
|
||||
});
|
||||
|
||||
// Offline banner
|
||||
|
||||
@ -16,10 +16,22 @@
|
||||
"ignoreDependencies": [
|
||||
"@fontsource/inter",
|
||||
"@fontsource/poppins",
|
||||
"@next/bundle-analyzer",
|
||||
"autoprefixer",
|
||||
"yaml",
|
||||
"sharp",
|
||||
"workbox-precaching",
|
||||
"workbox-window",
|
||||
"@storybook/addon-a11y",
|
||||
"@storybook/addon-actions",
|
||||
"@storybook/addon-docs",
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-postcss",
|
||||
"@storybook/addon-viewport",
|
||||
"@storybook/cli",
|
||||
"@storybook/mdx2-csf",
|
||||
"@storybook/preset-scss",
|
||||
"@mdx-js/react",
|
||||
"@storybook/testing-library",
|
||||
"@svgr/webpack",
|
||||
@ -34,6 +46,9 @@
|
||||
"install",
|
||||
"mdx-mermaid",
|
||||
"mermaid",
|
||||
"sass-loader",
|
||||
"sb",
|
||||
"storybook-addon-fetch-mock",
|
||||
"storybook-preset-less",
|
||||
"ts-jest",
|
||||
"stylelint-config-standard",
|
||||
@ -46,6 +61,16 @@
|
||||
"@commitlint/cli",
|
||||
"@commitlint/config-conventional",
|
||||
"babel-plugin-dynamic-import-node",
|
||||
"@babel/preset-react"
|
||||
"@babel/preset-react",
|
||||
"postcss",
|
||||
"stylelint",
|
||||
"@babel/core",
|
||||
"@storybook/addon-mdx-gfm",
|
||||
"@storybook/addon-styling-webpack",
|
||||
"@storybook/nextjs",
|
||||
"@storybook/react",
|
||||
"resolve-url-loader",
|
||||
"i18next-parser",
|
||||
"i18next-scanner"
|
||||
]
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
import { Button } from 'antd';
|
||||
import { FC } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import styles from './ActionButton/ActionButton.module.scss';
|
||||
|
||||
// Lazy loaded components
|
||||
@ -14,7 +15,10 @@ export type NotifyButtonProps = {
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export const NotifyButton: FC<NotifyButtonProps> = ({ onClick, text }) => (
|
||||
export const NotifyButton: FC<NotifyButtonProps> = ({ onClick, text }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Button
|
||||
type="primary"
|
||||
className={styles.button}
|
||||
@ -22,6 +26,7 @@ export const NotifyButton: FC<NotifyButtonProps> = ({ onClick, text }) => (
|
||||
onClick={onClick}
|
||||
id="notify-button"
|
||||
>
|
||||
{text || 'Notify'}
|
||||
{text || t('Notify')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@ import { Table, Tag, Typography } from 'antd';
|
||||
import Linkify from 'react-linkify';
|
||||
import { SortOrder, TablePaginationConfig } from 'antd/lib/table/interface';
|
||||
import { format } from 'date-fns';
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
@ -28,39 +29,41 @@ export type LogTableProps = {
|
||||
};
|
||||
|
||||
export const LogTable: FC<LogTableProps> = ({ logs, initialPageSize }) => {
|
||||
if (!logs?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { t } = useTranslation();
|
||||
const [pageSize, setPageSize] = useState(initialPageSize);
|
||||
|
||||
const handleTableChange = (pagination: TablePaginationConfig) => {
|
||||
setPageSize(pagination.pageSize);
|
||||
};
|
||||
|
||||
if (!logs?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Level',
|
||||
title: t('Level'),
|
||||
dataIndex: 'level',
|
||||
key: 'level',
|
||||
filters: [
|
||||
{
|
||||
text: 'Info',
|
||||
text: t('Info'),
|
||||
value: 'info',
|
||||
},
|
||||
{
|
||||
text: 'Warning',
|
||||
text: t('Warning'),
|
||||
value: 'warning',
|
||||
},
|
||||
{
|
||||
text: 'Error',
|
||||
value: 'error',
|
||||
text: t('Error'),
|
||||
value: 'Error',
|
||||
},
|
||||
],
|
||||
onFilter: (level, row) => row.level.indexOf(level) === 0,
|
||||
render: renderColumnLevel,
|
||||
},
|
||||
{
|
||||
title: 'Timestamp',
|
||||
title: t('Timestamp'),
|
||||
dataIndex: 'time',
|
||||
key: 'time',
|
||||
render: timestamp => {
|
||||
@ -72,7 +75,7 @@ export const LogTable: FC<LogTableProps> = ({ logs, initialPageSize }) => {
|
||||
defaultSortOrder: 'descend' as SortOrder,
|
||||
},
|
||||
{
|
||||
title: 'Message',
|
||||
title: t('Message'),
|
||||
dataIndex: 'message',
|
||||
key: 'message',
|
||||
render: renderMessage,
|
||||
@ -81,7 +84,7 @@ export const LogTable: FC<LogTableProps> = ({ logs, initialPageSize }) => {
|
||||
|
||||
return (
|
||||
<div className="logs-section">
|
||||
<Title>Logs</Title>
|
||||
<Title>{t('Logs')}</Title>
|
||||
<Table
|
||||
size="middle"
|
||||
dataSource={logs}
|
||||
|
||||
@ -4,6 +4,7 @@ import React, { useState, useEffect, FC } from 'react';
|
||||
import { Collapse, Typography, Skeleton } from 'antd';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import { fetchExternalData } from '../../utils/apis';
|
||||
|
||||
const { Panel } = Collapse;
|
||||
@ -27,6 +28,7 @@ const ArticleItem: FC<ArticleProps> = ({
|
||||
date_published: date,
|
||||
defaultOpen = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dateObject = new Date(date);
|
||||
const dateString = format(dateObject, 'MMM dd, yyyy, HH:mm');
|
||||
return (
|
||||
@ -36,7 +38,7 @@ const ArticleItem: FC<ArticleProps> = ({
|
||||
<p className="timestamp">
|
||||
{dateString} (
|
||||
<Link href={`${OWNCAST_BASE_URL}${url}`} target="_blank" rel="noopener noreferrer">
|
||||
Link
|
||||
{t('Link')}
|
||||
</Link>
|
||||
)
|
||||
</p>
|
||||
@ -48,6 +50,7 @@ const ArticleItem: FC<ArticleProps> = ({
|
||||
};
|
||||
|
||||
export const NewsFeed = () => {
|
||||
const { t } = useTranslation();
|
||||
const [feed, setFeed] = useState<ArticleProps[]>([]);
|
||||
const [loading, setLoading] = useState<Boolean>(true);
|
||||
|
||||
@ -69,11 +72,11 @@ export const NewsFeed = () => {
|
||||
}, []);
|
||||
|
||||
const loadingSpinner = loading ? <Skeleton loading active /> : null;
|
||||
const noNews = !loading && feed.length === 0 ? <div>No news.</div> : null;
|
||||
const noNews = !loading && feed.length === 0 ? <div>{t('No news.')}</div> : null;
|
||||
|
||||
return (
|
||||
<section className="news-feed form-module">
|
||||
<Title level={2}>News & Updates from Owncast</Title>
|
||||
<Title level={2}>{t('News & Updates from Owncast')}</Title>
|
||||
{loadingSpinner}
|
||||
{feed.map(item => (
|
||||
<ArticleItem {...item} key={item.url} defaultOpen={feed.length === 1} />
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import { Card, Col, Row, Typography } from 'antd';
|
||||
import Link from 'next/link';
|
||||
import { FC, useContext } from 'react';
|
||||
@ -43,6 +44,7 @@ export type OfflineProps = {
|
||||
export const Offline: FC<OfflineProps> = ({ logs = [], config }) => {
|
||||
const serverStatusData = useContext(ServerStatusContext);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { serverConfig } = serverStatusData || {};
|
||||
const { rtmpServerPort, streamKeyOverridden } = serverConfig;
|
||||
const instanceUrl = global.window?.location.hostname || '';
|
||||
@ -55,7 +57,7 @@ export const Offline: FC<OfflineProps> = ({ logs = [], config }) => {
|
||||
const data = [
|
||||
{
|
||||
icon: <BookTwoTone twoToneColor="#6f42c1" />,
|
||||
title: 'Use your broadcasting software',
|
||||
title: t('Use your broadcasting software'),
|
||||
content: (
|
||||
<div>
|
||||
<a
|
||||
@ -63,12 +65,13 @@ export const Offline: FC<OfflineProps> = ({ logs = [], config }) => {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn how to point your existing software to your new server and start streaming your
|
||||
content.
|
||||
{t(
|
||||
'Learn how to point your existing software to your new server and start streaming your content.',
|
||||
)}
|
||||
</a>
|
||||
<div className="stream-info-container">
|
||||
<Text strong className="stream-info-label">
|
||||
Streaming URL:
|
||||
{t('Streaming URL:')}
|
||||
</Text>
|
||||
{rtmpURL && (
|
||||
<Paragraph className="stream-info-box" copyable>
|
||||
@ -76,14 +79,14 @@ export const Offline: FC<OfflineProps> = ({ logs = [], config }) => {
|
||||
</Paragraph>
|
||||
)}
|
||||
<Text strong className="stream-info-label">
|
||||
Streaming Keys:
|
||||
{t('Streaming Keys:')}
|
||||
</Text>
|
||||
<Text strong className="stream-info-box">
|
||||
{!streamKeyOverridden ? (
|
||||
<Link href="/admin/config/server"> View </Link>
|
||||
<Link href="/admin/config/server"> {t('View')} </Link>
|
||||
) : (
|
||||
<span style={{ paddingLeft: '10px', fontWeight: 'normal' }}>
|
||||
Overridden via command line.
|
||||
{t('Overridden via command line.')}
|
||||
</span>
|
||||
)}
|
||||
</Text>
|
||||
@ -93,7 +96,7 @@ export const Offline: FC<OfflineProps> = ({ logs = [], config }) => {
|
||||
},
|
||||
{
|
||||
icon: <PlaySquareTwoTone twoToneColor="#f9826c" />,
|
||||
title: 'Embed your video onto other sites',
|
||||
title: t('Embed your video onto other sites'),
|
||||
content: (
|
||||
<div>
|
||||
<a
|
||||
@ -101,7 +104,7 @@ export const Offline: FC<OfflineProps> = ({ logs = [], config }) => {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn how you can add your Owncast stream to other sites you control.
|
||||
{t('Learn how you can add your Owncast stream to other sites you control.')}
|
||||
</a>
|
||||
</div>
|
||||
),
|
||||
@ -111,19 +114,19 @@ export const Offline: FC<OfflineProps> = ({ logs = [], config }) => {
|
||||
if (!config?.chatDisabled) {
|
||||
data.push({
|
||||
icon: <MessageTwoTone twoToneColor="#0366d6" />,
|
||||
title: 'Chat is disabled',
|
||||
content: <span>Chat will continue to be disabled until you begin a live stream.</span>,
|
||||
title: t('Chat is disabled'),
|
||||
content: <span>{t('Chat will continue to be disabled until you begin a live stream.')}</span>,
|
||||
});
|
||||
}
|
||||
|
||||
if (!config?.yp?.enabled) {
|
||||
data.push({
|
||||
icon: <ProfileTwoTone twoToneColor="#D18BFE" />,
|
||||
title: 'Find an audience on the Owncast Directory',
|
||||
title: t('Find an audience on the Owncast Directory'),
|
||||
content: (
|
||||
<div>
|
||||
List yourself in the Owncast Directory and show off your stream. Enable it in{' '}
|
||||
<Link href="/admin/config/general/">settings.</Link>
|
||||
{t('List yourself in the Owncast Directory and show off your stream. Enable it in')}{' '}
|
||||
<Link href="/admin/config/general/">{t('settings.')}</Link>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
@ -132,12 +135,13 @@ export const Offline: FC<OfflineProps> = ({ logs = [], config }) => {
|
||||
if (!config?.federation?.enabled) {
|
||||
data.push({
|
||||
icon: <img alt="fediverse" width="20px" src="/img/fediverse-color.png" />,
|
||||
title: 'Add your Owncast instance to the Fediverse',
|
||||
title: t('Add your Owncast instance to the Fediverse'),
|
||||
content: (
|
||||
<div>
|
||||
<Link href="/admin/config-federation/">Enable Owncast social</Link> features to have your
|
||||
instance join the Fediverse, allowing people to follow, share and engage with your live
|
||||
stream.
|
||||
<Link href="/admin/config-federation/">{t('Enable Owncast social features')}</Link>{' '}
|
||||
{t(
|
||||
'to have your instance join the Fediverse, allowing people to follow, share and engage with your live stream.',
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
});
|
||||
@ -152,8 +156,8 @@ export const Offline: FC<OfflineProps> = ({ logs = [], config }) => {
|
||||
<OwncastLogo variant="simple" />
|
||||
</span>
|
||||
<div>
|
||||
<Title level={2}>No stream is active</Title>
|
||||
<p>You should start one.</p>
|
||||
<Title level={2}>{t('No stream is active')}</Title>
|
||||
<p>{t('You should start one.')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
@ -2,6 +2,7 @@ import { Alert, Button, Card, Col, Row, Statistic, Typography } from 'antd';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Link from 'next/link';
|
||||
import React, { FC, useContext } from 'react';
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import { ServerStatusContext } from '../../utils/server-status-context';
|
||||
|
||||
// Lazy loaded components
|
||||
@ -22,6 +23,7 @@ export type StreamHealthOverviewProps = {
|
||||
};
|
||||
|
||||
export const StreamHealthOverview: FC<StreamHealthOverviewProps> = ({ showTroubleshootButton }) => {
|
||||
const { t } = useTranslation();
|
||||
const serverStatusData = useContext(ServerStatusContext);
|
||||
const { health } = serverStatusData;
|
||||
if (!health) {
|
||||
@ -45,15 +47,15 @@ export const StreamHealthOverview: FC<StreamHealthOverviewProps> = ({ showTroubl
|
||||
<Row gutter={8}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title="Healthy Stream"
|
||||
value={healthy ? 'Yes' : 'No'}
|
||||
title={t('Healthy Stream')}
|
||||
value={healthy ? t('Yes') : t('No')}
|
||||
valueStyle={{ color }}
|
||||
prefix={healthy ? <CheckCircleOutlined /> : <ExclamationCircleOutlined />}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title="Playback Health"
|
||||
title={t('Playback Health')}
|
||||
value={healthPercentage}
|
||||
valueStyle={{ color }}
|
||||
suffix="%"
|
||||
@ -65,8 +67,7 @@ export const StreamHealthOverview: FC<StreamHealthOverviewProps> = ({ showTroubl
|
||||
type="secondary"
|
||||
style={{ textAlign: 'center', fontSize: '0.7em', opacity: '0.3' }}
|
||||
>
|
||||
Stream health represents {representation}% of all known players. Other player status is
|
||||
unknown.
|
||||
{`${t('Stream health represents')} ${representation}% ${t('of all known players. Other player status is unknown.')}`}
|
||||
</Typography.Text>
|
||||
</Row>
|
||||
<Row
|
||||
@ -82,7 +83,7 @@ export const StreamHealthOverview: FC<StreamHealthOverviewProps> = ({ showTroubl
|
||||
showTroubleshootButton && (
|
||||
<Link passHref href="/admin/stream-health">
|
||||
<Button size="small" type="text" style={{ color: 'black' }}>
|
||||
TROUBLESHOOT
|
||||
{t('TROUBLESHOOT')}
|
||||
</Button>
|
||||
</Link>
|
||||
)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { FC } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import styles from './Footer.module.scss';
|
||||
import { ServerStatus } from '../../../interfaces/server-status.model';
|
||||
import { serverStatusState } from '../../stores/ClientConfigStore';
|
||||
@ -7,20 +8,21 @@ import { serverStatusState } from '../../stores/ClientConfigStore';
|
||||
export const Footer: FC = () => {
|
||||
const clientStatus = useRecoilValue<ServerStatus>(serverStatusState);
|
||||
const { versionNumber } = clientStatus;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<footer className={styles.footer} id="footer">
|
||||
<span>
|
||||
Powered by <a href="https://owncast.online">Owncast v{versionNumber}</a>
|
||||
{t('Powered by Owncast')} <a href="https://owncast.online">v{versionNumber}</a>
|
||||
</span>
|
||||
<span className={styles.links}>
|
||||
<a href="https://owncast.online/docs" target="_blank" rel="noreferrer">
|
||||
Documentation
|
||||
{t('Documentation')}
|
||||
</a>
|
||||
<a href="https://owncast.online/help" target="_blank" rel="noreferrer">
|
||||
Contribute
|
||||
{t('Contribute')}
|
||||
</a>
|
||||
<a href="https://github.com/owncast/owncast" target="_blank" rel="noreferrer">
|
||||
Source
|
||||
{t('Source')}
|
||||
</a>
|
||||
</span>
|
||||
</footer>
|
||||
|
||||
@ -3,6 +3,7 @@ import { FC, useEffect, useState } from 'react';
|
||||
import cn from 'classnames';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Link from 'next/link';
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import styles from './Header.module.scss';
|
||||
|
||||
// Lazy loaded components
|
||||
@ -23,6 +24,7 @@ export type HeaderComponentProps = {
|
||||
|
||||
export const Header: FC<HeaderComponentProps> = ({ name, chatAvailable, chatDisabled, online }) => {
|
||||
const [canHideChat, setCanHideChat] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setCanHideChat(window.innerWidth >= 768);
|
||||
@ -32,18 +34,18 @@ export const Header: FC<HeaderComponentProps> = ({ name, chatAvailable, chatDisa
|
||||
<header className={cn([`${styles.header}`], 'global-header')}>
|
||||
{online ? (
|
||||
<Link href="#player" className={styles.skipLink}>
|
||||
Skip to player
|
||||
{t('Skip to player')}
|
||||
</Link>
|
||||
) : (
|
||||
<Link href="#offline-message" className={styles.skipLink}>
|
||||
Skip to offline message
|
||||
{t('Skip to offline message')}
|
||||
</Link>
|
||||
)}
|
||||
<Link href="#skip-to-content" className={styles.skipLink}>
|
||||
Skip to page content
|
||||
{t('Skip to page content')}
|
||||
</Link>
|
||||
<Link href="#footer" className={styles.skipLink}>
|
||||
Skip to footer
|
||||
{t('Skip to footer')}
|
||||
</Link>
|
||||
<div className={styles.logo}>
|
||||
<div id="header-logo" className={styles.logoImage}>
|
||||
@ -59,10 +61,10 @@ export const Header: FC<HeaderComponentProps> = ({ name, chatAvailable, chatDisa
|
||||
{!chatAvailable && !chatDisabled && (
|
||||
<Tooltip
|
||||
overlayClassName={styles.toolTip}
|
||||
title="Chat will be available when the stream is live."
|
||||
title={t('Chat will be available when the stream is live.')}
|
||||
placement="left"
|
||||
>
|
||||
<span className={styles.chatOfflineText}>Chat is offline</span>
|
||||
<span className={styles.chatOfflineText}>{t('Chat is offline')}</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</header>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect, FC } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import styles from './NotifyReminderPopup.module.scss';
|
||||
import { Popover } from '../Popover/Popover';
|
||||
|
||||
@ -24,6 +25,7 @@ export const NotifyReminderPopup: FC<NotifyReminderPopupProps> = ({
|
||||
}) => {
|
||||
const [openPopup, setOpenPopup] = useState(open);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setOpenPopup(open);
|
||||
@ -33,7 +35,7 @@ export const NotifyReminderPopup: FC<NotifyReminderPopupProps> = ({
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
const title = <div className={styles.title}>Stay updated!</div>;
|
||||
const title = <div className={styles.title}>{t('Stay updated!')}</div>;
|
||||
|
||||
const popupClicked = e => {
|
||||
e.stopPropagation();
|
||||
@ -56,7 +58,7 @@ export const NotifyReminderPopup: FC<NotifyReminderPopupProps> = ({
|
||||
>
|
||||
<CloseOutlined />
|
||||
</button>
|
||||
<div className={styles.contentbutton}>Click and never miss future streams!</div>
|
||||
<div className={styles.contentbutton}>{t('Click and never miss future streams!')}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import { FC } from 'react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import dynamic from 'next/dynamic';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import styles from './OfflineBanner.module.scss';
|
||||
|
||||
// Lazy loaded components
|
||||
@ -36,13 +37,15 @@ export const OfflineBanner: FC<OfflineBannerProps> = ({
|
||||
onFollowClick,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
let text;
|
||||
if (customText) {
|
||||
text = customText;
|
||||
} else if (!customText && notificationsEnabled && fediverseAccount) {
|
||||
text = (
|
||||
<span>
|
||||
This stream is offline. You can{' '}
|
||||
{t('This stream is offline. You can')}{' '}
|
||||
<span role="link" tabIndex={0} className={styles.actionLink} onClick={onNotifyClick}>
|
||||
be notified
|
||||
</span>{' '}
|
||||
@ -56,21 +59,25 @@ export const OfflineBanner: FC<OfflineBannerProps> = ({
|
||||
} else if (!customText && notificationsEnabled) {
|
||||
text = (
|
||||
<span>
|
||||
This stream is offline.{' '}
|
||||
{t('This stream is offline')}.{' '}
|
||||
<span role="link" tabIndex={0} className={styles.actionLink} onClick={onNotifyClick}>
|
||||
Be notified
|
||||
</span>{' '}
|
||||
the next time {streamName} goes live.
|
||||
{t('the next time goes live', { streamer: streamName })}.
|
||||
</span>
|
||||
);
|
||||
} else if (!customText && fediverseAccount) {
|
||||
text = (
|
||||
<span>
|
||||
This stream is offline.{' '}
|
||||
{t('This stream is offline.')}{' '}
|
||||
<span role="link" tabIndex={0} className={styles.actionLink} onClick={onFollowClick}>
|
||||
Follow
|
||||
{t('Follow')}
|
||||
</span>{' '}
|
||||
{fediverseAccount} on the Fediverse to see the next time {streamName} goes live.
|
||||
{t('on the Fediverse to see the next time goes live', {
|
||||
fediverseAccount,
|
||||
streamer: streamName,
|
||||
})}
|
||||
.
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
@ -95,7 +102,7 @@ export const OfflineBanner: FC<OfflineBannerProps> = ({
|
||||
{lastLive && (
|
||||
<div className={styles.lastLiveDate}>
|
||||
<ClockCircleOutlined className={styles.clockIcon} />
|
||||
{`Last live ${formatDistanceToNow(new Date(lastLive))} ago.`}
|
||||
{`${t('Last live ago', { timeAgo: formatDistanceToNow(new Date(lastLive)) })}`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
20
web/i18n/ar.json
Normal file
20
web/i18n/ar.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/bn.json
Normal file
20
web/i18n/bn.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/de.json
Normal file
20
web/i18n/de.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Benachrichtigen",
|
||||
"Powered by Owncast": "Betrieben von Owncast",
|
||||
"Documentation": "Dokumentation",
|
||||
"Contribute": "Beitragen",
|
||||
"Source": "Quelle",
|
||||
"Skip to player": "Zum Spieler springen",
|
||||
"Skip to offline message": "Zur Offline-Nachricht springen",
|
||||
"Skip to page content": "Direkt zum Hauptinhalt",
|
||||
"Skip to footer": "Zum Footer springen",
|
||||
"Chat will be available when the stream is live": "Chat ist verfügbar, wenn der Stream live ist.",
|
||||
"Chat is offline": "Chat ist offline",
|
||||
"Stay updated!": "Bleiben Sie auf dem Laufenden Ihrer Tätigkeiten!",
|
||||
"Click and never miss future streams!": "Klicke und verpasse niemals zukünftige Streams!",
|
||||
"This stream is offline": "Dieser Stream ist offline.",
|
||||
"the next time goes live": "das nächste Mal geht live",
|
||||
"Follow": "Folgen",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Zuletzt live"
|
||||
}
|
||||
20
web/i18n/el.json
Normal file
20
web/i18n/el.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/en.json
Normal file
20
web/i18n/en.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/es.json
Normal file
20
web/i18n/es.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notificar",
|
||||
"Powered by Owncast": "Desarrollado por Owncast",
|
||||
"Documentation": "Documentación",
|
||||
"Contribute": "Contribuir",
|
||||
"Source": "Fuente",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/fr.json
Normal file
20
web/i18n/fr.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Avertir",
|
||||
"Powered by Owncast": "Propulsé par Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribuer",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Passer au joueur",
|
||||
"Skip to offline message": "Aller au message d'absence",
|
||||
"Skip to page content": "Aller au contenu principal",
|
||||
"Skip to footer": "Aller au pied de page",
|
||||
"Chat will be available when the stream is live": "Le chat sera disponible quand le direct débutera.",
|
||||
"Chat is offline": "Le chat est hors ligne",
|
||||
"Stay updated!": "Restez à jour !",
|
||||
"Click and never miss future streams!": "Cliquez et ne manquez jamais les futurs diffusions !",
|
||||
"This stream is offline": "Ce serveur est hors-ligne.",
|
||||
"the next time goes live": "la prochaine fois que diffuse en direct",
|
||||
"Follow": "Suivre",
|
||||
"on the Fediverse to see the next time goes live": "sur le Fédiverse pour voir la prochaine fois que lance un direct",
|
||||
"Last live ago": "Dernière diffusion il y a"
|
||||
}
|
||||
20
web/i18n/ga.json
Normal file
20
web/i18n/ga.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/hi.json
Normal file
20
web/i18n/hi.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/hr.json
Normal file
20
web/i18n/hr.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
19
web/i18n/index.js
Normal file
19
web/i18n/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
const en = require('./en.json');
|
||||
const es = require('./es.json');
|
||||
const de = require('./de.json');
|
||||
const fr = require('./fr.json');
|
||||
|
||||
const i18n = {
|
||||
translations: {
|
||||
en,
|
||||
es,
|
||||
de,
|
||||
fr,
|
||||
},
|
||||
defaultLang: 'en',
|
||||
useBrowserDefault: true,
|
||||
// optional property, will default to "query" if not set
|
||||
languageDataStore: 'query' || 'localStorage',
|
||||
};
|
||||
|
||||
module.exports = i18n;
|
||||
20
web/i18n/it.json
Normal file
20
web/i18n/it.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notifica",
|
||||
"Powered by Owncast": "Alimentato da Owncast",
|
||||
"Documentation": "Documentazione",
|
||||
"Contribute": "Contribuisci",
|
||||
"Source": "Fonte",
|
||||
"Skip to player": "Vai al lettore video",
|
||||
"Skip to offline message": "Vai al messaggio offline",
|
||||
"Skip to page content": "Vai al contenuto della pagina",
|
||||
"Skip to footer": "Vai a piè di pagina",
|
||||
"Chat will be available when the stream is live": "La chat sarà disponibile quando lo stream è in diretta.",
|
||||
"Chat is offline": "La chat è offline",
|
||||
"Stay updated!": "Rimani aggiornato!",
|
||||
"Click and never miss future streams!": "Clicca e non perderti mai gli stream futuri!",
|
||||
"This stream is offline": "Questo stream è offline.",
|
||||
"the next time goes live": "la prossima volta che andrà in diretta",
|
||||
"Follow": "Segui",
|
||||
"on the Fediverse to see the next time goes live": "sul Fediverso per vedere la prossima volta che andrà in diretta",
|
||||
"Last live ago": "Ultima diretta fa"
|
||||
}
|
||||
20
web/i18n/ja.json
Normal file
20
web/i18n/ja.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "通知",
|
||||
"Powered by Owncast": "Owncastによる提供",
|
||||
"Documentation": "ドキュメンテーション",
|
||||
"Contribute": "貢献",
|
||||
"Source": "ソース",
|
||||
"Skip to player": "プレイヤーへスキップ",
|
||||
"Skip to offline message": "オフラインメッセージへスキップ",
|
||||
"Skip to page content": "メインコンテンツへスキップ",
|
||||
"Skip to footer": "フッターへスキップ",
|
||||
"Chat will be available when the stream is live": "チャットは配信が始まると利用できます",
|
||||
"Chat is offline": "チャットはオフラインです",
|
||||
"Stay updated!": "通知を受け取るようにしましょう",
|
||||
"Click and never miss future streams!": "配信を見逃さないようにクリックしましょう",
|
||||
"This stream is offline": "配信はオフラインです",
|
||||
"the next time goes live": "次の配信で",
|
||||
"Follow": "フォロー",
|
||||
"on the Fediverse to see the next time goes live": "Fediverseで次回のライブを見るために",
|
||||
"Last live ago": "前回の配信は 前でした"
|
||||
}
|
||||
20
web/i18n/ko.json
Normal file
20
web/i18n/ko.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/ms.json
Normal file
20
web/i18n/ms.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/nl.json
Normal file
20
web/i18n/nl.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/no.json
Normal file
20
web/i18n/no.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/pa.json
Normal file
20
web/i18n/pa.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/pl.json
Normal file
20
web/i18n/pl.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/pt.json
Normal file
20
web/i18n/pt.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notificar",
|
||||
"Powered by Owncast": "Desenvolvido por Owncast",
|
||||
"Documentation": "Documentação",
|
||||
"Contribute": "Contribua",
|
||||
"Source": "Fonte",
|
||||
"Skip to player": "Pular para o player",
|
||||
"Skip to offline message": "Pular para mensagem offline",
|
||||
"Skip to page content": "Pular para página de conteúdo",
|
||||
"Skip to footer": "Pular para o rodapé",
|
||||
"Chat will be available when the stream is live": "O Chat estará disponível quando a transmissão estiver ativa.",
|
||||
"Chat is offline": "O chat está off-line",
|
||||
"Stay updated!": "Mantenha-se atualizado!",
|
||||
"Click and never miss future streams!": "Clique e não perca futuras transmissões!",
|
||||
"This stream is offline": "Esta transmissão não está ativa.",
|
||||
"the next time goes live": "a próxima transmissão será",
|
||||
"Follow": "Siga",
|
||||
"on the Fediverse to see the next time goes live": "no Fediverse para ver a data da próxima transmissão",
|
||||
"Last live ago": "Última transmissão foi"
|
||||
}
|
||||
20
web/i18n/ru.json
Normal file
20
web/i18n/ru.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Оповещать",
|
||||
"Powered by Owncast": "Работает на Owncast",
|
||||
"Documentation": "Документация",
|
||||
"Contribute": "Внести вклад",
|
||||
"Source": "Исходный код",
|
||||
"Skip to player": "Перейти к плееру",
|
||||
"Skip to offline message": "Перейти к сообщениям офлайн",
|
||||
"Skip to page content": "Перейти к основному содержимому",
|
||||
"Skip to footer": "Перейти к нижнему колонтитулу",
|
||||
"Chat will be available when the stream is live": "Чат будет доступен, когда будет запущен прямой эфир.",
|
||||
"Chat is offline": "Чат не в сети",
|
||||
"Stay updated!": "Будьте в курсе!",
|
||||
"Click and never miss future streams!": "Кликните и никогда не пропустите будущие стримы!",
|
||||
"This stream is offline": "Эта трансляция выключена.",
|
||||
"the next time goes live": "в следующий раз, когда выйдет в эфир",
|
||||
"Follow": "Подписаться",
|
||||
"on the Fediverse to see the next time goes live": "на Fedivers, чтобы посмотреть, когда в следующий раз выйдет в прямой эфир",
|
||||
"Last live ago": "Последний эфир назад"
|
||||
}
|
||||
1
web/i18n/strings.json
Normal file
1
web/i18n/strings.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
20
web/i18n/strings_old.json
Normal file
20
web/i18n/strings_old.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/sv.json
Normal file
20
web/i18n/sv.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/th.json
Normal file
20
web/i18n/th.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/vi.json
Normal file
20
web/i18n/vi.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
20
web/i18n/zh.json
Normal file
20
web/i18n/zh.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"Notify": "Notify",
|
||||
"Powered by Owncast": "Powered by Owncast",
|
||||
"Documentation": "Documentation",
|
||||
"Contribute": "Contribute",
|
||||
"Source": "Source",
|
||||
"Skip to player": "Skip to player",
|
||||
"Skip to offline message": "Skip to offline message",
|
||||
"Skip to page content": "Skip to page content",
|
||||
"Skip to footer": "Skip to footer",
|
||||
"Chat will be available when the stream is live": "Chat will be available when the stream is live.",
|
||||
"Chat is offline": "Chat is offline",
|
||||
"Stay updated!": "Stay updated!",
|
||||
"Click and never miss future streams!": "Click and never miss future streams!",
|
||||
"This stream is offline": "This stream is offline.",
|
||||
"the next time goes live": "the next time {{streamer}} goes live",
|
||||
"Follow": "Follow",
|
||||
"on the Fediverse to see the next time goes live": "on the Fediverse to see the next time goes live",
|
||||
"Last live ago": "Last live {{timeAgo}} ago"
|
||||
}
|
||||
113
web/i18next-parser.config.mjs
Normal file
113
web/i18next-parser.config.mjs
Normal file
@ -0,0 +1,113 @@
|
||||
// i18next-parser.config.js
|
||||
|
||||
export default {
|
||||
contextSeparator: '_',
|
||||
// Key separator used in your translation keys
|
||||
|
||||
createOldCatalogs: true,
|
||||
// Save the \_old files
|
||||
|
||||
defaultNamespace: 'translation',
|
||||
// Default namespace used in your i18next config
|
||||
|
||||
defaultValue: function (locale, namespace, key, value) {
|
||||
return `${key}`;
|
||||
}, // Default value to give to keys with no value
|
||||
// You may also specify a function accepting the locale, namespace, key, and value as arguments
|
||||
|
||||
indentation: 2,
|
||||
// Indentation of the catalog files
|
||||
|
||||
keepRemoved: false,
|
||||
// Keep keys from the catalog that are no longer in code
|
||||
// You may either specify a boolean to keep or discard all removed keys.
|
||||
// You may also specify an array of patterns: the keys from the catalog that are no long in the code but match one of the patterns will be kept.
|
||||
// The patterns are applied to the full key including the namespace, the parent keys and the separators.
|
||||
|
||||
keySeparator: '.',
|
||||
// Key separator used in your translation keys
|
||||
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
|
||||
|
||||
// see below for more details
|
||||
lexers: {
|
||||
hbs: ['HandlebarsLexer'],
|
||||
handlebars: ['HandlebarsLexer'],
|
||||
|
||||
htm: ['HTMLLexer'],
|
||||
html: ['HTMLLexer'],
|
||||
|
||||
mjs: ['JavascriptLexer'],
|
||||
js: ['JavascriptLexer'], // if you're writing jsx inside .js files, change this to JsxLexer
|
||||
ts: ['JavascriptLexer'],
|
||||
jsx: ['JsxLexer'],
|
||||
tsx: ['JsxLexer'],
|
||||
|
||||
default: ['JavascriptLexer'],
|
||||
},
|
||||
|
||||
lineEnding: 'auto',
|
||||
// Control the line ending. See options at https://github.com/ryanve/eol
|
||||
|
||||
locales: ['en'],
|
||||
// An array of the locales in your applications
|
||||
|
||||
namespaceSeparator: ':',
|
||||
// Namespace separator used in your translation keys
|
||||
// If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance.
|
||||
|
||||
output: 'i18n/strings.json',
|
||||
// Supports $LOCALE and $NAMESPACE injection
|
||||
// Supports JSON (.json) and YAML (.yml) file formats
|
||||
// Where to write the locale files relative to process.cwd()
|
||||
|
||||
pluralSeparator: '_',
|
||||
// Plural separator used in your translation keys
|
||||
// If you want to use plain english keys, separators such as `_` might conflict. You might want to set `pluralSeparator` to a different string that does not occur in your keys.
|
||||
// If you don't want to generate keys for plurals (for example, in case you are using ICU format), set `pluralSeparator: false`.
|
||||
|
||||
input: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
|
||||
// An array of globs that describe where to look for source files
|
||||
// relative to the location of the configuration file
|
||||
|
||||
sort: false,
|
||||
// Whether or not to sort the catalog. Can also be a [compareFunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#parameters)
|
||||
|
||||
verbose: false,
|
||||
// Display info about the parsing including some stats
|
||||
|
||||
failOnWarnings: false,
|
||||
// Exit with an exit code of 1 on warnings
|
||||
|
||||
failOnUpdate: false,
|
||||
// Exit with an exit code of 1 when translations are updated (for CI purpose)
|
||||
|
||||
customValueTemplate: null,
|
||||
// If you wish to customize the value output the value as an object, you can set your own format.
|
||||
// ${defaultValue} is the default value you set in your translation function.
|
||||
// Any other custom property will be automatically extracted.
|
||||
//
|
||||
// Example:
|
||||
// {
|
||||
// message: "${defaultValue}",
|
||||
// description: "${maxLength}", // t('my-key', {maxLength: 150})
|
||||
// }
|
||||
|
||||
resetDefaultValueLocale: null,
|
||||
// The locale to compare with default values to determine whether a default value has been changed.
|
||||
// If this is set and a default value differs from a translation in the specified locale, all entries
|
||||
// for that key across locales are reset to the default value, and existing translations are moved to
|
||||
// the `_old` file.
|
||||
|
||||
// i18nextOptions: { returnDetails: true, lng: '$LOCALE' },
|
||||
// If you wish to customize options in internally used i18next instance, you can define an object with any
|
||||
// configuration property supported by i18next (https://www.i18next.com/overview/configuration-options).
|
||||
// { compatibilityJSON: 'v3' } can be used to generate v3 compatible plurals.
|
||||
|
||||
yamlOptions: null,
|
||||
// If you wish to customize options for yaml output, you can define an object here.
|
||||
// Configuration options are here (https://github.com/nodeca/js-yaml#dump-object---options-).
|
||||
// Example:
|
||||
// {
|
||||
// lineWidth: -1,
|
||||
// }
|
||||
};
|
||||
1742
web/package-lock.json
generated
1742
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,8 @@
|
||||
"build-storybook": "storybook build",
|
||||
"build-styles": "cd ./style-definitions && style-dictionary build && ./build.sh && cd -",
|
||||
"test": "jest",
|
||||
"format": "prettier --write **/*.{js,ts,jsx,tsx,css,md,scss}"
|
||||
"format": "prettier --write **/*.{js,ts,jsx,tsx,css,md,scss}",
|
||||
"translate": "i18next -c i18next-parser.config.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "4.8.3",
|
||||
@ -39,10 +40,13 @@
|
||||
"classnames": "2.5.1",
|
||||
"date-fns": "^4.0.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"i18next-parser": "^9.1.0",
|
||||
"i18next-scanner": "^4.6.0",
|
||||
"interweave": "^13.0.0",
|
||||
"interweave-autolink": "^5.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"next": "14.2.21",
|
||||
"next-export-i18n": "^3.0.0",
|
||||
"next-pwa": "^5.6.0",
|
||||
"next-with-less": "3.0.1",
|
||||
"postcss-flexbugs-fixes": "5.0.2",
|
||||
|
||||
@ -24,7 +24,7 @@ class InlineStylesHead extends Head {
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Html>
|
||||
<InlineStylesHead />
|
||||
<body>
|
||||
<Main />
|
||||
|
||||
@ -5,6 +5,7 @@ import { ColumnsType } from 'antd/es/table';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import { MessageType } from '../../../types/chat';
|
||||
import {
|
||||
CHAT_HISTORY,
|
||||
@ -61,6 +62,7 @@ export default function Chat() {
|
||||
const [bulkProcessing, setBulkProcessing] = useState(false);
|
||||
const [bulkOutcome, setBulkOutcome] = useState(null);
|
||||
const [bulkAction, setBulkAction] = useState('');
|
||||
const { t } = useTranslation();
|
||||
let outcomeTimeout = null;
|
||||
let chatReloadInterval = null;
|
||||
|
||||
@ -153,7 +155,7 @@ export default function Chat() {
|
||||
|
||||
const chatColumns: ColumnsType<MessageType> = [
|
||||
{
|
||||
title: 'Time',
|
||||
title: t('Time'),
|
||||
dataIndex: 'timestamp',
|
||||
key: 'timestamp',
|
||||
className: 'timestamp-col',
|
||||
@ -166,7 +168,7 @@ export default function Chat() {
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: 'User',
|
||||
title: t('User'),
|
||||
dataIndex: 'user',
|
||||
key: 'user',
|
||||
className: 'name-col',
|
||||
@ -182,7 +184,7 @@ export default function Chat() {
|
||||
width: 110,
|
||||
},
|
||||
{
|
||||
title: 'Message',
|
||||
title: t('Message'),
|
||||
dataIndex: 'body',
|
||||
key: 'body',
|
||||
className: 'message-col',
|
||||
@ -201,8 +203,8 @@ export default function Chat() {
|
||||
key: 'hiddenAt',
|
||||
className: 'toggle-col',
|
||||
filters: [
|
||||
{ text: 'Visible messages', value: true },
|
||||
{ text: 'Hidden messages', value: false },
|
||||
{ text: t('Visible messages'), value: true },
|
||||
{ text: t('Hidden messages'), value: false },
|
||||
],
|
||||
onFilter: (value, record) => record.visible === value,
|
||||
render: (hiddenAt, record) => (
|
||||
@ -219,10 +221,12 @@ export default function Chat() {
|
||||
|
||||
return (
|
||||
<div className="chat-messages">
|
||||
<Title>Chat Messages</Title>
|
||||
<p>Manage the messages from viewers that show up on your stream.</p>
|
||||
<Title>{t('Chat Messages')}</Title>
|
||||
<p>{t('Manage the messages from viewers that show up on your stream.')}</p>
|
||||
<div className={bulkDivClasses}>
|
||||
<span className="label">Check multiple messages to change their visibility to: </span>
|
||||
<span className="label">
|
||||
{t('Check multiple messages to change their visibility to:')}{' '}
|
||||
</span>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
@ -234,7 +238,7 @@ export default function Chat() {
|
||||
disabled={!selectedRowKeys.length || (bulkAction && bulkAction !== 'show')}
|
||||
onClick={handleSubmitBulkShow}
|
||||
>
|
||||
Show
|
||||
{t('Show')}
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
@ -246,7 +250,7 @@ export default function Chat() {
|
||||
disabled={!selectedRowKeys.length || (bulkAction && bulkAction !== 'hide')}
|
||||
onClick={handleSubmitBulkHide}
|
||||
>
|
||||
Hide
|
||||
{t('Hide')}
|
||||
</Button>
|
||||
</div>
|
||||
<Table
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect, useContext, ReactElement } from 'react';
|
||||
import { Tabs } from 'antd';
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import { ServerStatusContext } from '../../../utils/server-status-context';
|
||||
import {
|
||||
CONNECTED_CLIENTS,
|
||||
@ -24,6 +25,7 @@ export default function ChatUsers() {
|
||||
const [ipBans, setIPBans] = useState([]);
|
||||
const [clients, setClients] = useState([]);
|
||||
const [moderators, setModerators] = useState([]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const getInfo = async () => {
|
||||
try {
|
||||
@ -71,34 +73,50 @@ export default function ChatUsers() {
|
||||
<>
|
||||
<ClientTable data={clients} />
|
||||
<p className="description">
|
||||
Visit the{' '}
|
||||
{t('Visit the')}{' '}
|
||||
<a
|
||||
href="https://owncast.online/docs/viewers/?source=admin"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
documentation
|
||||
{t('documentation')}
|
||||
</a>{' '}
|
||||
to configure additional details about your viewers.
|
||||
{t('to configure additional details about your viewers.')}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<p className="description">
|
||||
When a stream is active and chat is enabled, connected chat clients will be displayed here.
|
||||
{t(
|
||||
'When a stream is active and chat is enabled, connected chat clients will be displayed here.',
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
|
||||
const connectedUserTabTitle = (
|
||||
<span>Connected {online ? `(${clients.length})` : '(offline)'}</span>
|
||||
<span>
|
||||
{t('Connected')} ({online ? clients.length : t('offline')})
|
||||
</span>
|
||||
);
|
||||
|
||||
const bannedUsersTabTitle = <span>Banned Users ({disabledUsers.length})</span>;
|
||||
const bannedUsersTabTitle = (
|
||||
<span>
|
||||
{t('Banned Users')} ({disabledUsers.length})
|
||||
</span>
|
||||
);
|
||||
const bannedUsersTable = <UserTable data={disabledUsers} />;
|
||||
|
||||
const bannedIPTabTitle = <span>IP Bans ({ipBans.length})</span>;
|
||||
const bannedIPTabTitle = (
|
||||
<span>
|
||||
{t('IP Bans')} ({ipBans.length})
|
||||
</span>
|
||||
);
|
||||
const bannedIpTable = <BannedIPsTable data={ipBans} />;
|
||||
|
||||
const moderatorUsersTabTitle = <span>Moderators ({moderators.length})</span>;
|
||||
const moderatorUsersTabTitle = (
|
||||
<span>
|
||||
{t('Moderators')} ({moderators.length})
|
||||
</span>
|
||||
);
|
||||
const moderatorTable = <UserTable data={moderators} />;
|
||||
|
||||
const items = [
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Row, Col, Typography, Alert, Spin } from 'antd';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import { fetchData, FETCH_INTERVAL, HARDWARE_STATS } from '../../utils/apis';
|
||||
import { Chart } from '../../components/admin/Chart';
|
||||
import { StatisticItem } from '../../components/admin/StatisticItem';
|
||||
@ -22,6 +23,7 @@ const SaveOutlined = dynamic(() => import('@ant-design/icons/SaveOutlined'), {
|
||||
});
|
||||
|
||||
export default function HardwareInfo() {
|
||||
const { t } = useTranslation();
|
||||
const [hardwareStatus, setHardwareStatus] = useState({
|
||||
cpu: [], // Array<TimedValue>(),
|
||||
memory: [], // Array<TimedValue>(),
|
||||
@ -53,13 +55,13 @@ export default function HardwareInfo() {
|
||||
if (!hardwareStatus.cpu) {
|
||||
return (
|
||||
<div>
|
||||
<Typography.Title>Hardware Info</Typography.Title>
|
||||
<Typography.Title>{t('Hardware Info')}</Typography.Title>
|
||||
|
||||
<Alert
|
||||
style={{ marginTop: '10px' }}
|
||||
banner
|
||||
message="Please wait"
|
||||
description="No hardware details have been collected yet."
|
||||
message={t('Please wait')}
|
||||
description={t('No hardware details have been collected yet.')}
|
||||
type="info"
|
||||
/>
|
||||
<Spin spinning style={{ width: '100%', margin: '10px' }} />
|
||||
@ -73,19 +75,19 @@ export default function HardwareInfo() {
|
||||
|
||||
const series = [
|
||||
{
|
||||
name: 'CPU',
|
||||
name: t('CPU'),
|
||||
color: '#B63FFF',
|
||||
data: hardwareStatus.cpu,
|
||||
pointStyle: 'rect',
|
||||
},
|
||||
{
|
||||
name: 'Memory',
|
||||
name: t('Memory'),
|
||||
color: '#2087E2',
|
||||
data: hardwareStatus.memory,
|
||||
pointStyle: 'circle',
|
||||
},
|
||||
{
|
||||
name: 'Disk',
|
||||
name: t('Disk'),
|
||||
color: '#FF7700',
|
||||
data: hardwareStatus.disk,
|
||||
pointStyle: 'rectRounded',
|
||||
@ -94,7 +96,7 @@ export default function HardwareInfo() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography.Title>Hardware Info</Typography.Title>
|
||||
<Typography.Title>{t('Hardware Info')}</Typography.Title>
|
||||
<br />
|
||||
<div>
|
||||
<Row gutter={[16, 16]} justify="space-around">
|
||||
@ -130,7 +132,7 @@ export default function HardwareInfo() {
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Chart title="% used" dataCollections={series} color="#FF7700" unit="%" />
|
||||
<Chart title={`% ${t('used')}`} dataCollections={series} color="#FF7700" unit="%" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -5,6 +5,7 @@ import Title from 'antd/lib/typography/Title';
|
||||
import React, { ReactElement } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import { AdminLayout } from '../../components/layouts/AdminLayout';
|
||||
|
||||
// Lazy loaded components
|
||||
@ -50,10 +51,12 @@ const SlidersTwoTone = dynamic(() => import('@ant-design/icons/SlidersTwoTone'),
|
||||
});
|
||||
|
||||
export default function Help() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const questions = [
|
||||
{
|
||||
icon: <SettingTwoTone style={{ fontSize: '24px' }} />,
|
||||
title: 'I want to configure my owncast instance',
|
||||
title: t('I want to configure my owncast instance'),
|
||||
content: (
|
||||
<div>
|
||||
<a
|
||||
@ -61,14 +64,14 @@ export default function Help() {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<LinkOutlined /> Learn more
|
||||
<LinkOutlined /> {t('Learn more')}
|
||||
</a>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <CameraTwoTone style={{ fontSize: '24px' }} />,
|
||||
title: 'Help configuring my broadcasting software',
|
||||
title: t('Help configuring my broadcasting software'),
|
||||
content: (
|
||||
<div>
|
||||
<a
|
||||
@ -76,14 +79,14 @@ export default function Help() {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<LinkOutlined /> Learn more
|
||||
<LinkOutlined /> {t('Learn more')}
|
||||
</a>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <Html5TwoTone style={{ fontSize: '24px' }} />,
|
||||
title: 'I want to embed my stream into another site',
|
||||
title: t('I want to embed my stream into another site'),
|
||||
content: (
|
||||
<div>
|
||||
<a
|
||||
@ -91,14 +94,14 @@ export default function Help() {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<LinkOutlined /> Learn more
|
||||
<LinkOutlined /> {t('Learn more')}
|
||||
</a>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <EditTwoTone style={{ fontSize: '24px' }} />,
|
||||
title: 'I want to customize my website',
|
||||
title: t('I want to customize my website'),
|
||||
content: (
|
||||
<div>
|
||||
<a
|
||||
@ -106,14 +109,14 @@ export default function Help() {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<LinkOutlined /> Learn more
|
||||
<LinkOutlined /> {t('Learn more')}
|
||||
</a>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <SlidersTwoTone style={{ fontSize: '24px' }} />,
|
||||
title: 'I want to tweak my video output',
|
||||
title: t('I want to tweak my video output'),
|
||||
content: (
|
||||
<div>
|
||||
<a
|
||||
@ -121,14 +124,14 @@ export default function Help() {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<LinkOutlined /> Learn more
|
||||
<LinkOutlined /> {t('Learn more')}
|
||||
</a>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <DatabaseTwoTone style={{ fontSize: '24px' }} />,
|
||||
title: 'I want to use an external storage provider',
|
||||
title: t('I want to use an external storage provider'),
|
||||
content: (
|
||||
<div>
|
||||
<a
|
||||
@ -136,7 +139,7 @@ export default function Help() {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<LinkOutlined /> Learn more
|
||||
<LinkOutlined /> {t('Learn more')}
|
||||
</a>
|
||||
</div>
|
||||
),
|
||||
@ -146,58 +149,58 @@ export default function Help() {
|
||||
const otherResources = [
|
||||
{
|
||||
icon: <BugTwoTone style={{ fontSize: '24px' }} />,
|
||||
title: 'I found a bug',
|
||||
title: t('I found a bug'),
|
||||
content: (
|
||||
<div>
|
||||
If you found a bug, then please
|
||||
{t('If you found a bug, then please')}
|
||||
<a
|
||||
href="https://github.com/owncast/owncast/issues/new/choose"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{' '}
|
||||
let us know
|
||||
{t('let us know')}
|
||||
</a>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <QuestionCircleTwoTone style={{ fontSize: '24px' }} />,
|
||||
title: 'I have a general question',
|
||||
title: t('I have a general question'),
|
||||
content: (
|
||||
<div>
|
||||
Most general questions are answered in our
|
||||
{t('Most general questions are answered in our')}
|
||||
<a
|
||||
href="https://owncast.online/faq/?source=admin"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{' '}
|
||||
FAQ
|
||||
{t('FAQ')}
|
||||
</a>{' '}
|
||||
or exist in our{' '}
|
||||
{t('or exist in our')}{' '}
|
||||
<a
|
||||
href="https://github.com/owncast/owncast/discussions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
discussions
|
||||
{t('discussions')}
|
||||
</a>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <ApiTwoTone style={{ fontSize: '24px' }} />,
|
||||
title: 'I want to build add-ons for Owncast',
|
||||
title: t('I want to build add-ons for Owncast'),
|
||||
content: (
|
||||
<div>
|
||||
You can build your own bots, overlays, tools and add-ons with our
|
||||
{t('You can build your own bots, overlays, tools and add-ons with our')}
|
||||
<a
|
||||
href="https://owncast.online/thirdparty?source=admin"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
developer APIs.
|
||||
{t('developer APIs.')}
|
||||
</a>
|
||||
</div>
|
||||
),
|
||||
@ -206,11 +209,11 @@ export default function Help() {
|
||||
|
||||
return (
|
||||
<div className="help-page">
|
||||
<Title style={{ textAlign: 'center' }}>How can we help you?</Title>
|
||||
<Title style={{ textAlign: 'center' }}>{t('How can we help you?')}</Title>
|
||||
<Row gutter={[16, 16]} justify="space-around" align="middle">
|
||||
<Col xs={24} lg={12} style={{ textAlign: 'center' }}>
|
||||
<Result status="500" />
|
||||
<Title level={2}>Troubleshooting</Title>
|
||||
<Title level={2}>{t('Troubleshooting')}</Title>
|
||||
<Button
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@ -218,12 +221,12 @@ export default function Help() {
|
||||
icon={<LinkOutlined />}
|
||||
type="primary"
|
||||
>
|
||||
Fix your problems
|
||||
{t('Fix your problems')}
|
||||
</Button>
|
||||
</Col>
|
||||
<Col xs={24} lg={12} style={{ textAlign: 'center' }}>
|
||||
<Result status="404" />
|
||||
<Title level={2}>Documentation</Title>
|
||||
<Title level={2}>{t('Documentation')}</Title>
|
||||
<Button
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@ -231,12 +234,12 @@ export default function Help() {
|
||||
icon={<LinkOutlined />}
|
||||
type="primary"
|
||||
>
|
||||
Read the Docs
|
||||
{t('Read the Docs')}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider />
|
||||
<Title level={2}>Common tasks</Title>
|
||||
<Title level={2}>{t('Common tasks')}</Title>
|
||||
<Row gutter={[16, 16]}>
|
||||
{questions.map(question => (
|
||||
<Col xs={24} lg={12} key={question.title}>
|
||||
@ -247,7 +250,7 @@ export default function Help() {
|
||||
))}
|
||||
</Row>
|
||||
<Divider />
|
||||
<Title level={2}>Other</Title>
|
||||
<Title level={2}>{t('Other')}</Title>
|
||||
<Row gutter={[16, 16]}>
|
||||
{otherResources.map(question => (
|
||||
<Col xs={24} lg={12} key={question.title}>
|
||||
|
||||
@ -3,6 +3,7 @@ import React, { useState, useEffect, useContext, ReactElement } from 'react';
|
||||
import { Skeleton, Card, Statistic, Row, Col } from 'antd';
|
||||
import { formatDistanceToNow, formatRelative } from 'date-fns';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import { ServerStatusContext } from '../../utils/server-status-context';
|
||||
import { LogTable } from '../../components/admin/LogTable';
|
||||
import { Offline } from '../../components/admin/Offline';
|
||||
@ -39,6 +40,8 @@ function streamDetailsFormatter(streamDetails) {
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const serverStatusData = useContext(ServerStatusContext);
|
||||
const { broadcaster, serverConfig: configData } = serverStatusData || {};
|
||||
const { remoteAddr, streamDetails } = broadcaster || {};
|
||||
@ -101,12 +104,12 @@ export default function Home() {
|
||||
<div className="stream-details-item-container">
|
||||
<Statistic
|
||||
className="stream-details-item"
|
||||
title="Outbound Video Stream"
|
||||
title={t('Outbound Video Stream')}
|
||||
value={videoSetting}
|
||||
/>
|
||||
<Statistic
|
||||
className="stream-details-item"
|
||||
title="Outbound Audio Stream"
|
||||
title={t('Outbound Audio Stream')}
|
||||
value={audioSetting}
|
||||
/>
|
||||
</div>
|
||||
@ -130,17 +133,17 @@ export default function Home() {
|
||||
<Row gutter={[16, 16]} align="middle">
|
||||
<Col span={8} sm={24} md={8}>
|
||||
<Statistic
|
||||
title={`Stream started ${formatRelative(broadcastDate, Date.now())}`}
|
||||
title={`${t('Stream started')} ${formatRelative(broadcastDate, Date.now())}`}
|
||||
value={formatDistanceToNow(broadcastDate)}
|
||||
prefix={<ClockCircleOutlined />}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8} sm={24} md={8}>
|
||||
<Statistic title="Viewers" value={viewerCount} prefix={<UserOutlined />} />
|
||||
<Statistic title={t('Viewers')} value={viewerCount} prefix={<UserOutlined />} />
|
||||
</Col>
|
||||
<Col span={8} sm={24} md={8}>
|
||||
<Statistic
|
||||
title="Peak viewer count"
|
||||
title={t('Peak viewer count')}
|
||||
value={sessionPeakViewerCount}
|
||||
prefix={<UserOutlined />}
|
||||
/>
|
||||
@ -154,28 +157,28 @@ export default function Home() {
|
||||
<Col className="stream-details" span={12} sm={24} md={24} lg={12}>
|
||||
<Card
|
||||
size="small"
|
||||
title="Outbound Stream Details"
|
||||
title={t('Outbound Stream Details')}
|
||||
type="inner"
|
||||
className="outbound-details"
|
||||
>
|
||||
{videoQualitySettings}
|
||||
</Card>
|
||||
|
||||
<Card size="small" title="Inbound Stream Details" type="inner">
|
||||
<Card size="small" title={t('Inbound Stream Details')} type="inner">
|
||||
<Statistic
|
||||
className="stream-details-item"
|
||||
title="Input"
|
||||
title={t('Input')}
|
||||
value={`${encoder} ${formatIPAddress(remoteAddr)}`}
|
||||
/>
|
||||
<Statistic
|
||||
className="stream-details-item"
|
||||
title="Inbound Video Stream"
|
||||
title={t('Inbound Video Stream')}
|
||||
value={streamDetails}
|
||||
formatter={streamDetailsFormatter}
|
||||
/>
|
||||
<Statistic
|
||||
className="stream-details-item"
|
||||
title="Inbound Audio Stream"
|
||||
title={t('Inbound Audio Stream')}
|
||||
value={streamAudioDetailString}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
@ -2,6 +2,7 @@ import React, { useState, useEffect, useContext, ReactElement } from 'react';
|
||||
import { Row, Col, Typography, MenuProps, Dropdown, Spin, Alert } from 'antd';
|
||||
import { getUnixTime, sub } from 'date-fns';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useTranslation } from 'next-export-i18n';
|
||||
import { Chart } from '../../components/admin/Chart';
|
||||
import { StatisticItem } from '../../components/admin/StatisticItem';
|
||||
import { ViewerTable } from '../../components/admin/ViewerTable';
|
||||
@ -26,6 +27,7 @@ const FETCH_INTERVAL = 60 * 1000; // 1 min
|
||||
|
||||
export default function ViewersOverTime() {
|
||||
const context = useContext(ServerStatusContext);
|
||||
const { t } = useTranslation();
|
||||
const { online, broadcaster, viewerCount, overallPeakViewerCount, sessionPeakViewerCount } =
|
||||
context || {};
|
||||
let streamStart;
|
||||
@ -34,13 +36,13 @@ export default function ViewersOverTime() {
|
||||
}
|
||||
|
||||
const times = [
|
||||
{ title: 'Current stream', start: streamStart },
|
||||
{ title: 'Last 12 hours', start: sub(new Date(), { hours: 12 }) },
|
||||
{ title: 'Last 24 hours', start: sub(new Date(), { hours: 24 }) },
|
||||
{ title: 'Last 7 days', start: sub(new Date(), { days: 7 }) },
|
||||
{ title: 'Last 30 days', start: sub(new Date(), { days: 30 }) },
|
||||
{ title: 'Last 3 months', start: sub(new Date(), { months: 3 }) },
|
||||
{ title: 'Last 6 months', start: sub(new Date(), { months: 6 }) },
|
||||
{ title: t('Current stream'), start: streamStart },
|
||||
{ title: t('Last 12 hours'), start: sub(new Date(), { hours: 12 }) },
|
||||
{ title: t('Last 24 hours'), start: sub(new Date(), { hours: 24 }) },
|
||||
{ title: t('Last 7 days'), start: sub(new Date(), { days: 7 }) },
|
||||
{ title: t('Last 30 days'), start: sub(new Date(), { days: 30 }) },
|
||||
{ title: t('Last 3 months'), start: sub(new Date(), { months: 3 }) },
|
||||
{ title: t('Last 6 months'), start: sub(new Date(), { months: 6 }) },
|
||||
];
|
||||
|
||||
const [loadingChart, setLoadingChart] = useState(true);
|
||||
@ -94,13 +96,13 @@ export default function ViewersOverTime() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography.Title>Viewer Info</Typography.Title>
|
||||
<Typography.Title>{t('Viewer Info')}</Typography.Title>
|
||||
<br />
|
||||
<Row gutter={[16, 16]} justify="space-around">
|
||||
{online && (
|
||||
<Col span={8} md={8}>
|
||||
<StatisticItem
|
||||
title="Current viewers"
|
||||
title={t('Current viewers')}
|
||||
value={viewerCount.toString()}
|
||||
prefix={<UserOutlined />}
|
||||
/>
|
||||
@ -108,14 +110,14 @@ export default function ViewersOverTime() {
|
||||
)}
|
||||
<Col md={online ? 8 : 12}>
|
||||
<StatisticItem
|
||||
title={online ? 'Max viewers this stream' : 'Max viewers last stream'}
|
||||
title={online ? t('Max viewers this stream') : t('Max viewers last stream')}
|
||||
value={sessionPeakViewerCount.toString()}
|
||||
prefix={<UserOutlined />}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={online ? 8 : 12}>
|
||||
<StatisticItem
|
||||
title="All-time max viewers"
|
||||
title={t('max viewers')}
|
||||
value={overallPeakViewerCount.toString()}
|
||||
prefix={<UserOutlined />}
|
||||
/>
|
||||
@ -125,8 +127,8 @@ export default function ViewersOverTime() {
|
||||
<Alert
|
||||
style={{ marginTop: '10px' }}
|
||||
banner
|
||||
message="Please wait"
|
||||
description="No viewer data has been collected yet."
|
||||
message={t('Please wait')}
|
||||
description={t('No viewer data has been collected yet.')}
|
||||
type="info"
|
||||
/>
|
||||
)}
|
||||
@ -134,7 +136,7 @@ export default function ViewersOverTime() {
|
||||
<Spin spinning={!viewerInfo.length || loadingChart}>
|
||||
{viewerInfo.length > 0 && (
|
||||
<Chart
|
||||
title="Viewers"
|
||||
title={t('Viewers')}
|
||||
data={viewerInfo}
|
||||
color="#2087E2"
|
||||
unit="viewers"
|
||||
|
||||
Reference in New Issue
Block a user