Extracting translation strings

This commit is contained in:
Philipp Heckel 2022-04-07 21:46:33 -04:00
parent 0ec9a4c89b
commit 893701c07b
5 changed files with 101 additions and 49 deletions

View file

@ -1,3 +1,8 @@
{ {
"nav_topics_title": "Abonnierte Themen",
"nav_button_all_notifications": "Alle Benachrichtigungen",
"nav_button_settings": "Einstellungen",
"nav_button_documentation": "Dokumentation",
"nav_button_publish_message": "Nachricht senden",
"nav_button_subscribe": "Thema abonnieren" "nav_button_subscribe": "Thema abonnieren"
} }

View file

@ -1,3 +1,35 @@
{ {
"nav_button_subscribe": "Subscribe to topic" "nav_topics_title": "Subscribed topics",
"nav_button_all_notifications": "All notifications",
"nav_button_settings": "Settings",
"nav_button_documentation": "Documentation",
"nav_button_publish_message": "Publish message",
"nav_button_subscribe": "Subscribe to topic",
"alert_grant_title": "Notifications are disabled",
"alert_grant_description": "Grant your browser permission to display desktop notifications.",
"alert_grant_button": "Grant now",
"alert_not_supported_title": "Notifications not supported",
"alert_not_supported_description": "Notifications are not supported in your browser.",
"notifications_copied_to_clipboard": "Copied to clipboard",
"notifications_tags": "Tags",
"notifications_attachment_copy_url_title": "Copy attachment URL to clipboard",
"notifications_attachment_copy_url_button": "Copy URL",
"notifications_attachment_open_title": "Go to {{url}}",
"notifications_attachment_open_button": "Open attachment",
"notifications_attachment_link_expires": "link expires {{date}}",
"notifications_attachment_link_expired": "download link expired",
"notifications_click_copy_url_title": "Copy link URL to clipboard",
"notifications_click_copy_url_button": "Copy link",
"notifications_click_open_title": "Go to {{url}}",
"notifications_click_open_button": "Open link",
"notifications_none_for_topic_title": "You haven't received any notifications for this topic yet.",
"notifications_none_for_topic_description": "To send notifications to this topic, simply PUT or POST to the topic URL.",
"notifications_none_for_any_title": "You haven't received any notifications.",
"notifications_none_for_any_description": "To send notifications to a topic, simply PUT or POST to the topic URL. Here's an example using one of your topics.",
"notifications_no_subscriptions_title": "It looks like you don't have any subscriptions yet.",
"notifications_no_subscriptions_description": "Click the \"Add subscription\" link to create or subscribe to a topic. After that, you can send messages via PUT or POST and you'll receive notifications here.",
"notifications_example": "Example",
"notifications_more_details": "For more information, check out the <websiteLink>website</websiteLink> or <docsLink>documentation</docsLink>.",
"notifications_loading": "Loading notifications ...",
"emoji_picker_search_placeholder": "Search emoji"
} }

View file

@ -9,6 +9,7 @@ import IconButton from "@mui/material/IconButton";
import {Close} from "@mui/icons-material"; import {Close} from "@mui/icons-material";
import Popper from "@mui/material/Popper"; import Popper from "@mui/material/Popper";
import {splitNoEmpty} from "../app/utils"; import {splitNoEmpty} from "../app/utils";
import {useTranslation} from "react-i18next";
// Create emoji list by category and create a search base (string with all search words) // Create emoji list by category and create a search base (string with all search words)
// //
@ -36,6 +37,7 @@ rawEmojis.forEach(emoji => {
}); });
const EmojiPicker = (props) => { const EmojiPicker = (props) => {
const { t } = useTranslation();
const open = Boolean(props.anchorEl); const open = Boolean(props.anchorEl);
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const searchRef = useRef(null); const searchRef = useRef(null);
@ -71,7 +73,7 @@ const EmojiPicker = (props) => {
inputRef={searchRef} inputRef={searchRef}
margin="dense" margin="dense"
size="small" size="small"
placeholder="Search emoji" placeholder={t("emoji_picker_search_placeholder")}
value={search} value={search}
onChange={ev => setSearch(ev.target.value)} onChange={ev => setSearch(ev.target.value)}
type="text" type="text"

View file

@ -97,14 +97,14 @@ const NavList = (props) => {
{!showSubscriptionsList && {!showSubscriptionsList &&
<ListItemButton onClick={() => navigate(routes.root)} selected={location.pathname === config.appRoot}> <ListItemButton onClick={() => navigate(routes.root)} selected={location.pathname === config.appRoot}>
<ListItemIcon><ChatBubble/></ListItemIcon> <ListItemIcon><ChatBubble/></ListItemIcon>
<ListItemText primary="All notifications"/> <ListItemText primary={t("nav_button_all_notifications")}/>
</ListItemButton>} </ListItemButton>}
{showSubscriptionsList && {showSubscriptionsList &&
<> <>
<ListSubheader>Subscribed topics</ListSubheader> <ListSubheader>{t("nav_topics_title")}</ListSubheader>
<ListItemButton onClick={() => navigate(routes.root)} selected={location.pathname === config.appRoot}> <ListItemButton onClick={() => navigate(routes.root)} selected={location.pathname === config.appRoot}>
<ListItemIcon><ChatBubble/></ListItemIcon> <ListItemIcon><ChatBubble/></ListItemIcon>
<ListItemText primary="All notifications"/> <ListItemText primary={t("nav_button_all_notifications")}/>
</ListItemButton> </ListItemButton>
<SubscriptionList <SubscriptionList
subscriptions={props.subscriptions} subscriptions={props.subscriptions}
@ -114,15 +114,15 @@ const NavList = (props) => {
</>} </>}
<ListItemButton onClick={() => navigate(routes.settings)} selected={location.pathname === routes.settings}> <ListItemButton onClick={() => navigate(routes.settings)} selected={location.pathname === routes.settings}>
<ListItemIcon><SettingsIcon/></ListItemIcon> <ListItemIcon><SettingsIcon/></ListItemIcon>
<ListItemText primary="Settings"/> <ListItemText primary={t("nav_button_settings")}/>
</ListItemButton> </ListItemButton>
<ListItemButton onClick={() => openUrl("/docs")}> <ListItemButton onClick={() => openUrl("/docs")}>
<ListItemIcon><ArticleIcon/></ListItemIcon> <ListItemIcon><ArticleIcon/></ListItemIcon>
<ListItemText primary="Documentation"/> <ListItemText primary={t("nav_button_documentation")}/>
</ListItemButton> </ListItemButton>
<ListItemButton onClick={() => props.onPublishMessageClick()}> <ListItemButton onClick={() => props.onPublishMessageClick()}>
<ListItemIcon><Send/></ListItemIcon> <ListItemIcon><Send/></ListItemIcon>
<ListItemText primary="Publish message"/> <ListItemText primary={t("nav_button_publish_message")}/>
</ListItemButton> </ListItemButton>
<ListItemButton onClick={() => setSubscribeDialogOpen(true)}> <ListItemButton onClick={() => setSubscribeDialogOpen(true)}>
<ListItemIcon><AddIcon/></ListItemIcon> <ListItemIcon><AddIcon/></ListItemIcon>
@ -181,20 +181,19 @@ const SubscriptionItem = (props) => {
}; };
const NotificationGrantAlert = (props) => { const NotificationGrantAlert = (props) => {
const { t } = useTranslation();
return ( return (
<> <>
<Alert severity="warning" sx={{paddingTop: 2}}> <Alert severity="warning" sx={{paddingTop: 2}}>
<AlertTitle>Notifications are disabled</AlertTitle> <AlertTitle>{t("alert_grant_title")}</AlertTitle>
<Typography gutterBottom> <Typography gutterBottom>{t("alert_grant_description")}</Typography>
Grant your browser permission to display desktop notifications.
</Typography>
<Button <Button
sx={{float: 'right'}} sx={{float: 'right'}}
color="inherit" color="inherit"
size="small" size="small"
onClick={props.onRequestPermissionClick} onClick={props.onRequestPermissionClick}
> >
Grant now {t("alert_grant_button")}
</Button> </Button>
</Alert> </Alert>
<Divider/> <Divider/>
@ -203,13 +202,12 @@ const NotificationGrantAlert = (props) => {
}; };
const NotificationNotSupportedAlert = () => { const NotificationNotSupportedAlert = () => {
const { t } = useTranslation();
return ( return (
<> <>
<Alert severity="warning" sx={{paddingTop: 2}}> <Alert severity="warning" sx={{paddingTop: 2}}>
<AlertTitle>Notifications not supported</AlertTitle> <AlertTitle>{t("alert_not_supported_title")}</AlertTitle>
<Typography gutterBottom> <Typography gutterBottom>{t("alert_not_supported_description")}</Typography>
Notifications are not supported in your browser.
</Typography>
</Alert> </Alert>
<Divider/> <Divider/>
</> </>

View file

@ -39,6 +39,7 @@ import priority4 from "../img/priority-4.svg";
import priority5 from "../img/priority-5.svg"; import priority5 from "../img/priority-5.svg";
import logoOutline from "../img/ntfy-outline.svg"; import logoOutline from "../img/ntfy-outline.svg";
import AttachmentIcon from "./AttachmentIcon"; import AttachmentIcon from "./AttachmentIcon";
import {Trans, useTranslation} from "react-i18next";
const Notifications = (props) => { const Notifications = (props) => {
if (props.mode === "all") { if (props.mode === "all") {
@ -72,6 +73,7 @@ const SingleSubscription = (props) => {
} }
const NotificationList = (props) => { const NotificationList = (props) => {
const { t } = useTranslation();
const pageSize = 20; const pageSize = 20;
const notifications = props.notifications; const notifications = props.notifications;
const [snackOpen, setSnackOpen] = useState(false); const [snackOpen, setSnackOpen] = useState(false);
@ -112,7 +114,7 @@ const NotificationList = (props) => {
open={snackOpen} open={snackOpen}
autoHideDuration={3000} autoHideDuration={3000}
onClose={() => setSnackOpen(false)} onClose={() => setSnackOpen(false)}
message="Copied to clipboard" message={t("notifications_copied_to_clipboard")}
/> />
</Stack> </Stack>
</Container> </Container>
@ -121,6 +123,7 @@ const NotificationList = (props) => {
} }
const NotificationItem = (props) => { const NotificationItem = (props) => {
const { t } = useTranslation();
const notification = props.notification; const notification = props.notification;
const attachment = notification.attachment; const attachment = notification.attachment;
const date = formatShortDateTime(notification.time); const date = formatShortDateTime(notification.time);
@ -160,24 +163,24 @@ const NotificationItem = (props) => {
{notification.title && <Typography variant="h5" component="div">{formatTitle(notification)}</Typography>} {notification.title && <Typography variant="h5" component="div">{formatTitle(notification)}</Typography>}
<Typography variant="body1" sx={{ whiteSpace: 'pre-line' }}>{autolink(formatMessage(notification))}</Typography> <Typography variant="body1" sx={{ whiteSpace: 'pre-line' }}>{autolink(formatMessage(notification))}</Typography>
{attachment && <Attachment attachment={attachment}/>} {attachment && <Attachment attachment={attachment}/>}
{tags && <Typography sx={{ fontSize: 14 }} color="text.secondary">Tags: {tags}</Typography>} {tags && <Typography sx={{ fontSize: 14 }} color="text.secondary">{t("notifications_tags")}: {tags}</Typography>}
</CardContent> </CardContent>
{showActions && {showActions &&
<CardActions sx={{paddingTop: 0}}> <CardActions sx={{paddingTop: 0}}>
{showAttachmentActions && <> {showAttachmentActions && <>
<Tooltip title="Copy attachment URL to clipboard"> <Tooltip title={t("notifications_attachment_copy_url_title")}>
<Button onClick={() => handleCopy(attachment.url)}>Copy URL</Button> <Button onClick={() => handleCopy(attachment.url)}>{t("notifications_attachment_copy_url_button")}</Button>
</Tooltip> </Tooltip>
<Tooltip title={`Go to ${attachment.url}`}> <Tooltip title={t("notifications_attachment_open_title", { url: attachment.url })}>
<Button onClick={() => openUrl(attachment.url)}>Open attachment</Button> <Button onClick={() => openUrl(attachment.url)}>{t("notifications_attachment_open_button")}</Button>
</Tooltip> </Tooltip>
</>} </>}
{showClickAction && <> {showClickAction && <>
<Tooltip title="Copy link URL to clipboard"> <Tooltip title={t("notifications_click_copy_url_title")}>
<Button onClick={() => handleCopy(notification.click)}>Copy link</Button> <Button onClick={() => handleCopy(notification.click)}>{t("notifications_click_copy_url_button")}</Button>
</Tooltip> </Tooltip>
<Tooltip title={`Go to ${notification.click}`}> <Tooltip title={t("notifications_click_open_title", { url: notification.click })}>
<Button onClick={() => openUrl(notification.click)}>Open link</Button> <Button onClick={() => openUrl(notification.click)}>{t("notifications_click_open_button")}</Button>
</Tooltip> </Tooltip>
</>} </>}
</CardActions>} </CardActions>}
@ -208,6 +211,7 @@ const priorityFiles = {
}; };
const Attachment = (props) => { const Attachment = (props) => {
const { t } = useTranslation();
const attachment = props.attachment; const attachment = props.attachment;
const expired = attachment.expires && attachment.expires < Date.now()/1000; const expired = attachment.expires && attachment.expires < Date.now()/1000;
const expires = attachment.expires && attachment.expires > Date.now()/1000; const expires = attachment.expires && attachment.expires > Date.now()/1000;
@ -224,10 +228,10 @@ const Attachment = (props) => {
infos.push(formatBytes(attachment.size)); infos.push(formatBytes(attachment.size));
} }
if (expires) { if (expires) {
infos.push(`link expires ${formatShortDateTime(attachment.expires)}`); infos.push(t("notifications_attachment_link_expires", { date: formatShortDateTime(attachment.expires) }));
} }
if (expired) { if (expired) {
infos.push(`download link expired`); infos.push(t("notifications_attachment_link_expired"));
} }
const maybeInfoText = (infos.length > 0) ? <><br/>{infos.join(", ")}</> : null; const maybeInfoText = (infos.length > 0) ? <><br/>{infos.join(", ")}</> : null;
@ -326,82 +330,93 @@ const Image = (props) => {
} }
const NoNotifications = (props) => { const NoNotifications = (props) => {
const { t } = useTranslation();
const shortUrl = topicShortUrl(props.subscription.baseUrl, props.subscription.topic); const shortUrl = topicShortUrl(props.subscription.baseUrl, props.subscription.topic);
return ( return (
<VerticallyCenteredContainer maxWidth="xs"> <VerticallyCenteredContainer maxWidth="xs">
<Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}> <Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
<img src={logoOutline} height="64" width="64" alt="No notifications"/><br /> <img src={logoOutline} height="64" width="64"/><br />
You haven't received any notifications for this topic yet. {t("notifications_none_for_topic_title")}
</Typography> </Typography>
<Paragraph> <Paragraph>
To send notifications to this topic, simply PUT or POST to the topic URL. {t("notifications_none_for_topic_description")}
</Paragraph> </Paragraph>
<Paragraph> <Paragraph>
Example:<br/> {t("notifications_example")}:<br/>
<tt> <tt>
$ curl -d "Hi" {shortUrl} $ curl -d "Hi" {shortUrl}
</tt> </tt>
</Paragraph> </Paragraph>
<Paragraph> <Paragraph>
For more detailed instructions, check out the <Link href="https://ntfy.sh" target="_blank" rel="noopener">website</Link> or <ForMoreDetails/>
{" "}<Link href="https://ntfy.sh/docs" target="_blank" rel="noopener">documentation</Link>.
</Paragraph> </Paragraph>
</VerticallyCenteredContainer> </VerticallyCenteredContainer>
); );
}; };
const NoNotificationsWithoutSubscription = (props) => { const NoNotificationsWithoutSubscription = (props) => {
const { t } = useTranslation();
const subscription = props.subscriptions[0]; const subscription = props.subscriptions[0];
const shortUrl = topicShortUrl(subscription.baseUrl, subscription.topic); const shortUrl = topicShortUrl(subscription.baseUrl, subscription.topic);
return ( return (
<VerticallyCenteredContainer maxWidth="xs"> <VerticallyCenteredContainer maxWidth="xs">
<Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}> <Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
<img src={logoOutline} height="64" width="64" alt="No notifications"/><br /> <img src={logoOutline} height="64" width="64"/><br />
You haven't received any notifications. {t("notifications_none_for_any_title")}
</Typography> </Typography>
<Paragraph> <Paragraph>
To send notifications to a topic, simply PUT or POST to the topic URL. Here's {t("notifications_none_for_any_description")}
an example using one of your topics.
</Paragraph> </Paragraph>
<Paragraph> <Paragraph>
Example:<br/> {t("notifications_example")}:<br/>
<tt> <tt>
$ curl -d "Hi" {shortUrl} $ curl -d "Hi" {shortUrl}
</tt> </tt>
</Paragraph> </Paragraph>
<Paragraph> <Paragraph>
For more detailed instructions, check out the <Link href="https://ntfy.sh" target="_blank" rel="noopener">website</Link> or <ForMoreDetails/>
{" "}<Link href="https://ntfy.sh/docs" target="_blank" rel="noopener">documentation</Link>.
</Paragraph> </Paragraph>
</VerticallyCenteredContainer> </VerticallyCenteredContainer>
); );
}; };
const NoSubscriptions = () => { const NoSubscriptions = () => {
const { t } = useTranslation();
return ( return (
<VerticallyCenteredContainer maxWidth="xs"> <VerticallyCenteredContainer maxWidth="xs">
<Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}> <Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
<img src={logoOutline} height="64" width="64" alt="No topics"/><br /> <img src={logoOutline} height="64" width="64"/><br />
It looks like you don't have any subscriptions yet. {t("notifications_no_subscriptions_title")}
</Typography> </Typography>
<Paragraph> <Paragraph>
Click the "Add subscription" link to create or subscribe to a topic. After that, you can send messages {t("notifications_no_subscriptions_description")}
via PUT or POST and you'll receive notifications here.
</Paragraph> </Paragraph>
<Paragraph> <Paragraph>
For more information, check out the <Link href="https://ntfy.sh" target="_blank" rel="noopener">website</Link> or <ForMoreDetails/>
{" "}<Link href="https://ntfy.sh/docs" target="_blank" rel="noopener">documentation</Link>.
</Paragraph> </Paragraph>
</VerticallyCenteredContainer> </VerticallyCenteredContainer>
); );
}; };
const ForMoreDetails = () => {
return (
<Trans
i18nKey="notifications_more_details"
components={{
websiteLink: <Link href="https://ntfy.sh" target="_blank" rel="noopener"/>,
docsLink: <Link href="https://ntfy.sh/docs" target="_blank" rel="noopener"/>
}}
/>
);
};
const Loading = () => { const Loading = () => {
const { t } = useTranslation();
return ( return (
<VerticallyCenteredContainer> <VerticallyCenteredContainer>
<Typography variant="h5" color="text.secondary" align="center" sx={{ paddingBottom: 1 }}> <Typography variant="h5" color="text.secondary" align="center" sx={{ paddingBottom: 1 }}>
<CircularProgress disableShrink sx={{marginBottom: 1}}/><br /> <CircularProgress disableShrink sx={{marginBottom: 1}}/><br />
Loading notifications ... {t("notifications_loading")}
</Typography> </Typography>
</VerticallyCenteredContainer> </VerticallyCenteredContainer>
); );