1
0
Fork 0
mirror of https://git.sr.ht/~ashkeel/strimertul synced 2024-09-18 01:50:50 +00:00

i18n is done hooray!

This commit is contained in:
Ash Keel 2021-09-18 20:32:24 +02:00
parent 5bcb55f0a2
commit de7c23f14b
No known key found for this signature in database
GPG key ID: BAD8D93E7314ED3E
8 changed files with 236 additions and 119 deletions

View file

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

View file

@ -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<ModalProps>): React.ReactElement {
const { t } = useTranslation();
return (
<div className={`modal ${active ? 'is-active' : ''}`}>
<div
@ -52,14 +54,14 @@ function Modal({
disabled={!confirmEnabled}
onClick={() => onConfirm()}
>
{confirmName ?? 'OK'}
{confirmName ?? t('actions.ok')}
</button>
{showCancel ? (
<button
className={`button ${cancelClass ?? ''}`}
onClick={() => onClose()}
>
{cancelName ?? 'Cancel'}
{cancelName ?? t('actions.cancel')}
</button>
) : null}
</footer>

View file

@ -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 (
<nav
className="pagination is-small"
@ -52,7 +54,7 @@ function PageList({
<li>
<button
className="button pagination-link"
aria-label={`Goto page ${min}`}
aria-label={t('system.pagination.gotopage', { page: min })}
onClick={() => onPageChange(min)}
>
{min}
@ -69,7 +71,9 @@ function PageList({
<li>
<button
className="button pagination-link"
aria-label={`Goto page ${current - 1}`}
aria-label={t('system.pagination.gotopage', {
page: current - 1,
})}
onClick={() => onPageChange(current - 1)}
>
{current - 1}
@ -79,7 +83,9 @@ function PageList({
<li>
<button
className="pagination-link is-current"
aria-label={`Page ${current}`}
aria-label={t('system.pagination.page', {
page: current,
})}
aria-current="page"
>
{current}
@ -89,7 +95,9 @@ function PageList({
<li>
<button
className="button pagination-link"
aria-label={`Goto page ${current + 1}`}
aria-label={t('system.pagination.gotopage', {
page: current + 1,
})}
onClick={() => onPageChange(current + 1)}
>
{current + 1}
@ -105,7 +113,9 @@ function PageList({
<li>
<button
className="button pagination-link"
aria-label={`Goto page ${max}`}
aria-label={t('system.pagination.gotopage', {
page: max,
})}
onClick={() => onPageChange(max)}
>
{max}

View file

@ -1,5 +1,6 @@
import { RouteComponentProps } from '@reach/router';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector, useDispatch } from 'react-redux';
import { useModule } from '../../../lib/react-utils';
import { RootState } from '../../../store';
@ -13,9 +14,11 @@ interface GoalItemProps {
onDelete: () => void;
}
function GoalItem({ item, onToggleState, onEdit, onDelete }: GoalItemProps) {
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';
@ -46,7 +49,7 @@ function GoalItem({ item, onToggleState, onEdit, onDelete }: GoalItemProps) {
}}
>
{item.contributed >= item.total ? (
<span className="goal-reached">Reached!</span>
<span className="goal-reached">{t('loyalty.goals.reached')}</span>
) : (
<>
{item.contributed} / {item.total} {currency}
@ -69,11 +72,11 @@ function GoalItem({ item, onToggleState, onEdit, onDelete }: GoalItemProps) {
<div className="contributors" style={{ marginTop: '1rem' }}>
{contributors.length > 0 ? (
<>
<b>Contributors:</b>
<b>{t('loyalty.goals.contributors')}</b>
<table className="table is-striped is-narrow">
<tr>
<th>Username</th>
<th>Points</th>
<th>{t('form-common.username')}</th>
<th>{t('loyalty.points')}</th>
</tr>
{contributors.map(([user, points]) => (
<tr>
@ -89,18 +92,18 @@ function GoalItem({ item, onToggleState, onEdit, onDelete }: GoalItemProps) {
</table>
</>
) : (
<b>No one has contributed yet :(</b>
<b>{t('loyalty.goals.no-contributors')}</b>
)}
</div>
<div style={{ marginTop: '1rem' }}>
<a className="button is-small" onClick={onToggleState}>
{item.enabled ? 'Disable' : 'Enable'}
{item.enabled ? t('actions.disable') : t('actions.enable')}
</a>{' '}
<a className="button is-small" onClick={onEdit}>
Edit
{t('actions.edit')}
</a>{' '}
<a className="button is-small" onClick={onDelete}>
Delete
{t('actions.delete')}
</a>
</div>
</div>
@ -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({
>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">Goal ID</label>
<label className="label">{t('loyalty.goals.id')}</label>
</div>
<div className="field-body">
<div className="field">
@ -183,29 +188,24 @@ function GoalModal({
<input
className={idInvalid ? 'input is-danger' : 'input'}
type="text"
placeholder="goal_id_here"
placeholder={t('loyalty.goals.id-placeholder')}
value={slug}
onChange={(ev) => setIDex(ev.target.value)}
/>
</p>
{idInvalid ? (
<p className="help is-danger">
There is already a goal with this ID! Please choose a different
one.
{t('loyalty.goals.err-goalid-dup')}
</p>
) : (
<p className="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.
</p>
<p className="help">{t('loyalty.goals.id-help')}</p>
)}
</div>
</div>
</div>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">Name</label>
<label className="label">{t('loyalty.goals.name')}</label>
</div>
<div className="field-body">
<div className="field">
@ -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({
</div>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">Icon</label>
<label className="label">{t('loyalty.goals.icon')}</label>
</div>
<div className="field-body">
<div className="field">
@ -232,7 +232,7 @@ function GoalModal({
<input
className="input"
type="text"
placeholder="Image URL"
placeholder={t('loyalty.goals.icon-placeholder')}
value={image ?? ''}
onChange={(ev) => setImage(ev.target.value)}
/>
@ -242,14 +242,14 @@ function GoalModal({
</div>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">Description</label>
<label className="label">{t('loyalty.goals.description')}</label>
</div>
<div className="field-body">
<div className="field">
<p className="control">
<textarea
className="textarea"
placeholder="What's gonna happen when we reach this goal?"
placeholder={t('loyalty.goals.description-placeholder')}
onChange={(ev) => setDescription(ev.target.value)}
value={description}
></textarea>
@ -259,7 +259,7 @@ function GoalModal({
</div>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">Required</label>
<label className="label">{t('form-common.required')}</label>
</div>
<div className="field-body">
<div className="field has-addons">
@ -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 (
<>
<h1 className="title is-4">Community goals</h1>
<h1 className="title is-4">{t('loyalty.goals.header')}</h1>
<div className="field is-grouped">
<p className="control">
@ -345,7 +346,7 @@ export default function LoyaltyGoalsPage(
disabled={!active}
onClick={() => setCreateModal(true)}
>
New goal
{t('loyalty.goals.new')}
</button>
</p>
@ -353,7 +354,7 @@ export default function LoyaltyGoalsPage(
<input
className="input"
type="text"
placeholder="Search by name"
placeholder={t('loyalty.goals.search')}
value={goalFilter}
onChange={(ev) => setGoalFilter(ev.target.value)}
/>
@ -361,16 +362,16 @@ export default function LoyaltyGoalsPage(
</div>
<GoalModal
title="New goal"
confirmText="Create"
title={t('loyalty.goals.new')}
confirmText={t('actions.create')}
active={createModal}
onConfirm={createGoal}
onClose={() => setCreateModal(false)}
/>
{showModifyGoal ? (
<GoalModal
title="Modify goal"
confirmText="Edit"
title={t('loyalty.goals.modify')}
confirmText={t('actions.edit')}
active={true}
onConfirm={(goal) => modifyGoal(showModifyGoal.id, goal)}
initialData={showModifyGoal}

View file

@ -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 (
<>
<h1 className="title is-4">Redemption queue</h1>
<h1 className="title is-4">{t('loyalty.queue.header')}</h1>
{redemptions ? (
<>
<div className="field">
<input
className="input is-small"
type="text"
placeholder="Search by username"
placeholder={t('loyalty.queue.search')}
value={usernameFilter}
onChange={(ev) =>
setUsernameFilter(ev.target.value.toLowerCase())
@ -132,7 +134,7 @@ export default function LoyaltyRedeemQueuePage(
<tr>
<th style={{ width: '20%' }}>
<span className="sortable" onClick={() => changeSort('when')}>
Date
{t('form-common.date')}
{sorting.key === 'when' ? (
<span className="sort-icon">
{sorting.order === 'asc' ? '▴' : '▾'}
@ -142,7 +144,7 @@ export default function LoyaltyRedeemQueuePage(
</th>
<th>
<span className="sortable" onClick={() => changeSort('user')}>
Username
{t('form-common.username')}
{sorting.key === 'user' ? (
<span className="sort-icon">
{sorting.order === 'asc' ? '▴' : '▾'}
@ -150,8 +152,8 @@ export default function LoyaltyRedeemQueuePage(
) : null}
</span>
</th>
<th>Reward name</th>
<th>Request</th>
<th>{t('loyalty.queue.reward-name')}</th>
<th>{t('loyalty.queue.request')}</th>
<th></th>
</tr>
</thead>
@ -168,11 +170,15 @@ export default function LoyaltyRedeemQueuePage(
<td>{redemption.reward.name}</td>
<td>{redemption.request_text}</td>
<td style={{ textAlign: 'right' }}>
<a onClick={() => acceptRedeem(redemption)}>Accept</a>
<a onClick={() => acceptRedeem(redemption)}>
{t('loyalty.queue.accept')}
</a>
{redemption.username !== '@PLATFORM' ? (
<>
{' 🞄 '}
<a onClick={() => refundRedeem(redemption)}>Refund</a>
<a onClick={() => refundRedeem(redemption)}>
{t('loyalty.queue.refund')}
</a>
</>
) : null}
</td>
@ -190,10 +196,7 @@ export default function LoyaltyRedeemQueuePage(
/>
</>
) : (
<p>
Redemption queue is not available (loyalty disabled or no one has
redeemed anything yet)
</p>
<p>{t('loyalty.queue.err-not-available')}</p>
)}
</>
);

View file

@ -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 ? (
<div style={{ marginTop: '1rem' }}>
<b>Cooldown:</b> {prettyTime(item.cooldown * 1000)}
<b>{t('loyalty.rewards.cooldown')}:</b>{' '}
{prettyTime(item.cooldown * 1000)}
</div>
) : null}
{item.required_info ? (
<div style={{ marginTop: '1rem' }}>
<b>Required info:</b> {item.required_info}
<b>{t('loyalty.rewards.required-info')}:</b> {item.required_info}
</div>
) : null}
<div style={{ marginTop: '1rem' }}>
<a className="button is-small" onClick={onTest}>
Test
{t('loyalty.rewards.test')}
</a>{' '}
<a className="button is-small" onClick={onToggleState}>
{item.enabled ? 'Disable' : 'Enable'}
</a>{' '}
<a className="button is-small" onClick={onEdit}>
Edit
{t('actions.edit')}
</a>{' '}
<a className="button is-small" onClick={onDelete}>
Delete
{t('actions.delete')}
</a>
</div>
</div>
@ -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({
>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">Reward ID</label>
<label className="label">{t('loyalty.rewards.id')}</label>
</div>
<div className="field-body">
<div className="field">
@ -193,29 +198,24 @@ function RewardModal({
<input
className={idInvalid ? 'input is-danger' : 'input'}
type="text"
placeholder="reward_id_here"
placeholder={t('loyalty.rewards.id-placeholder')}
value={slug}
onChange={(ev) => setIDex(ev.target.value)}
/>
</p>
{idInvalid ? (
<p className="help is-danger">
There is already a reward with this ID! Please choose a
different one.
{t('loyalty.rewards.err-rewid-dup')}
</p>
) : (
<p className="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.
</p>
<p className="help">{t('loyalty.rewards.id-help')}</p>
)}
</div>
</div>
</div>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">Name</label>
<label className="label">{t('loyalty.rewards.name')}</label>
</div>
<div className="field-body">
<div className="field">
@ -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({
</div>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">Icon</label>
<label className="label">{t('loyalty.rewards.icon')}</label>
</div>
<div className="field-body">
<div className="field">
@ -242,7 +242,7 @@ function RewardModal({
<input
className="input"
type="text"
placeholder="Image URL"
placeholder={t('loyalty.rewards.icon-placeholder')}
value={image ?? ''}
onChange={(ev) => setImage(ev.target.value)}
/>
@ -252,14 +252,14 @@ function RewardModal({
</div>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">Description</label>
<label className="label">{t('loyalty.rewards.description')}</label>
</div>
<div className="field-body">
<div className="field">
<p className="control">
<textarea
className="textarea"
placeholder="What's so cool about this reward?"
placeholder={t('loyalty.rewards.description-placeholder')}
onChange={(ev) => setDescription(ev.target.value)}
value={description}
></textarea>
@ -269,7 +269,7 @@ function RewardModal({
</div>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">Cost</label>
<label className="label">{t('loyalty.rewards.cost')}</label>
</div>
<div className="field-body">
<div className="field has-addons">
@ -298,7 +298,7 @@ function RewardModal({
checked={extraRequired}
onChange={(ev) => setExtraRequired(ev.target.checked)}
/>{' '}
Requires viewer-specified details
{t('loyalty.rewards.requires-extra-info')}
</label>
</div>
</div>
@ -307,7 +307,7 @@ function RewardModal({
<>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">Required info</label>
<label className="label">{t('loyalty.rewards.extra-info')}</label>
</div>
<div className="field-body">
<div className="field">
@ -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}
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">Cooldown</label>
<label className="label">{t('loyalty.rewards.cooldown')}</label>
</div>
<div className="field-body">
<div className="field has-addons">
@ -361,9 +361,9 @@ function RewardModal({
setTempCooldownMult(intMult);
}}
>
<option value="1">seconds</option>
<option value="60">minutes</option>
<option value="3600">hours</option>
<option value="1">{t('form-common.time.seconds')}</option>
<option value="60">{t('form-common.time.minutes')}</option>
<option value="3600">{t('form-common.time.hours')}</option>
</select>
</span>
</p>
@ -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 (
<>
<h1 className="title is-4">Loyalty rewards</h1>
<h1 className="title is-4">{t('loyalty.rewards.header')}</h1>
<div className="field is-grouped">
<p className="control">
@ -449,7 +450,7 @@ export default function LoyaltyRewardsPage(
disabled={!active}
onClick={() => setCreateModal(true)}
>
New reward
{t('loyalty.rewards.new-reward')}
</button>
</p>
@ -457,7 +458,7 @@ export default function LoyaltyRewardsPage(
<input
className="input"
type="text"
placeholder="Search by name"
placeholder={t('loyalty.rewards.search')}
value={rewardFilter}
onChange={(ev) => setRewardFilter(ev.target.value)}
/>
@ -465,16 +466,16 @@ export default function LoyaltyRewardsPage(
</div>
<RewardModal
title="New reward"
confirmText="Create"
title={t('loyalty.rewards.new-reward')}
confirmText={t('actions.create')}
active={createModal}
onConfirm={createReward}
onClose={() => setCreateModal(false)}
/>
{showModifyReward ? (
<RewardModal
title="Modify reward"
confirmText="Edit"
title={t('loyalty.rewards.modify-reward')}
confirmText={t('actions.edit')}
active={true}
onConfirm={(reward) => modifyReward(showModifyReward.id, reward)}
initialData={showModifyReward}

View file

@ -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 (
<>
<h1 className="title is-4">Loyalty system configuration</h1>
<h1 className="title is-4">{t('loyalty.config.header')}</h1>
<div className="field">
<label className="checkbox">
<input
@ -67,21 +69,21 @@ export default function LoyaltySettingPage(
}),
)
}
/>{' '}
Enable loyalty points{' '}
/>
{` ${t('loyalty.config.enable')} `}
{twitchActive && twitchBotActive
? ''
: '(Twitch bot must be enabled for this!)'}
: t('loyalty.config.err-twitchbot-disabled')}
</label>
</div>
<div className="field">
<label className="label">Currency name</label>
<label className="label">{t('loyalty.config.currency-name')}</label>
<p className="control">
<input
disabled={!active}
className="input"
type="text"
placeholder="points"
placeholder={t('loyalty.points-fallback')}
value={loyaltyConfig?.currency ?? ''}
onChange={(ev) =>
dispatch(
@ -95,11 +97,13 @@ export default function LoyaltySettingPage(
</p>
</div>
<label className="label">
How often to give {loyaltyConfig?.currency || 'points'}
{t('loyalty.config.point-reward-frequency', {
points: loyaltyConfig?.currency || t('loyalty.points-fallback'),
})}
</label>
<div className="field has-addons" style={{ marginBottom: 0 }}>
<p className="control">
<a className="button is-static">Give</a>
<a className="button is-static">{t('loyalty.config.give-points')}</a>
</p>
<p className="control">
<input
@ -126,7 +130,7 @@ export default function LoyaltySettingPage(
/>
</p>
<p className="control">
<a className="button is-static">every</a>
<a className="button is-static">{t('loyalty.config.points-every')}</a>
</p>
<p className="control">
<input
@ -157,15 +161,15 @@ export default function LoyaltySettingPage(
setTempIntervalMult(intMult);
}}
>
<option value="1">seconds</option>
<option value="60">minutes</option>
<option value="3600">hours</option>
<option value="1">{t('form-common.time.seconds')}</option>
<option value="60">{t('form-common.time.minutes')}</option>
<option value="3600">{t('form-common.time.hours')}</option>
</select>
</span>
</p>
</div>
<div className="field">
<label className="label">Bonus points for active users</label>
<label className="label">{t('loyalty.config.bonus-points')}</label>
<p className="control">
<input
disabled={!active}
@ -197,7 +201,7 @@ export default function LoyaltySettingPage(
dispatch(setLoyaltyConfig(loyaltyConfig));
}}
>
Save
{t('actions.save')}
</button>
</>
);

View file

@ -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({
>
<div className="field is-horizontal">
<div className="field-label is-normal">
<label className="label">Username</label>
<label className="label">{t('form-common.username')}</label>
</div>
<div className="field-body">
<div className="field">
@ -120,8 +122,9 @@ export default function LoyaltyUserListPage(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
props: RouteComponentProps<unknown>,
): 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<SortingOrder>({
@ -190,8 +193,8 @@ export default function LoyaltyUserListPage(
return (
<>
<UserModal
title="Give points to user"
confirmText="Give"
title={t('loyalty.userlist.give-points')}
confirmText={t('loyalty.userlist.give-button')}
active={createModal}
onConfirm={(entry) => assignPoints(entry)}
initialData={{ user: '', entry: { points: 0 } }}
@ -199,22 +202,24 @@ export default function LoyaltyUserListPage(
/>
{editModal ? (
<UserModal
title="Modify balance"
confirmText="Edit"
title={t('loyalty.userlist.modify-balance')}
confirmText={t('actions.edit')}
active={true}
onConfirm={(entry) => modifyUser(entry)}
initialData={editModal}
onClose={() => setEditModal(null)}
/>
) : null}
<h1 className="title is-4">All viewers with {currency}</h1>
<h1 className="title is-4">
{t('loyalty.userlist.userlist-header', { currency })}
</h1>
{users ? (
<>
<div className="field">
<input
className="input is-small"
type="text"
placeholder="Search by username"
placeholder={t('loyalty.queue.search')}
value={usernameFilter}
onChange={(ev) =>
setUsernameFilter(ev.target.value.toLowerCase())
@ -223,7 +228,7 @@ export default function LoyaltyUserListPage(
</div>
<div className="field">
<a className="button is-small" onClick={() => setCreateModal(true)}>
Give points to user
{t('loyalty.userlist.give-points')}
</a>
</div>
<PageList
@ -239,7 +244,7 @@ export default function LoyaltyUserListPage(
<tr>
<th>
<span className="sortable" onClick={() => changeSort('user')}>
Username
{t('form-common.username')}
{sorting.key === 'user' ? (
<span className="sort-icon">
{sorting.order === 'asc' ? '▴' : '▾'}
@ -279,7 +284,7 @@ export default function LoyaltyUserListPage(
})
}
>
Edit
{t('actions.edit')}
</a>
</td>
</tr>
@ -296,9 +301,7 @@ export default function LoyaltyUserListPage(
/>
</>
) : (
<p>
Viewer list is not available (loyalty disabled or no one has points)
</p>
<p>{t('loyalty.userlist.err-not-available')}</p>
)}
</>
);