Compare commits

...

21 Commits

Author SHA1 Message Date
github-actions[bot] 49f4ebdbd7
New Crowdin translations by GitHub Action (#3457) 2 years ago
Gabe Kangas f0429afd92
Translations update (#3456) 2 years ago
github-actions[bot] eae7220700
New Crowdin translations by GitHub Action (#3455) 2 years ago
Gabe Kangas 394eeddc53
chore(deps): update to next config to address build errors 2 years ago
github-actions[bot] 5e83cd6f7c
New Crowdin translations by GitHub Action (#3452) 2 years ago
Owncast fa9c36e62a Commit updated translations 2 years ago
Gabe Kangas d124f3ecb3
Translations update (#3453) 2 years ago
Owncast 97ca0b0a56 Commit updated translations 2 years ago
Gabe Kangas fae7e0e88c Run translation job when web components are updated 2 years ago
Gabe Kangas fb72be0b6b Tooltip i18n 2 years ago
Gabe Kangas 0a05ee87cf Point to updated translated files 2 years ago
github-actions[bot] bfdd6086d6
New Crowdin translations by GitHub Action (#3448) 2 years ago
Gabe Kangas 3444a0f7b1 Update configs 2 years ago
Gabe Kangas e4edbd2735 Update crowdlin config 2 years ago
Gabe Kangas 9c9333a07d try to fix the multiple parsing of a file 2 years ago
Gabe Kangas 5f8aca8d51 Update defaults again 2 years ago
Gabe Kangas 8fdc8b1e79 Update parser config 2 years ago
Gabe Kangas 3d741015fd Update default value 2 years ago
Gabe Kangas cf1f46fa5e Update CI job 2 years ago
Gabe Kangas 8497c3f60b Add CI job for translations 2 years ago
Gabe Kangas 1c8b9d0728 First pass at configuring localization 2 years ago
  1. 55
      .github/workflows/translations.yml
  2. 10
      crowdin.yml
  3. 11
      web/.knip.json
  4. 11
      web/components/action-buttons/NotifyButton.tsx
  5. 10
      web/components/ui/Footer/Footer.tsx
  6. 14
      web/components/ui/Header/Header.tsx
  7. 6
      web/components/ui/NotifyReminderPopup/NotifyReminderPopup.tsx
  8. 21
      web/components/ui/OfflineBanner/OfflineBanner.tsx
  9. 20
      web/i18n/ar.json
  10. 20
      web/i18n/bn.json
  11. 20
      web/i18n/de.json
  12. 20
      web/i18n/el.json
  13. 20
      web/i18n/en.json
  14. 20
      web/i18n/es.json
  15. 20
      web/i18n/fr.json
  16. 20
      web/i18n/ga.json
  17. 20
      web/i18n/hi.json
  18. 20
      web/i18n/hr.json
  19. 19
      web/i18n/index.js
  20. 20
      web/i18n/it.json
  21. 20
      web/i18n/ja.json
  22. 20
      web/i18n/ko.json
  23. 20
      web/i18n/ms.json
  24. 20
      web/i18n/nl.json
  25. 20
      web/i18n/no.json
  26. 20
      web/i18n/pa.json
  27. 20
      web/i18n/ru.json
  28. 20
      web/i18n/strings.json
  29. 20
      web/i18n/sv.json
  30. 20
      web/i18n/th.json
  31. 20
      web/i18n/vi.json
  32. 20
      web/i18n/zh.json
  33. 113
      web/i18next-parser.config.mjs
  34. 64
      web/next.config.js
  35. 1424
      web/package-lock.json
  36. 6
      web/package.json
  37. 2
      web/pages/_document.tsx

55
.github/workflows/translations.yml

@ -0,0 +1,55 @@ @@ -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: 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 }}
- name: Crowdin action
uses: crowdin/github-action@v1
with:
upload_sources: true
download_translations: true
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 }}

10
crowdin.yml

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
'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
project_id_env: CROWDIN_PROJECT_ID
api_token_env: CROWDIN_PERSONAL_TOKEN

11
web/.knip.json

@ -9,14 +9,15 @@ @@ -9,14 +9,15 @@
"**/*stories.*",
"next-env.d.ts",
"public/**",
"tests/**"
"tests/**",
"i18n/index.js",
"i18next-parser.config.mjs"
],
"ignoreDependencies": [
"@fontsource/inter",
"@fontsource/poppins",
"@next/bundle-analyzer",
"autoprefixer",
"webpack-deadcode-plugin",
"yaml",
"postcss-flexbugs-fixes",
"sharp",
@ -31,9 +32,7 @@ @@ -31,9 +32,7 @@
"@storybook/addon-links",
"@storybook/addon-postcss",
"@storybook/addon-viewport",
"@storybook/builder-webpack5",
"@storybook/cli",
"@storybook/manager-webpack5",
"@storybook/mdx2-csf",
"@storybook/preset-scss",
"@mdx-js/react",
@ -64,16 +63,13 @@ @@ -64,16 +63,13 @@
"less-loader",
"mdx-mermaid",
"mermaid",
"prettier",
"sass-loader",
"sb",
"storybook-addon-designs",
"storybook-addon-fetch-mock",
"storybook-preset-less",
"style-loader",
"ts-jest",
"stylelint-config-standard",
"stylelint-config-standard-scss",
"postcss",
"stylelint",
"@babel/core",
@ -82,5 +78,6 @@ @@ -82,5 +78,6 @@
"@storybook/nextjs",
"@storybook/react",
"resolve-url-loader",
"i18next-scanner"
]
}

11
web/components/action-buttons/NotifyButton.tsx

@ -1,6 +1,7 @@ @@ -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 = { @@ -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 }) => ( @@ -22,6 +26,7 @@ export const NotifyButton: FC<NotifyButtonProps> = ({ onClick, text }) => (
onClick={onClick}
id="notify-button"
>
{text || 'Notify'}
{text || t('Notify')}
</Button>
);
);
};

10
web/components/ui/Footer/Footer.tsx

@ -1,5 +1,6 @@ @@ -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'; @@ -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>

14
web/components/ui/Header/Header.tsx

@ -3,6 +3,7 @@ import { FC, useEffect, useState } from 'react'; @@ -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 = { @@ -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 @@ -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 @@ -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>

6
web/components/ui/NotifyReminderPopup/NotifyReminderPopup.tsx

@ -1,5 +1,6 @@ @@ -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> = ({ @@ -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> = ({ @@ -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> = ({ @@ -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>
);

21
web/components/ui/OfflineBanner/OfflineBanner.tsx

@ -5,6 +5,7 @@ import { FC } from 'react'; @@ -5,6 +5,7 @@ import { FC } from 'react';
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
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> = ({ @@ -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> = ({ @@ -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> = ({ @@ -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

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/bn.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/de.json

@ -0,0 +1,20 @@ @@ -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

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/en.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/es.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/fr.json

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
{
"Notify": "Notify",
"Powered by Owncast": "Propulsé par Owncast",
"Documentation": "Documentation",
"Contribute": "Contribuer",
"Source": "Source",
"Skip to player": "Passer au joueur",
"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": "Le chat est hors ligne",
"Stay updated!": "Restez à jour !",
"Click and never miss future streams!": "Click and never miss future streams!",
"This stream is offline": "Ce serveur est hors-ligne.",
"the next time goes live": "la prochaine fois que vous serez en vie",
"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 ago"
}

20
web/i18n/ga.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/hi.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/hr.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

19
web/i18n/index.js

@ -0,0 +1,19 @@ @@ -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

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/ja.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/ko.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/ms.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/nl.json

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
{
"Notify": "Melden",
"Powered by Owncast": "Mogelijk gemaakt door Owncast",
"Documentation": "Documentatie",
"Contribute": "Bijdragen",
"Source": "Bron",
"Skip to player": "Ga naar speler",
"Skip to offline message": "Ga naar offlinebericht",
"Skip to page content": "Ga naar pagina-inhoud",
"Skip to footer": "Ga naar footer",
"Chat will be available when the stream is live": "Chat is beschikbaar wanneer de stream live is.",
"Chat is offline": "Chat is offline",
"Stay updated!": "Blijf op de hoogte!",
"Click and never miss future streams!": "Klik en mis geen toekomstige streams meer!",
"This stream is offline": "Deze stream is offline.",
"the next time goes live": "de volgende keer live gaat",
"Follow": "Volg",
"on the Fediverse to see the next time goes live": "via de Fediverse om de volgende keer live te zien",
"Last live ago": "Laatste live geleden"
}

20
web/i18n/no.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/pa.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/ru.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/strings.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/sv.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/th.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/vi.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

20
web/i18n/zh.json

@ -0,0 +1,20 @@ @@ -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 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 ago"
}

113
web/i18next-parser.config.mjs

@ -0,0 +1,113 @@ @@ -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,
// }
};

64
web/next.config.js

@ -2,7 +2,7 @@ const withLess = require('next-with-less'); @@ -2,7 +2,7 @@ const withLess = require('next-with-less');
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
const { PHASE_DEVELOPMENT_SERVER } = require('next/constants');
const runtimeCaching = require('next-pwa/cache');
const withPWA = require('next-pwa')({
@ -31,27 +31,7 @@ const withPWA = require('next-pwa')({ @@ -31,27 +31,7 @@ const withPWA = require('next-pwa')({
disable: process.env.NODE_ENV === 'development',
});
module.exports = withPWA(
withBundleAnalyzer(
withLess({
productionBrowserSourceMaps: process.env.SOURCE_MAPS === 'true',
trailingSlash: true,
reactStrictMode: true,
images: {
unoptimized: true,
},
swcMinify: true,
output: 'export',
webpack(config) {
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: ['@svgr/webpack'],
});
return config;
},
async rewrites() {
async function rewrites() {
return [
{
source: '/api/:path*',
@ -78,8 +58,46 @@ module.exports = withPWA( @@ -78,8 +58,46 @@ module.exports = withPWA(
destination: 'http://localhost:8080/customjavascript', // Proxy to Backend to work around CORS.
},
];
}
module.exports = async phase => {
/**
* @type {import('next').NextConfig}
*/
let nextConfig = withPWA(
withBundleAnalyzer(
withLess({
productionBrowserSourceMaps: process.env.SOURCE_MAPS === 'true',
trailingSlash: true,
reactStrictMode: true,
images: {
unoptimized: true,
},
swcMinify: true,
webpack(config) {
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: ['@svgr/webpack'],
});
return config;
},
pageExtensions: ['tsx'],
}),
),
);
);
if (phase === PHASE_DEVELOPMENT_SERVER) {
nextConfig = {
...nextConfig,
rewrites,
};
} else {
nextConfig = {
...nextConfig,
output: 'export',
};
}
return nextConfig;
};

1424
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

6
web/package.json

@ -11,7 +11,8 @@ @@ -11,7 +11,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}"
"format": "prettier --write **/*.{js,ts,jsx,tsx,css,md}",
"translate": "i18next -c i18next-parser.config.mjs"
},
"dependencies": {
"@ant-design/icons": "4.8.1",
@ -32,10 +33,12 @@ @@ -32,10 +33,12 @@
"classnames": "2.3.2",
"date-fns": "^2.29.3",
"graphemer": "^1.4.0",
"i18next-parser": "^8.9.0",
"interweave": "^13.0.0",
"interweave-autolink": "^5.1.0",
"lodash": "4.17.21",
"next": "14.0.1",
"next-export-i18n": "^2.1.0",
"next-pwa": "^5.6.0",
"next-with-less": "3.0.1",
"picmo": "5.8.5",
@ -107,6 +110,7 @@ @@ -107,6 +110,7 @@
"eslint-plugin-storybook": "0.6.15",
"handlebars": "^4.7.7",
"html-webpack-plugin": "5.5.3",
"i18next-scanner": "^4.4.0",
"install": "^0.13.0",
"knip": "^2.11.0",
"less": "4.2.0",

2
web/pages/_document.tsx

@ -24,7 +24,7 @@ class InlineStylesHead extends Head { @@ -24,7 +24,7 @@ class InlineStylesHead extends Head {
export default function Document() {
return (
<Html lang="en">
<Html>
<InlineStylesHead />
<body>
<Main />

Loading…
Cancel
Save