Add localization support for FollowModal component (#4558)

* Initial plan

* Add localization support for FollowModal component

Co-authored-by: gabek <414923+gabek@users.noreply.github.com>

* Update web/components/modals/FollowModal/FollowModal.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update web/components/modals/FollowModal/FollowForm.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: gabek <414923+gabek@users.noreply.github.com>
Co-authored-by: Gabe Kangas <gabek@real-ity.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Copilot
2025-09-15 21:50:36 -07:00
committed by GitHub
co-authored by gabek Copilot copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Gabe Kangas
parent aa6ab61cbc
commit 51ae467a5a
5 changed files with 92 additions and 13 deletions
@@ -1,8 +1,11 @@
/* eslint-disable react/no-unescaped-entities */ /* eslint-disable react/no-unescaped-entities */
import { Input, Button, Alert, Spin, Space } from 'antd';
import { FC, useState } from 'react'; import { FC, useState } from 'react';
import { Input, Button, Alert, Spin, Space } from 'antd';
import { useTranslation } from 'next-export-i18n';
import styles from './FollowModal.module.scss'; import styles from './FollowModal.module.scss';
import { isValidFediverseAccount } from '../../../utils/validators'; import { isValidFediverseAccount } from '../../../utils/validators';
import { Translation } from '../../ui/Translation/Translation';
import { Localization } from '../../../types/localization';
const ENDPOINT = '/api/remotefollow'; const ENDPOINT = '/api/remotefollow';
@@ -15,6 +18,7 @@ export const FollowForm: FC<FollowFormProps> = ({ handleClose }: FollowFormProps
const [valid, setValid] = useState(false); const [valid, setValid] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState(null); const [errorMessage, setErrorMessage] = useState(null);
const { t } = useTranslation();
const handleAccountChange = a => { const handleAccountChange = a => {
setRemoteAccount(a); setRemoteAccount(a);
@@ -55,7 +59,7 @@ export const FollowForm: FC<FollowFormProps> = ({ handleClose }: FollowFormProps
return; return;
} }
if (!result.redirectUrl) { if (!result.redirectUrl) {
setErrorMessage('Unable to follow.'); setErrorMessage(t(Localization.Frontend.FollowModal.unableToFollow) || 'Unable to follow.');
setLoading(false); setLoading(false);
return; return;
} }
@@ -69,7 +73,12 @@ export const FollowForm: FC<FollowFormProps> = ({ handleClose }: FollowFormProps
<Spin spinning={loading}> <Spin spinning={loading}>
{errorMessage && ( {errorMessage && (
<Alert <Alert
message="Follow Error" message={
<Translation
translationKey={Localization.Frontend.FollowModal.followError}
defaultText="Follow Error"
/>
}
description={errorMessage} description={errorMessage}
type="error" type="error"
closable closable
@@ -78,24 +87,41 @@ export const FollowForm: FC<FollowFormProps> = ({ handleClose }: FollowFormProps
)} )}
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<div className={styles.instructions}>Enter your username @server to follow</div> <div className={styles.instructions}>
<Translation
translationKey={Localization.Frontend.FollowModal.instructions}
defaultText="Enter your username @server to follow"
/>
</div>
<Input <Input
value={remoteAccount} value={remoteAccount}
size="large" size="large"
onChange={e => handleAccountChange(e.target.value)} onChange={e => handleAccountChange(e.target.value)}
placeholder="Your fediverse account @account@server" placeholder={
t(Localization.Frontend.FollowModal.placeholder) ||
'Your fediverse account @account@server'
}
defaultValue={remoteAccount} defaultValue={remoteAccount}
/> />
<div className={styles.footer}> <div className={styles.footer}>
You'll be redirected to your Fediverse server and asked to confirm the action. <Translation
translationKey={Localization.Frontend.FollowModal.redirectMessage}
defaultText="You'll be redirected to your Fediverse server and asked to confirm the action."
/>
</div> </div>
</div> </div>
<Space className={styles.buttons}> <Space className={styles.buttons}>
<Button onClick={joinButtonPressed} type="text"> <Button onClick={joinButtonPressed} type="text">
Join the Fediverse <Translation
translationKey={Localization.Frontend.FollowModal.joinFediverse}
defaultText="Join the Fediverse"
/>
</Button> </Button>
<Button disabled={!valid} type="primary" onClick={remoteFollowButtonPressed}> <Button disabled={!valid} type="primary" onClick={remoteFollowButtonPressed}>
Follow <Translation
translationKey={Localization.Frontend.FollowModal.followButton}
defaultText="Follow"
/>
</Button> </Button>
</Space> </Space>
</Spin> </Spin>
@@ -24,6 +24,9 @@ const meta = {
- Validate the input to make sure it's a valid looking account. - Validate the input to make sure it's a valid looking account.
- Handle errors that come back from the server. - Handle errors that come back from the server.
- Perform the redirect to the remote server when the backend response is received. - Perform the redirect to the remote server when the backend response is received.
## Localization Support
This component now supports localization and all user-facing strings can be translated. To test with different languages, add '?lang=de' or '?lang=fr' to the URL to see translated versions where available.
`, `,
}, },
}, },
@@ -37,3 +40,15 @@ const Template: StoryFn<typeof FollowModal> = () => <Example />;
export const Basic = { export const Basic = {
render: Template, render: Template,
}; };
export const LocalizationExample = {
render: Template,
parameters: {
docs: {
description: {
story:
'This story demonstrates the localization support. All text strings are now translatable. To test different languages, add "?lang=XX" to the URL with a supported language code like "de" for German.',
},
},
},
};
@@ -1,8 +1,10 @@
/* eslint-disable react/no-unescaped-entities */ /* eslint-disable react/no-unescaped-entities */
import { Space } from 'antd';
import { FC } from 'react'; import { FC } from 'react';
import { Space } from 'antd';
import styles from './FollowModal.module.scss'; import styles from './FollowModal.module.scss';
import { FollowForm } from './FollowForm'; import { FollowForm } from './FollowForm';
import { Translation } from '../../ui/Translation/Translation';
import { Localization } from '../../../types/localization';
export type FollowModalProps = { export type FollowModalProps = {
handleClose: () => void; handleClose: () => void;
@@ -13,12 +15,22 @@ export type FollowModalProps = {
export const FollowModal: FC<FollowModalProps> = ({ handleClose, account, name }) => ( export const FollowModal: FC<FollowModalProps> = ({ handleClose, account, name }) => (
<Space direction="vertical" id="follow-modal"> <Space direction="vertical" id="follow-modal">
<div className={styles.header}> <div className={styles.header}>
By following this stream you'll get notified on the Fediverse when it goes live. Now is a <Translation
great time to translationKey={Localization.Frontend.FollowModal.description}
defaultText="By following this stream you'll get notified on the Fediverse when it goes live. Now is a great time to"
/>
<a href="https://owncast.online/join-fediverse" target="_blank" rel="noreferrer"> <a href="https://owncast.online/join-fediverse" target="_blank" rel="noreferrer">
&nbsp;learn about the Fediverse&nbsp; &nbsp;
<Translation
translationKey={Localization.Frontend.FollowModal.learnFediverse}
defaultText="learn about the Fediverse"
/>
&nbsp;
</a> </a>
if it's new to you. <Translation
translationKey={Localization.Frontend.FollowModal.newToYou}
defaultText="if it's new to you."
/>
</div> </div>
<div className={styles.account}> <div className={styles.account}>
<img src="/logo" alt="logo" className={styles.logo} /> <img src="/logo" alt="logo" className={styles.logo} />
+12
View File
@@ -118,6 +118,18 @@
"contribute": "Contribute", "contribute": "Contribute",
"source": "Source" "source": "Source"
}, },
"FollowModal": {
"description": "By following this stream you'll get notified on the Fediverse when it goes live. Now is a great time to",
"learnFediverse": "learn about the Fediverse",
"newToYou": "if it's new to you.",
"instructions": "Enter your username @server to follow",
"placeholder": "Your fediverse account @account@server",
"redirectMessage": "You'll be redirected to your Fediverse server and asked to confirm the action.",
"joinFediverse": "Join the Fediverse",
"followButton": "Follow",
"followError": "Follow Error",
"unableToFollow": "Unable to follow."
},
"Header": { "Header": {
"skipToPlayer": "Skip to player", "skipToPlayer": "Skip to player",
"skipToOfflineMessage": "Skip to offline message", "skipToOfflineMessage": "Skip to offline message",
+14
View File
@@ -100,6 +100,20 @@ export const Localization = {
source: 'Frontend.Footer.source', source: 'Frontend.Footer.source',
}, },
// Follow modal component
FollowModal: {
description: 'Frontend.FollowModal.description',
learnFediverse: 'Frontend.FollowModal.learnFediverse',
newToYou: 'Frontend.FollowModal.newToYou',
instructions: 'Frontend.FollowModal.instructions',
placeholder: 'Frontend.FollowModal.placeholder',
redirectMessage: 'Frontend.FollowModal.redirectMessage',
joinFediverse: 'Frontend.FollowModal.joinFediverse',
followButton: 'Frontend.FollowModal.followButton',
followError: 'Frontend.FollowModal.followError',
unableToFollow: 'Frontend.FollowModal.unableToFollow',
},
// Offline banner messages // Offline banner messages
offlineBasic: 'Frontend.offlineBasic', offlineBasic: 'Frontend.offlineBasic',
offlineNotifyOnly: 'Frontend.offlineNotifyOnly', offlineNotifyOnly: 'Frontend.offlineNotifyOnly',