- {getVipTokenRedemptionDescription()}
+ {t("vip.redemptionDescription")}
- How to earn VIP tokens
+ {t("vip.howToEarn")}
{vipTokenAutomationDetails.earningRules.length ? (
@@ -1169,9 +1251,7 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
)}
) : (
-
- This channel grants VIP tokens manually right now.
-
+
{t("vip.manualOnly")}
)}
{vipTokenAutomationDetails.notes.map((note) => (
{note}
@@ -1189,7 +1269,7 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
onClick={handleRequestIdentityShare}
disabled={helperState !== "ready"}
>
- Share Twitch Identity
+ {t("panel.shareIdentityButton")}
) : null}
@@ -1199,20 +1279,22 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
{helperState === "error" ? (
} tone="danger">
- {helperError ?? "Unable to load the Twitch extension helper."}
+ {translateExtensionMessage(
+ helperError ?? t("notices.helperLoadFailed"),
+ t
+ )}
) : null}
{helperTimedOut && !auth ? (
}>
- Open this page from Twitch Local Test or Hosted Test to receive
- panel authorization.
+ {t("notices.authorizationHint")}
) : null}
{bootstrapError ? (
} tone="danger">
- {bootstrapError}
+ {translateExtensionMessage(bootstrapError, t)}
) : null}
@@ -1221,7 +1303,7 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
icon={
}
tone="default"
>
- {connectionMessage}
+ {translateExtensionMessage(connectionMessage, t)}
) : null}
@@ -1231,19 +1313,19 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
key={transientNotice.id}
tone={transientNotice.tone}
>
- {transientNotice.message}
+ {translateExtensionMessage(transientNotice.message, t)}
) : null}
{bootstrap?.setup ? (
}>
- {bootstrap.setup.message}
+ {translateExtensionMessage(bootstrap.setup.message, t)}
) : null}
{bootstrap?.channel && !channelRequestsOpen ? (
}>
- {ADD_REQUESTS_WHEN_LIVE_MESSAGE}
+ {addRequestsWhenLiveMessage}
) : null}
@@ -1270,7 +1352,7 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
style={{ fontFamily: '"IBM Plex Sans", sans-serif' }}
>
- Playlist
+ {t("queue.tab")}
({queueCount})
@@ -1281,7 +1363,7 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
className="h-auto rounded-none border-0 px-3 py-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-(--muted) shadow-none after:bottom-0 after:h-px after:bg-(--brand-deep) data-[state=active]:text-(--brand-deep)"
style={{ fontFamily: '"IBM Plex Sans", sans-serif' }}
>
- Search
+ {t("search.tab")}
@@ -1296,7 +1378,7 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
- Queue tools
+ {t("queue.tools")}
{showShufflePlaylistControl ? (
@@ -1374,7 +1456,7 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
})
) : (
- Queue is empty.
+ {t("queue.empty")}
)}
@@ -1411,8 +1493,8 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
autoCapitalize="none"
placeholder={
editingRequest
- ? "Search for a song to edit your request"
- : "Search title, artist, or album"
+ ? t("search.placeholderEdit")
+ : t("search.placeholder")
}
className="h-8 rounded-none border-(--border-strong) px-2 py-1 text-[12px] shadow-none focus-visible:ring-1 focus-visible:ring-(--brand) focus-visible:ring-offset-0"
/>
@@ -1436,7 +1518,7 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
{searchError ? (
- {searchError}
+ {translateExtensionMessage(searchError, t)}
) : null}
{showSpecialRequestControls ? (
@@ -1463,7 +1545,7 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
{searchTabBlockedByRequestsOff ? (
- Requests are off right now.
+ {t("requests.offRightNow")}
) : searchResults?.items?.length ? (
@@ -1481,7 +1563,7 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
- {formatSearchSongLabel(item)}
+ {formatSearchSongLabel(item, t)}
{formatSearchSongMeta(item)}
@@ -1502,10 +1584,10 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
}}
>
{pendingAction === managerActionKey
- ? "Adding..."
+ ? t("buttons.adding")
: isEditingRequest
- ? "Edit"
- : "Add"}
+ ? t("buttons.edit")
+ : t("buttons.add")}
) : showViewerSearchActions ? (
@@ -1522,7 +1604,7 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
}
title={
!viewerRequestsAvailable
- ? ADD_REQUESTS_WHEN_LIVE_MESSAGE
+ ? addRequestsWhenLiveMessage
: undefined
}
onClick={() => {
@@ -1538,11 +1620,11 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
>
{pendingAction === actionKey
? isEditingRequest
- ? "Editing..."
- : "Adding..."
+ ? t("buttons.editing")
+ : t("buttons.adding")
: isEditingRequest
- ? "Edit"
- : "Add"}
+ ? t("buttons.edit")
+ : t("buttons.add")}
) : debouncedSearchQuery.trim().length >= 3 && !searching ? (
- No songs matched that search.
+ {t("search.noResults")}
) : null}
@@ -1594,6 +1676,7 @@ export function ExtensionPanelApp(props: { apiBaseUrl?: string }) {
}
export function ExtensionPanelModeratorPreview() {
+ const { t } = useLocaleTranslation("extension");
const [playlist, setPlaylist] = useState
({
currentItemId: "preview-current",
items: createMockModeratorPlaylistItems(),
@@ -1634,7 +1717,7 @@ export function ExtensionPanelModeratorPreview() {
const queueCount = playlist.items.length;
const showPlaylistPositions = false;
const footerPlaylistHref = toExtensionAppUrl("/jimmy-pants");
- const footerPlaylistLabel = getPanelPlaylistFooterLabel("Jimmy Pants_");
+ const footerPlaylistLabel = getPanelPlaylistFooterLabel("Jimmy Pants_", t);
const queuedPlaylistItems = playlist.items.filter(
(item) => getString(item, "id") !== currentPlaylistItemId
);
@@ -1643,11 +1726,11 @@ export function ExtensionPanelModeratorPreview() {
const canShufflePlaylist =
showShufflePlaylistControl && currentPlaylistItemId == null;
const shufflePlaylistTooltip = currentPlaylistItemId
- ? "Mark the current song played before shuffling."
- : "Shuffle the queue.";
+ ? t("queue.shuffleBlocked")
+ : t("queue.shuffle");
const vipSearchDisabledReason =
mockModeratorViewerProfile.vipTokensAvailable < 1
- ? "Not enough VIP tokens."
+ ? t("vip.notEnough")
: null;
const editingRequest = getViewerEditablePanelItem(
playlist.items,
@@ -2029,7 +2112,9 @@ export function ExtensionPanelModeratorPreview() {
) {
showTransientMessage(
"danger",
- `You already have ${activeRequestLimit} active request${activeRequestLimit === 1 ? "" : "s"} in this playlist.`
+ t("requests.limitReached", {
+ count: activeRequestLimit,
+ })
);
return;
}
@@ -2102,9 +2187,13 @@ export function ExtensionPanelModeratorPreview() {
if (mutation.action !== "reorderItems") {
showTransientMessage(
"success",
- getPlaylistMutationSuccessMessage(mutation, {
- ok: true,
- })
+ getPlaylistMutationSuccessMessage(
+ mutation,
+ {
+ ok: true,
+ },
+ t
+ )
);
}
@@ -2163,21 +2252,27 @@ export function ExtensionPanelModeratorPreview() {
-
-
- Jimmy Pants_'s Request Playlist
-
-
- You have{" "}
- {formatVipTokensCompact(
- mockModeratorViewerProfile.vipTokensAvailable
- )}{" "}
- ·{" "}
- {formatRequestLimitCompact(
- activeRequestCount,
- activeRequestLimit
- )}
-
+
+
+
+ {t("panel.titleWithChannel", {
+ displayName: "Jimmy Pants_",
+ })}
+
+
+ {formatVipTokensCompact(
+ mockModeratorViewerProfile.vipTokensAvailable,
+ t
+ )}{" "}
+ ·{" "}
+ {formatRequestLimitCompact(
+ activeRequestCount,
+ activeRequestLimit,
+ t
+ )}
+
+
+
@@ -2187,7 +2282,7 @@ export function ExtensionPanelModeratorPreview() {
key={transientNotice.id}
tone={transientNotice.tone}
>
- {transientNotice.message}
+ {translateExtensionMessage(transientNotice.message, t)}
) : null}
@@ -2211,7 +2306,7 @@ export function ExtensionPanelModeratorPreview() {
style={{ fontFamily: '"IBM Plex Sans", sans-serif' }}
>
- Playlist
+ {t("queue.tab")}
({queueCount})
@@ -2222,7 +2317,7 @@ export function ExtensionPanelModeratorPreview() {
className="h-auto rounded-none border-0 px-3 py-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-(--muted) shadow-none after:bottom-0 after:h-px after:bg-(--brand-deep) data-[state=active]:text-(--brand-deep)"
style={{ fontFamily: '"IBM Plex Sans", sans-serif' }}
>
- Search
+ {t("search.tab")}
@@ -2237,7 +2332,7 @@ export function ExtensionPanelModeratorPreview() {
- Queue tools
+ {t("queue.tools")}
@@ -2335,8 +2430,8 @@ export function ExtensionPanelModeratorPreview() {
autoCapitalize="none"
placeholder={
editingRequest
- ? "Search for a song to edit your request"
- : "Search title, artist, or album"
+ ? t("search.placeholderEdit")
+ : t("search.placeholder")
}
className="h-8 rounded-none border-(--border-strong) px-2 py-1 text-[12px] shadow-none focus-visible:ring-1 focus-visible:ring-(--brand) focus-visible:ring-offset-0"
/>
@@ -2356,7 +2451,7 @@ export function ExtensionPanelModeratorPreview() {
{searchError ? (
- {searchError}
+ {translateExtensionMessage(searchError, t)}
) : null}
- {formatSearchSongLabel(item)}
+ {formatSearchSongLabel(item, t)}
{formatSearchSongMeta(item)}
@@ -2421,11 +2516,11 @@ export function ExtensionPanelModeratorPreview() {
>
{pendingAction === actionKey
? isEditingRequest
- ? "Editing..."
- : "Adding..."
+ ? t("buttons.editing")
+ : t("buttons.adding")
: isEditingRequest
- ? "Edit"
- : "Add"}
+ ? t("buttons.edit")
+ : t("buttons.add")}
) : !searching ? (
- No songs matched that search.
+ {t("search.noResults")}
) : null}
@@ -2499,6 +2594,7 @@ function PanelPlaylistRow(props: {
onRemoveRequest: (itemId: string) => Promise;
onPlaylistMutation: (mutation: PanelPlaylistMutation) => Promise;
}) {
+ const { t } = useLocaleTranslation("extension");
const itemRef = useRef(null);
const dragHandleRef = useRef(null);
const isCurrent = props.itemId === props.currentItemId;
@@ -2714,7 +2810,9 @@ function PanelPlaylistRow(props: {
@@ -2780,10 +2878,10 @@ function PanelPlaylistRow(props: {
- {formatSongLabel(props.item)}
+ {formatSongLabel(props.item, t)}
- {formatRequesterLine(props.item)}
+ {formatRequesterLine(props.item, t)}
@@ -2794,8 +2892,8 @@ function PanelPlaylistRow(props: {
size="sm"
variant="ghost"
className="h-6 w-6 rounded-none px-0 text-(--muted) shadow-none hover:bg-(--panel-soft) hover:text-(--text)"
- title="Request actions"
- aria-label="Request actions"
+ title={t("panel.requestActions")}
+ aria-label={t("panel.requestActions")}
>
@@ -2836,7 +2934,7 @@ function PanelPlaylistRow(props: {
>
{canEditOwnRequest ? (
props.onEditRequest(props.itemId)}
>
@@ -2846,7 +2944,11 @@ function PanelPlaylistRow(props: {
{canToggleVipRequest ? (
{
void props.onPlaylistMutation({
@@ -2870,7 +2972,7 @@ function PanelPlaylistRow(props: {
{canShowSetCurrent ? (
{
void props.onPlaylistMutation({
action: "setCurrent",
@@ -2889,7 +2991,7 @@ function PanelPlaylistRow(props: {
{canReturnToQueue ? (
{
void props.onPlaylistMutation({
action: "returnToQueue",
@@ -2910,7 +3012,7 @@ function PanelPlaylistRow(props: {
{canMarkPlayed ? (
{
void props.onPlaylistMutation({
action: "markPlayed",
@@ -2935,8 +3037,8 @@ function PanelPlaylistRow(props: {
>
{props.canManagePlaylist
- ? "Remove from playlist?"
- : "Remove request?"}
+ ? t("requests.removeFromPlaylistConfirm")
+ : t("requests.removeConfirm")}