feat: translate embeds

This commit is contained in:
tresabhi committed 2025-01-15 13:55:24 -06:00
1 parent baa16840df
commit 9aee8feaf4
13 files changed
+227 -81

No files matched your search

@@ -10,6 +10,7 @@ const SUPPORTED_LOCALES_DISCORD_MAP: Record<SupportedLocale, Locale> = {
zh: Locale.ChineseCN,
fr: Locale.French,
ja: Locale.Japanese,
pl: Locale.Polish,
};
export const SUPPORTED_LOCALES_DISCORD_MAP_INVERSE = Object.fromEntries(
+3
View File
@@ -7,6 +7,7 @@ export const SUPPORTED_LOCALES = [
'ru',
'uk',
'zh',
'pl',
] as const;
export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
@@ -22,6 +23,7 @@ export const SUPPORTED_LOCALE_BLITZ_MAP: Record<SupportedLocale, string> = {
ru: 'ru',
uk: 'uk',
zh: 'zh-Hans',
pl: 'pl',
};
export const SUPPORTED_LOCALE_FLAGS: Record<SupportedLocale, string> = {
@@ -33,4 +35,5 @@ export const SUPPORTED_LOCALE_FLAGS: Record<SupportedLocale, string> = {
ru: '🇷🇺',
uk: '🇺🇦',
zh: '🇨🇳',
pl: '🇵🇱',
};
+86 -2
View File
@@ -52,7 +52,8 @@
"uk": "Ukrainian",
"zh": "Chinese",
"ja": "Japanese",
"fr": "French"
"fr": "French",
"pl": "Polish"
},
"wn8_percentile": {
@@ -741,6 +742,12 @@
},
"common": {
"player_search": {
"hint": "Search players...",
"no_results": "No results",
"searching": "Searching..."
},
"copy_button": {
"copied": "Copied!"
},
@@ -1211,7 +1218,84 @@
"name": "Embed",
"button": "Create now",
"description": "Embeds for streaming",
"seo_description": "Create embeds for streaming World of Tanks Blitz with extreme customization"
"seo_description": "Create embeds for streaming World of Tanks Blitz with extreme customization",
"coming_soon": "More embeds coming soon.",
"suggest": "Suggest ideas.",
"preview": {
"title": "Example preview",
"upload": "Upload test background"
},
"configuration": {
"import": {
"title": "Import",
"description": "Paste your current URL if you have one already to import your settings",
"hint": "Paste URL...",
"apply": "Apply",
"idiot_title": "Invalid URL",
"idiot_description": "Try pasting the URL that BlitzKit previously generated, you silly goose!",
"idiot_button": "Okay, my bad"
},
"export": {
"title": "Export",
"description": "Copy all properties for you streaming software",
"generate": {
"button": "Generate URL",
"title": "Generate URL",
"subtitle": "Search an account to track",
"cancel": "Cancel",
"success": {
"title": "Copy URL",
"info": "Generated the URL for %s1. The embed will start tracking as soon as the URL is pasted. Reset at any time by right-clicking on PC or long-pressing on mobile and then selecting \"Reset\".",
"back": "Back",
"copy": "Copy URL"
}
},
"css": "Copy CSS",
"width": "Copy Width",
"height": "Copy Height",
"framerate": "Copy Framerate"
},
"customize": {
"title": "Customize",
"reset": "Reset",
"description": "Bells and whistles of your embed"
}
},
"types": {
"breakdown": {
"name": "Breakdown",
"options": {
"width": "Width",
"height": "Height",
"showTotal": "Show total",
"listGap": "List gap",
"listMaxTanks": "Max tanks",
"cardRadius": "Card radius",
"cardHeaderBackgroundColor": "Card header background color",
"cardHeaderPadding": "Card header padding",
"cardTitle": "Card title",
"cardTitleTypeColor": "Card title type color",
"cardTitleClassIcon": "Card title class icon",
"cardBodyBackgroundColor": "Card body background color",
"cardBodyPadding": "Card body padding",
"columnGap": "Column gap",
"columnValue": "Column value",
"columnLabel": "Column label",
"column1": "Column 1",
"column2": "Column 2",
"column3": "Column 3",
"column4": "Column 4",
"column1CustomLabel": "Column 1 label",
"column2CustomLabel": "Column 2 label",
"column3CustomLabel": "Column 3 label"
}
}
}
},
"charts": {
"name": "Charts",
+1
View File
@@ -0,0 +1 @@
{}
@@ -1,4 +1,5 @@
import { Select } from '@radix-ui/themes';
import { useLocale } from '../../hooks/useLocale';
import type { EmbedPreviewControllerProps } from '../../pages/[...locale]/tools/embed/[embed]/_index';
import { EmbedState } from '../../stores/embedState';
import type {
@@ -13,6 +14,7 @@ export function Enum({
config: EmbedConfigItemType<EmbedItemType.Enum>;
}) {
const mutateEmbedState = EmbedState.useMutation();
const { locale } = useLocale();
return (
<Select.Root
@@ -27,7 +29,7 @@ export function Enum({
<Select.Content>
{config.options.map((option) => (
<Select.Item key={option.value} value={option.value}>
{option.label}
{option.locales[locale]}
</Select.Item>
))}
</Select.Content>
@@ -3,7 +3,6 @@ import {
type AccountListItem,
type AccountListWithServer,
} from '@blitzkit/core';
import strings from '@blitzkit/i18n/strings/en.json';
import { CaretLeftIcon, Link2Icon, PersonIcon } from '@radix-ui/react-icons';
import {
Badge,
@@ -19,6 +18,8 @@ import { debounce } from 'lodash-es';
import { useCallback, useRef, useState } from 'react';
import { stringify } from 'urlon';
import type { embedConfigurations } from '../../constants/embeds';
import { literals } from '../../core/i18n/literals';
import { useLocale } from '../../hooks/useLocale';
import { EmbedState, type EmbedStateStore } from '../../stores/embedState';
import { CopyButton } from '../CopyButton';
@@ -34,6 +35,7 @@ export function GenerateURL({ embed }: GenerateURLProps) {
const input = useRef<HTMLInputElement>(null);
const [searching, setSearching] = useState(false);
const [results, setResults] = useState<AccountListWithServer>([]);
const { strings } = useLocale();
const performSearch = useCallback(
debounce(async () => {
if (!input.current) return;
@@ -72,20 +74,27 @@ export function GenerateURL({ embed }: GenerateURLProps) {
<Dialog.Trigger>
<Button>
<Link2Icon />
Generate URL
{strings.website.tools.embed.configuration.export.generate.button}
</Button>
</Dialog.Trigger>
<Dialog.Content width="fit-content">
<Flex direction="column" gap="1">
<Heading size="5">Generate URL</Heading>
<Text color="gray">Search an account to track</Text>
<Heading size="5">
{strings.website.tools.embed.configuration.export.generate.title}
</Heading>
<Text color="gray">
{
strings.website.tools.embed.configuration.export.generate
.subtitle
}
</Text>
</Flex>
<TextField.Root
mt="4"
style={{ width: '20rem' }}
placeholder="Search players..."
placeholder={strings.website.common.player_search.hint}
ref={input}
onChange={handleChange}
>
@@ -98,12 +107,14 @@ export function GenerateURL({ embed }: GenerateURLProps) {
{searching && (
<Text color="gray">
<Flex align="center" gap="2">
<Spinner /> Searching
<Spinner /> {strings.website.common.player_search.searching}
</Flex>
</Text>
)}
{!searching && results.length === 0 && (
<Text color="gray">No results</Text>
<Text color="gray">
{strings.website.common.player_search.no_results}
</Text>
)}
{!searching && results.length > 0 && (
@@ -131,7 +142,7 @@ export function GenerateURL({ embed }: GenerateURLProps) {
<Dialog.Close>
<Button color="red" variant="outline" style={{ width: '100%' }}>
Cancel
{strings.website.tools.embed.configuration.export.generate.cancel}
</Button>
</Dialog.Close>
</Dialog.Content>
@@ -140,12 +151,18 @@ export function GenerateURL({ embed }: GenerateURLProps) {
<Dialog.Root open={copyDialogOpen} onOpenChange={setCopyDialogOpen}>
<Dialog.Content style={{ width: 'fit-content' }}>
<Flex direction="column" gap="1">
<Heading size="5">Copy URL</Heading>
<Heading size="5">
{
strings.website.tools.embed.configuration.export.generate
.success.title
}
</Heading>
<Text color="gray">
Generated the URL for {copyUser?.nickname}. The embed will start
tracking as soon as the URL is pasted. Reset at any time by
right-clicking on PC or long-pressing on mobile and then selecting
"Reset".
{literals(
strings.website.tools.embed.configuration.export.generate
.success.info,
[`${copyUser?.nickname}`],
)}
</Text>
</Flex>
@@ -158,7 +175,11 @@ export function GenerateURL({ embed }: GenerateURLProps) {
setSearchDialogOpen(true);
}}
>
<CaretLeftIcon /> Back
<CaretLeftIcon />{' '}
{
strings.website.tools.embed.configuration.export.generate
.success.back
}
</Button>
<CopyButton
style={{ flex: 1 }}
@@ -186,7 +207,11 @@ export function GenerateURL({ embed }: GenerateURLProps) {
return `${location.origin}/tools/embed/${embed}/host?${searchParams.toString()}`;
}}
>
<Link2Icon /> Copy URL
<Link2Icon />{' '}
{
strings.website.tools.embed.configuration.export.generate
.success.copy
}
</CopyButton>
</Flex>
</Dialog.Content>
@@ -9,6 +9,7 @@ import {
} from '@radix-ui/themes';
import { useRef, useState } from 'react';
import { parse } from 'urlon';
import { useLocale } from '../../hooks/useLocale';
import { EmbedState } from '../../stores/embedState';
export function Import() {
@@ -16,12 +17,15 @@ export function Import() {
const [showInvalidURLPrompt, setShowInvalidURLPrompt] = useState(false);
const [showConfirmation, setConfirmation] = useState(false);
const mutateEmbedState = EmbedState.useMutation();
const { strings } = useLocale();
return (
<>
<Heading>Import</Heading>
<Heading>
{strings.website.tools.embed.configuration.import.title}
</Heading>
<Text size="2" color="gray" mb="2">
Paste your current URL if you have one already to import your settings
{strings.website.tools.embed.configuration.import.description}
</Text>
<AlertDialog.Root
@@ -29,15 +33,18 @@ export function Import() {
onOpenChange={setShowInvalidURLPrompt}
>
<AlertDialog.Content>
<AlertDialog.Title>Invalid URL</AlertDialog.Title>
<AlertDialog.Title>
{strings.website.tools.embed.configuration.import.idiot_title}
</AlertDialog.Title>
<AlertDialog.Description>
Try pasting the URL that BlitzKit previously generated, you silly
goose!
{strings.website.tools.embed.configuration.import.idiot_description}
</AlertDialog.Description>
<Flex gap="3" mt="4" justify="end">
<AlertDialog.Cancel>
<Button>Okay, my bad</Button>
<Button>
{strings.website.tools.embed.configuration.import.idiot_button}
</Button>
</AlertDialog.Cancel>
</Flex>
</AlertDialog.Content>
@@ -46,7 +53,7 @@ export function Import() {
<Flex gap="2" mb="6">
<TextField.Root
ref={importInput}
placeholder="Paste URL..."
placeholder={strings.website.tools.embed.configuration.import.hint}
style={{ flex: 1 }}
/>
<Tooltip content="Applied!" open={showConfirmation}>
@@ -72,7 +79,7 @@ export function Import() {
}
}}
>
Apply
{strings.website.tools.embed.configuration.import.apply}
</Button>
</Tooltip>
</Flex>
@@ -8,7 +8,7 @@ import {
sumCompositeStats,
type IndividualTankStats,
} from '@blitzkit/core';
import strings from '@blitzkit/i18n/strings/en.json';
import { localizedStrings } from '@blitzkit/i18n';
import { ReloadIcon } from '@radix-ui/react-icons';
import { ContextMenu } from '@radix-ui/themes';
import { useEffect, useMemo, useState } from 'react';
@@ -20,8 +20,16 @@ import { useEmbedStateCurry } from '../../stores/embedState/utilities';
import { BreakdownEmbedCard, BreakdownEmbedWrapper } from '../TanksEmbed';
export const compositeStatsKeysOptions = compositeStatsKeys.map((value) => ({
label: strings.common.composite_stats[value],
value,
locales: Object.entries(localizedStrings).reduce(
(accumulator, [locale, strings]) => {
return {
...accumulator,
[locale]: strings.common.composite_stats[value],
};
},
{},
),
}));
const [tankDefinitions, averageDefinitions] = await Promise.all([
@@ -8,7 +8,6 @@ import {
WidthIcon,
} from '@radix-ui/react-icons';
import { Box, Button, Flex, Heading, ScrollArea, Text } from '@radix-ui/themes';
import { capitalize, startCase } from 'lodash-es';
import { useEffect, useRef, useState, type ReactNode } from 'react';
import { CopyButton } from '../../../../../components/CopyButton';
import { Boolean } from '../../../../../components/Embeds/Boolean';
@@ -29,6 +28,11 @@ import {
extractEmbedConfigDefaults,
} from '../../../../../constants/embeds';
import { NAVBAR_HEIGHT } from '../../../../../constants/navbar';
import {
LocaleProvider,
useLocale,
type LocaleAcceptorProps,
} from '../../../../../hooks/useLocale';
import { App } from '../../../../../stores/app';
import { EmbedState, type EmbedConfig } from '../../../../../stores/embedState';
import { EmbedItemType } from '../../../../../stores/embedState/constants';
@@ -41,15 +45,17 @@ interface PageProps {
embed: keyof typeof embedConfigurations;
}
export function Page({ embed }: PageProps) {
export function Page({ embed, locale }: PageProps & LocaleAcceptorProps) {
const config = embedConfigurations[embed];
return (
<App.Provider>
<EmbedState.Provider data={extractEmbedConfigDefaults(config)}>
<Content embed={embed} />
</EmbedState.Provider>
</App.Provider>
<LocaleProvider locale={locale}>
<App.Provider>
<EmbedState.Provider data={extractEmbedConfigDefaults(config)}>
<Content embed={embed} />
</EmbedState.Provider>
</App.Provider>
</LocaleProvider>
);
}
@@ -60,6 +66,7 @@ function Content({ embed }: PageProps) {
imgur('SO13zur', { format: 'jpeg' }),
);
const fileInput = useRef<HTMLInputElement>();
const { strings } = useLocale();
useEffect(() => {
fileInput.current = document.createElement('input');
@@ -83,9 +90,11 @@ function Content({ embed }: PageProps) {
<Flex direction="column" gap="2" p="4">
<Import />
<Heading>Export</Heading>
<Heading>
{strings.website.tools.embed.configuration.export.title}
</Heading>
<Text size="2" color="gray" mb="2">
Copy all properties for you streaming software
{strings.website.tools.embed.configuration.export.description}
</Text>
<Flex mb="6" gap="2" wrap="wrap">
@@ -98,30 +107,32 @@ function Content({ embed }: PageProps) {
}
>
<CodeIcon />
Copy CSS
{strings.website.tools.embed.configuration.export.css}
</CopyButton>
<CopyButton
variant="outline"
copy={() => `${embedStateStore.getState().width}`}
>
<WidthIcon />
Copy width
{strings.website.tools.embed.configuration.export.width}
</CopyButton>
<CopyButton
variant="outline"
copy={() => `${embedStateStore.getState().height}`}
>
<HeightIcon />
Copy height
{strings.website.tools.embed.configuration.export.height}
</CopyButton>
<CopyButton variant="outline" copy={() => '1'}>
<TimerIcon />
Copy framerate
{strings.website.tools.embed.configuration.export.framerate}
</CopyButton>
</Flex>
<Flex justify="between" align="center">
<Heading>Customize</Heading>
<Heading>
{strings.website.tools.embed.configuration.customize.title}
</Heading>
<Button
color="red"
variant="ghost"
@@ -132,11 +143,12 @@ function Content({ embed }: PageProps) {
);
}}
>
<ResetIcon /> Reset
<ResetIcon />{' '}
{strings.website.tools.embed.configuration.customize.reset}
</Button>
</Flex>
<Text size="2" color="gray" mb="2">
Bells and whistles of your embed
{strings.website.tools.embed.configuration.customize.description}
</Text>
{Object.keys(config).map((configKey) => {
@@ -206,7 +218,14 @@ function Content({ embed }: PageProps) {
mb={oneLiner ? '2' : '4'}
pb={item.pad ? '6' : undefined}
>
<Text>{capitalize(startCase(configKey as string))}</Text>
<Text>
{
(
strings.website.tools.embed.types[embed]
.options as Record<string, string>
)[configKey]
}
</Text>
<Flex
gap="2"
align="center"
@@ -246,14 +265,16 @@ function Content({ embed }: PageProps) {
backgroundColor: 'var(--color-panel-solid)',
}}
>
<Text color="gray">Example preview</Text>
<Text color="gray">
{strings.website.tools.embed.preview.title}
</Text>
<Button
variant="outline"
color="gray"
onClick={() => fileInput.current?.click()}
>
<ImageIcon /> Upload test background
<ImageIcon /> {strings.website.tools.embed.preview.upload}
</Button>
</Flex>
@@ -20,6 +20,8 @@ export const getStaticPaths = (() => {
)
.flat();
}) satisfies GetStaticPaths;
const { locale } = Astro.params;
---
<title>BlitzKit - {capitalize(Astro.params.embed)} embed</title>
@@ -44,5 +46,5 @@ export const getStaticPaths = (() => {
</style>
<BaseLayout>
<Page client:load embed={Astro.params.embed} />
<Page {locale} client:load embed={Astro.params.embed} />
</BaseLayout>
@@ -1,6 +1,5 @@
import { imgur, ImgurSize } from '@blitzkit/core';
import { Box, Flex, Link, Text } from '@radix-ui/themes';
import { capitalize } from 'lodash-es';
import { PreviewWrapper } from '../../../../components/Embeds/PreviewWrapper';
import { LinkI18n } from '../../../../components/LinkI18n';
import { PageWrapper } from '../../../../components/PageWrapper';
@@ -9,52 +8,37 @@ import {
extractEmbedConfigDefaults,
} from '../../../../constants/embeds';
import { Var } from '../../../../core/radix/var';
import { useLocale } from '../../../../hooks/useLocale';
import {
LocaleProvider,
useLocale,
type LocaleAcceptorProps,
} from '../../../../hooks/useLocale';
import { App } from '../../../../stores/app';
import { EmbedState } from '../../../../stores/embedState';
export function Page() {
export function Page({ locale }: LocaleAcceptorProps) {
return (
<App.Provider>
<Content />
</App.Provider>
<LocaleProvider locale={locale}>
<App.Provider>
<Content />
</App.Provider>
</LocaleProvider>
);
}
function Content() {
// const wargaming = App.useDeferred((state) => state.logins.wargaming, {
// token: '',
// id: 0,
// expires: 0,
// });
const { locale } = useLocale();
const { locale, strings } = useLocale();
return (
<PageWrapper color="red">
{/* {!wargaming && (
<Flex direction="column" align="center" gap="2">
<Callout.Root>
<Callout.Icon>
<ExclamationTriangleIcon />
</Callout.Icon>
<Callout.Text>
<Flex align="center">You must log in to generate embeds.</Flex>
</Callout.Text>
</Callout.Root>
<WargamingLoginButton>Log in with Wargaming</WargamingLoginButton>
</Flex>
)} */}
<Text align="center" mt="3">
More embeds coming soon.{' '}
{strings.website.tools.embed.coming_soon}{' '}
<Link
underline="always"
href="https://discord.gg/nDt7AjGJQH"
target="_blank"
>
Suggest ideas
{strings.website.tools.embed.suggest}
</Link>
.
</Text>
@@ -86,7 +70,13 @@ function Content() {
}}
>
<Flex justify="center" p="2">
<Text>{capitalize(embed)}</Text>
<Text>
{
strings.website.tools.embed.types[
embed as keyof typeof strings.website.tools.embed.types
].name
}
</Text>
</Flex>
<Box
@@ -4,8 +4,10 @@ import ToolLayout from '../../../../layouts/ToolLayout.astro';
import { Page } from './_index';
export { getStaticPaths } from '../../index.astro';
const { locale } = Astro.params;
---
<ToolLayout tool={embedTool}>
<Page />
<Page {locale} />
</ToolLayout>
@@ -94,8 +94,8 @@ export type EmbedConfigItem = (
type: EmbedItemType.Enum;
default: string;
options: {
label: string;
value: string;
locales: Record<string, string>;
}[];
}
) & {