diff --git a/frontend/src/locale/en/translation.json b/frontend/src/locale/en/translation.json index 025e4c5..5b868cf 100644 --- a/frontend/src/locale/en/translation.json +++ b/frontend/src/locale/en/translation.json @@ -18,7 +18,11 @@ "system": { "menu-header": "Navigation", "loading": "Loading…", - "connection-lost": "Connection to server was lost, retrying..." + "connection-lost": "Connection to server was lost, retrying...", + "pagination": { + "page": "Page {{page}}", + "gotopage": "Go to page {{page}}" + } }, "debug": { "read-key": "Read key", @@ -28,7 +32,14 @@ "fix-json-btn": "Fix JSON" }, "actions": { - "save": "Save" + "save": "Save", + "edit": "Edit", + "delete": "Delete", + "disable": "Disable", + "enable": "Enable", + "create": "Create", + "cancel": "Cancel", + "ok": "OK" }, "http": { "header": "Web server configuration", @@ -43,8 +54,90 @@ "enable": "Enable back-end (stulbe) integration", "endpoint": "Stulbe Endpoint", "username": "User name", + "username-placeholder": "myUserName", "authkey": "Authorization key", - "authkey-placeholder": "key goes here", - "username-placeholder": "myUserName" + "authkey-placeholder": "key goes here" + }, + "loyalty": { + "goals": { + "reached": "Reached!", + "contributors": "Contributors:", + "no-contributors": "No one has contributed yet :(", + "id": "Goal ID", + "id-placeholder": "goal_id_here", + "id-help": "Choose a simple name that can be referenced by other software. It will be auto-generated from the goal name if you leave it blank.", + "err-goalid-dup": "There is already a goal with this ID! Please choose a different one.", + "name": "Name", + "name-placeholder": "My dream goal", + "icon": "Icon", + "icon-placeholder": "Image URL", + "description": "Description", + "description-placeholder": "What's gonna happen when we reach this goal?", + "header": "Community goals", + "new": "New goal", + "search": "Search by name", + "modify": "Modify goal" + }, + "points": "Points", + "points-fallback": "points", + "queue": { + "header": "Redemption queue", + "search": "Search by username", + "reward-name": "Reward name", + "request": "Request", + "accept": "Accept", + "refund": "Refund", + "err-not-available": "Redemption queue is not available (loyalty disabled or no one has redeemed anything yet)" + }, + "rewards": { + "test": "Test", + "cooldown": "Cooldown", + "required-info": "Required info", + "id": "Reward ID", + "id-placeholder": "reward_id_here", + "err-rewid-dup": "There is already a reward with this ID! Please choose a different one.", + "id-help": "Choose a simple name that can be referenced by other software. It will be auto-generated from the reward name if you leave it blank.", + "name": "Name", + "name-placeholder": "My awesome reward", + "icon": "Icon", + "icon-placeholder": "Image URL", + "description": "Description", + "description-placeholder": "What's so cool about this reward?", + "cost": "Cost", + "requires-extra-info": "Requires viewer-specified details", + "extra-info": "Required info", + "extra-info-placeholder": "What extra detail to ask the viewer for", + "header": "Loyalty rewards", + "new-reward": "New reward", + "search": "Search by name", + "modify-reward": "Modify reward" + }, + "config": { + "header": "Loyalty system configuration", + "enable": "Enable loyalty points", + "err-twitchbot-disabled": "(Twitch bot must be enabled for this!)", + "currency-name": "Currency name", + "bonus-points": "Bonus points for active users", + "points-every": "every", + "give-points": "Give", + "point-reward-frequency": "How often to give {{points}}" + }, + "userlist": { + "give-points": "Give points to user", + "modify-balance": "Modify balance", + "give-button": "Give", + "userlist-header": "All viewers with {{currency}}", + "err-not-available": "Viewer list is not available (loyalty disabled or no one has points)" + } + }, + "form-common": { + "required": "Required", + "date": "Date", + "username": "Username", + "time": { + "seconds": "seconds", + "minutes": "minutes", + "hours": "hours" + } } } diff --git a/frontend/src/ui/components/Modal.tsx b/frontend/src/ui/components/Modal.tsx index 383aa05..dd7ab94 100644 --- a/frontend/src/ui/components/Modal.tsx +++ b/frontend/src/ui/components/Modal.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; export interface ModalProps { title: string; @@ -28,6 +29,7 @@ function Modal({ bgDismiss, children, }: React.PropsWithChildren): React.ReactElement { + const { t } = useTranslation(); return (
onConfirm()} > - {confirmName ?? 'OK'} + {confirmName ?? t('actions.ok')} {showCancel ? ( ) : null} diff --git a/frontend/src/ui/components/PageList.tsx b/frontend/src/ui/components/PageList.tsx index c877868..4afc6b1 100644 --- a/frontend/src/ui/components/PageList.tsx +++ b/frontend/src/ui/components/PageList.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; export interface PageListProps { current: number; @@ -17,6 +18,7 @@ function PageList({ onSelectChange, onPageChange, }: PageListProps): React.ReactElement { + const { t } = useTranslation(); return (
@@ -126,6 +129,8 @@ function GoalModal({ title, confirmText, }: GoalModalProps) { + const { t } = useTranslation(); + const [loyaltyConfig] = useModule(modules.loyaltyConfig); const [goals] = useModule(modules.loyaltyGoals); @@ -175,7 +180,7 @@ function GoalModal({ >
- +
@@ -183,29 +188,24 @@ function GoalModal({ setIDex(ev.target.value)} />

{idInvalid ? (

- There is already a goal with this ID! Please choose a different - one. + {t('loyalty.goals.err-goalid-dup')}

) : ( -

- Choose a simple name that can be referenced by other software. - It will be auto-generated from the goal name if you leave it - blank. -

+

{t('loyalty.goals.id-help')}

)}
- +
@@ -214,7 +214,7 @@ function GoalModal({ disabled={!active} className="input" type="text" - placeholder="My dream goal" + placeholder={t('loyalty.goals.name-placeholder')} value={name ?? ''} onChange={(ev) => setName(ev.target.value)} /> @@ -224,7 +224,7 @@ function GoalModal({
- +
@@ -232,7 +232,7 @@ function GoalModal({ setImage(ev.target.value)} /> @@ -242,14 +242,14 @@ function GoalModal({
- +

@@ -259,7 +259,7 @@ function GoalModal({

- +
@@ -289,6 +289,7 @@ export default function LoyaltyGoalsPage( const [goals, setGoals] = useModule(modules.loyaltyGoals); const [moduleConfig] = useModule(modules.moduleConfig); + const { t } = useTranslation(); const dispatch = useDispatch(); const twitchActive = moduleConfig?.twitch ?? false; @@ -336,7 +337,7 @@ export default function LoyaltyGoalsPage( return ( <> -

Community goals

+

{t('loyalty.goals.header')}

@@ -345,7 +346,7 @@ export default function LoyaltyGoalsPage( disabled={!active} onClick={() => setCreateModal(true)} > - New goal + {t('loyalty.goals.new')}

@@ -353,7 +354,7 @@ export default function LoyaltyGoalsPage( setGoalFilter(ev.target.value)} /> @@ -361,16 +362,16 @@ export default function LoyaltyGoalsPage(
setCreateModal(false)} /> {showModifyGoal ? ( modifyGoal(showModifyGoal.id, goal)} initialData={showModifyGoal} diff --git a/frontend/src/ui/pages/loyalty/Queue.tsx b/frontend/src/ui/pages/loyalty/Queue.tsx index 61d16bb..cd6eab7 100644 --- a/frontend/src/ui/pages/loyalty/Queue.tsx +++ b/frontend/src/ui/pages/loyalty/Queue.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { useDispatch } from 'react-redux'; import { RouteComponentProps } from '@reach/router'; +import { useTranslation } from 'react-i18next'; import { useModule, useUserPoints } from '../../../lib/react-utils'; import { LoyaltyRedeem, @@ -32,6 +33,7 @@ export default function LoyaltyRedeemQueuePage( const [entriesPerPage, setEntriesPerPage] = useState(15); const [page, setPage] = useState(0); const [usernameFilter, setUsernameFilter] = useState(''); + const { t } = useTranslation(); const dispatch = useDispatch(); const changeSort = (key: 'user' | 'when') => { @@ -105,14 +107,14 @@ export default function LoyaltyRedeemQueuePage( return ( <> -

Redemption queue

+

{t('loyalty.queue.header')}

{redemptions ? ( <>
setUsernameFilter(ev.target.value.toLowerCase()) @@ -132,7 +134,7 @@ export default function LoyaltyRedeemQueuePage( changeSort('when')}> - Date + {t('form-common.date')} {sorting.key === 'when' ? ( {sorting.order === 'asc' ? '▴' : '▾'} @@ -142,7 +144,7 @@ export default function LoyaltyRedeemQueuePage( changeSort('user')}> - Username + {t('form-common.username')} {sorting.key === 'user' ? ( {sorting.order === 'asc' ? '▴' : '▾'} @@ -150,8 +152,8 @@ export default function LoyaltyRedeemQueuePage( ) : null} - Reward name - Request + {t('loyalty.queue.reward-name')} + {t('loyalty.queue.request')} @@ -168,11 +170,15 @@ export default function LoyaltyRedeemQueuePage( {redemption.reward.name} {redemption.request_text} - acceptRedeem(redemption)}>Accept + acceptRedeem(redemption)}> + {t('loyalty.queue.accept')} + {redemption.username !== '@PLATFORM' ? ( <> {' 🞄 '} - refundRedeem(redemption)}>Refund + refundRedeem(redemption)}> + {t('loyalty.queue.refund')} + ) : null} @@ -190,10 +196,7 @@ export default function LoyaltyRedeemQueuePage( /> ) : ( -

- Redemption queue is not available (loyalty disabled or no one has - redeemed anything yet) -

+

{t('loyalty.queue.err-not-available')}

)} ); diff --git a/frontend/src/ui/pages/loyalty/Rewards.tsx b/frontend/src/ui/pages/loyalty/Rewards.tsx index 97eb3e1..d821697 100644 --- a/frontend/src/ui/pages/loyalty/Rewards.tsx +++ b/frontend/src/ui/pages/loyalty/Rewards.tsx @@ -2,6 +2,7 @@ import { RouteComponentProps } from '@reach/router'; import React, { useEffect, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import prettyTime from 'pretty-ms'; +import { useTranslation } from 'react-i18next'; import { useModule } from '../../../lib/react-utils'; import { RootState } from '../../../store'; import { @@ -26,9 +27,11 @@ function RewardItem({ onDelete, onTest, }: RewardItemProps) { + const { t } = useTranslation(); const currency = useSelector( (state: RootState) => - state.api.moduleConfigs?.loyaltyConfig?.currency ?? 'points', + state.api.moduleConfigs?.loyaltyConfig?.currency ?? + t('loyalty.points-fallback'), ); const [expanded, setExpanded] = useState(false); const placeholder = 'https://bulma.io/images/placeholders/128x128.png'; @@ -75,26 +78,27 @@ function RewardItem({ {item.description} {item.cooldown > 0 ? (
- Cooldown: {prettyTime(item.cooldown * 1000)} + {t('loyalty.rewards.cooldown')}:{' '} + {prettyTime(item.cooldown * 1000)}
) : null} {item.required_info ? (
- Required info: {item.required_info} + {t('loyalty.rewards.required-info')}: {item.required_info}
) : null}
@@ -120,6 +124,7 @@ function RewardModal({ title, confirmText, }: RewardModalProps) { + const { t } = useTranslation(); const currency = useSelector( (state: RootState) => state.api.moduleConfigs?.loyaltyConfig?.currency ?? 'points', @@ -185,7 +190,7 @@ function RewardModal({ >
- +
@@ -193,29 +198,24 @@ function RewardModal({ setIDex(ev.target.value)} />

{idInvalid ? (

- There is already a reward with this ID! Please choose a - different one. + {t('loyalty.rewards.err-rewid-dup')}

) : ( -

- Choose a simple name that can be referenced by other software. - It will be auto-generated from the reward name if you leave it - blank. -

+

{t('loyalty.rewards.id-help')}

)}
- +
@@ -224,7 +224,7 @@ function RewardModal({ disabled={!active} className="input" type="text" - placeholder="My awesome reward" + placeholder={t('loyalty.rewards.name-placeholder')} value={name ?? ''} onChange={(ev) => setName(ev.target.value)} /> @@ -234,7 +234,7 @@ function RewardModal({
- +
@@ -242,7 +242,7 @@ function RewardModal({ setImage(ev.target.value)} /> @@ -252,14 +252,14 @@ function RewardModal({
- +

@@ -269,7 +269,7 @@ function RewardModal({

- +
@@ -298,7 +298,7 @@ function RewardModal({ checked={extraRequired} onChange={(ev) => setExtraRequired(ev.target.checked)} />{' '} - Requires viewer-specified details + {t('loyalty.rewards.requires-extra-info')}
@@ -307,7 +307,7 @@ function RewardModal({ <>
- +
@@ -316,7 +316,7 @@ function RewardModal({ disabled={!active} className="input" type="text" - placeholder="What extra detail to ask the viewer for" + placeholder={t('loyalty.rewards.extra-info-placeholder')} value={extraDetails ?? ''} onChange={(ev) => setExtraDetails(ev.target.value)} /> @@ -328,7 +328,7 @@ function RewardModal({ ) : null}
- +
@@ -361,9 +361,9 @@ function RewardModal({ setTempCooldownMult(intMult); }} > - - - + + +

@@ -382,6 +382,7 @@ export default function LoyaltyRewardsPage( const [moduleConfig] = useModule(modules.moduleConfig); const dispatch = useDispatch(); + const { t } = useTranslation(); const twitchActive = moduleConfig?.twitch ?? false; const loyaltyEnabled = moduleConfig?.loyalty ?? false; @@ -440,7 +441,7 @@ export default function LoyaltyRewardsPage( return ( <> -

Loyalty rewards

+

{t('loyalty.rewards.header')}

@@ -449,7 +450,7 @@ export default function LoyaltyRewardsPage( disabled={!active} onClick={() => setCreateModal(true)} > - New reward + {t('loyalty.rewards.new-reward')}

@@ -457,7 +458,7 @@ export default function LoyaltyRewardsPage( setRewardFilter(ev.target.value)} /> @@ -465,16 +466,16 @@ export default function LoyaltyRewardsPage(
setCreateModal(false)} /> {showModifyReward ? ( modifyReward(showModifyReward.id, reward)} initialData={showModifyReward} diff --git a/frontend/src/ui/pages/loyalty/Settings.tsx b/frontend/src/ui/pages/loyalty/Settings.tsx index 45aa531..b252dbf 100644 --- a/frontend/src/ui/pages/loyalty/Settings.tsx +++ b/frontend/src/ui/pages/loyalty/Settings.tsx @@ -1,5 +1,6 @@ import { RouteComponentProps } from '@reach/router'; import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { useModule } from '../../../lib/react-utils'; import { getInterval } from '../../../lib/time-utils'; @@ -14,6 +15,7 @@ export default function LoyaltySettingPage( const [twitchConfig] = useModule(modules.twitchConfig); const dispatch = useDispatch(); + const { t } = useTranslation(); const twitchActive = moduleConfig?.twitch ?? false; const twitchBotActive = twitchConfig?.enable_bot ?? false; @@ -52,7 +54,7 @@ export default function LoyaltySettingPage( return ( <> -

Loyalty system configuration

+

{t('loyalty.config.header')}

- +

dispatch( @@ -95,11 +97,13 @@ export default function LoyaltySettingPage(

- Give + {t('loyalty.config.give-points')}

- every + {t('loyalty.config.points-every')}

- - - + + +

- +

- Save + {t('actions.save')} ); diff --git a/frontend/src/ui/pages/loyalty/UserList.tsx b/frontend/src/ui/pages/loyalty/UserList.tsx index ff07626..7a92b78 100644 --- a/frontend/src/ui/pages/loyalty/UserList.tsx +++ b/frontend/src/ui/pages/loyalty/UserList.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { RouteComponentProps } from '@reach/router'; +import { useTranslation } from 'react-i18next'; import PageList from '../../components/PageList'; import { useModule, useUserPoints } from '../../../lib/react-utils'; import { RootState } from '../../../store'; @@ -33,6 +34,7 @@ function UserModal({ title, confirmText, }: UserModalProps) { + const { t } = useTranslation(); const currency = useSelector( (state: RootState) => state.api.moduleConfigs?.loyaltyConfig?.currency ?? 'points', @@ -69,7 +71,7 @@ function UserModal({ >

- +
@@ -120,8 +122,9 @@ export default function LoyaltyUserListPage( // eslint-disable-next-line @typescript-eslint/no-unused-vars props: RouteComponentProps, ): React.ReactElement { + const { t } = useTranslation(); const [loyaltyConfig] = useModule(modules.loyaltyConfig); - const currency = loyaltyConfig?.currency ?? 'points'; + const currency = loyaltyConfig?.currency ?? t('loyalty.points-fallback'); const users = useUserPoints(); const dispatch = useDispatch(); const [sorting, setSorting] = useState({ @@ -190,8 +193,8 @@ export default function LoyaltyUserListPage( return ( <> assignPoints(entry)} initialData={{ user: '', entry: { points: 0 } }} @@ -199,22 +202,24 @@ export default function LoyaltyUserListPage( /> {editModal ? ( modifyUser(entry)} initialData={editModal} onClose={() => setEditModal(null)} /> ) : null} -

All viewers with {currency}

+

+ {t('loyalty.userlist.userlist-header', { currency })} +

{users ? ( <>
setUsernameFilter(ev.target.value.toLowerCase()) @@ -223,7 +228,7 @@ export default function LoyaltyUserListPage(
changeSort('user')}> - Username + {t('form-common.username')} {sorting.key === 'user' ? ( {sorting.order === 'asc' ? '▴' : '▾'} @@ -279,7 +284,7 @@ export default function LoyaltyUserListPage( }) } > - Edit + {t('actions.edit')} @@ -296,9 +301,7 @@ export default function LoyaltyUserListPage( /> ) : ( -

- Viewer list is not available (loyalty disabled or no one has points) -

+

{t('loyalty.userlist.err-not-available')}

)} );