feat: homepage

This commit is contained in:
tresabhi committed 2025-01-06 20:09:11 -06:00
1 parent 1d44ecab18
commit d9688f6051
18 files changed
+303 -153

No files matched your search

+138 -1
View File
@@ -1,5 +1,18 @@
{ {
"test": {
"$": "test",
"long": "This is a long test string. You MUST NEVER see this in the UI. My favorite fruit is %s1 because it has the color of %s2. I like it so much that I will say its name 3 times! %s1! %s1! %s1! Once again, it's %s2, not %s3.",
"fox": "The quick brown fox jumps over the lazy dog."
},
"common": { "common": {
"branches": {
"local": "dev",
"dev": "beta",
"opentest": "opentest",
"preview": "preview"
},
"locales": { "locales": {
"en": "English", "en": "English",
"es": "Spanish", "es": "Spanish",
@@ -689,5 +702,129 @@
} }
}, },
"website": {} "website": {
"home": {
"hero": {
"credit": "By %s1 ❤️"
},
"hot_tanks": {
"title": "Popular tanks",
"subtitle": "Last %s1 days"
},
"other_projects": {
"title": "Other cool projects"
}
},
"tools": {
"tankopedia": {
"name": "Tankopedia",
"button": "Find tanks",
"description": "Blitz tank encyclopedia",
"seo_description": "Statistics, armor, and more for all tanks in World of Tanks Blitz"
},
"compare": {
"name": "Compare",
"button": "Compare tanks",
"description": "Compare tank statistics",
"seo_description": "Compare tanks statistics and loadouts in World of Tanks Blitz"
},
"performance": {
"name": "Performance",
"button": "View tanks",
"description": "Tank performance statistics",
"seo_description": "Best tanks and their performances in World of Tanks Blitz"
},
"gallery": {
"name": "Gallery",
"button": "Browse all",
"description": "Blitz player avatars",
"seo_description": "All World of Tanks Blitz player avatars"
},
"session": {
"name": "Session",
"button": "Start tracking",
"description": "Live session stats",
"seo_description": "Track your stats in real time as you play World of Tanks Blitz"
},
"tier_list": {
"name": "Tier list",
"button": "Rank tanks",
"description": "Rank Blitz tanks",
"seo_description": "Explore tier lists ranking tanks from best to worst, based on player ratings and statistical performance in World of Tanks Blitz"
},
"embed": {
"name": "Embed",
"button": "Create now",
"description": "Embeds for streaming",
"seo_description": "Create embeds for streaming World of Tanks Blitz with extreme customization"
},
"charts": {
"name": "Charts",
"button": "Nerd out",
"description": "Lots of graphs",
"seo_description": "Charts and graphs on anything and everything about tanks, players, and events in World of Tanks Blitz"
},
"rating": {
"name": "Rating",
"button": "View leaderboard",
"description": "Full rating leaderboard",
"seo_description": "Live and archived full rating leaderboard for World of Tanks Blitz"
},
"discord": {
"name": "Discord bot",
"button": "Install now",
"description": "Stats right in Discord",
"seo_description": "Get stats right in Discord with BlitzKit's Discord bot"
},
"more": {
"name": "More coming soon",
"button": "Join Discord",
"description": "What we're making",
"seo_description": "What we're making with BlitzKit"
},
"players": {
"name": "Players",
"button": "Lookup player",
"description": "Periodical player statistics",
"seo_description": "Periodical player statistics in World of Tanks Blitz"
},
"home": {
"name": "Home",
"button": "Get started",
"description": "Get started with BlitzKit",
"seo_description": "Get started with BlitzKit"
},
"blitz_analysis": {
"name": "BlitzAnalysis[]",
"button": "Analyze now",
"description": "Rich historic average stats",
"seo_description": "Rich historic average stats in World of Tanks Blitz"
},
"blitz_insider": {
"name": "Blitz Insider",
"button": "Discover today",
"description": "In-game session stats",
"seo_description": "In-game session stats in World of Tanks Blitz"
}
},
"footer": {
"motto": "Everything World of Tanks Blitz",
"copyright": "Unaffiliated with Wargaming or any service",
"about": {
"title": "About",
"credits": "Credits",
"discord": "Discord",
"github": "GitHub",
"patches": "Patches"
},
"legal": {
"title": "Legal",
"privacy": "Privacy policy",
"terms": "Terms of service"
}
}
}
} }
+1 -12
View File
@@ -1,13 +1,2 @@
export const SUPPORTED_LOCALES = [ export const SUPPORTED_LOCALES = ['en', 'es', 'hr', 'pt', 'ru', 'uk', 'zh'];
'en',
'es',
'hr',
'pt',
'ru',
'uk',
'zh',
] as const;
export const DEFAULT_LOCALE = SUPPORTED_LOCALES[0]; export const DEFAULT_LOCALE = SUPPORTED_LOCALES[0];
export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
+2
View File
@@ -0,0 +1,2 @@
export * from './strings';
export * from './translator';
+46
View File
@@ -0,0 +1,46 @@
import { merge } from 'lodash-es';
import type en from '../../lang/en.json';
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '../blitzkit';
import { TranslationTree } from './translator';
const DEBUG_MISSING_I18N = import.meta.env.DEBUG_MISSING_I18N;
const files = import.meta.glob('../../lang/*.json', { eager: true });
export const localizedStrings: Record<string, typeof en> = {};
function nuke(object: TranslationTree) {
for (const key in object) {
const typedKey = key as keyof typeof object;
if (typeof object[typedKey] === 'string') {
const value = object[typedKey];
object[typedKey] = redact(value as string);
} else nuke(object[typedKey] as TranslationTree);
}
}
SUPPORTED_LOCALES.forEach((locale) => {
const defaultStrings = files[`../../lang/${DEFAULT_LOCALE}.json`];
const strings = files[`../../lang/${locale}.json`];
const mergedStrings = merge({}, defaultStrings, strings);
if (DEBUG_MISSING_I18N === 'true') nuke(mergedStrings);
localizedStrings[locale] = mergedStrings as typeof en;
});
function redact(string: string) {
const matches = [...string.matchAll(/%s\d+/g)];
const characters = string
.split('')
.map((char) => (char === ' ' ? ' ' : '█')) as string[];
matches.forEach((match) => {
const start = match.index;
const matchText = match[0];
for (let i = 0; i < matchText.length; i++) {
characters[start + i] = matchText[i];
}
});
return characters.join('');
}
@@ -1,9 +1,6 @@
import en from '@blitzkit/core/lang/en.json'; export type TranslationTree = { [key: string]: TranslationNode };
import ru from '@blitzkit/core/lang/ru.json';
type TranslationTree = { [key: string]: Translation }; export type TranslationNode = string | TranslationTree;
type Translation = string | TranslationTree;
type NestedKeys<Type> = Type extends object type NestedKeys<Type> = Type extends object
? { ? {
@@ -17,7 +14,7 @@ type NestedKeys<Type> = Type extends object
}[keyof Type] }[keyof Type]
: never; : never;
class Prate< export class Translator<
Locale extends string, Locale extends string,
DefaultLocale extends Locale, DefaultLocale extends Locale,
Strings extends TranslationTree, Strings extends TranslationTree,
@@ -26,8 +23,8 @@ class Prate<
private readonly defaultStrings: Map<string, string>; private readonly defaultStrings: Map<string, string>;
constructor( constructor(
public strings: Record<Locale, Strings>, private strings: Record<Locale, Strings>,
public defaultLocale: DefaultLocale, private defaultLocale: DefaultLocale,
) { ) {
this.defaultStrings = this.flatten(strings[defaultLocale]); this.defaultStrings = this.flatten(strings[defaultLocale]);
} }
@@ -35,7 +32,7 @@ class Prate<
private flatten(strings: TranslationTree) { private flatten(strings: TranslationTree) {
const translations = new Map<string, string>(); const translations = new Map<string, string>();
function traverse(parent: string, tree: Record<string, Translation>) { function traverse(parent: string, tree: Record<string, TranslationNode>) {
for (const key in tree) { for (const key in tree) {
const path = `${parent}${key}`; const path = `${parent}${key}`;
@@ -91,5 +88,3 @@ class Prate<
return { translate, t: translate }; return { translate, t: translate };
} }
} }
const prate = new Prate({ en, ru }, 'en');
+1
View File
@@ -1,6 +1,7 @@
export * from './blitz'; export * from './blitz';
export * from './blitzkit'; export * from './blitzkit';
export * from './databases'; export * from './databases';
export * from './i18n';
export * from './imgur'; export * from './imgur';
export * from './math'; export * from './math';
export * from './patreon'; export * from './patreon';
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+16 -14
View File
@@ -2,10 +2,12 @@
import { assertSecret, fetchGameDefinitions } from '@blitzkit/core'; import { assertSecret, fetchGameDefinitions } from '@blitzkit/core';
import { Code, Flex, Heading, Link, Text } from '@radix-ui/themes'; import { Code, Flex, Heading, Link, Text } from '@radix-ui/themes';
import packageJson from '../../../../package.json'; import packageJson from '../../../../package.json';
import { getStrings } from '../core/i18n/getStrings';
import { LinkI18n } from './LinkI18n'; import { LinkI18n } from './LinkI18n';
const gameDefinitions = await fetchGameDefinitions(); const gameDefinitions = await fetchGameDefinitions();
const { locale } = Astro.params; const { locale } = Astro.params;
const strings = getStrings(locale);
--- ---
<style> <style>
@@ -15,7 +17,6 @@ const { locale } = Astro.params;
</style> </style>
<Flex className="footer" gap="6" p="6" direction="column" align="center"> <Flex className="footer" gap="6" p="6" direction="column" align="center">
{locale}
<Flex <Flex
align="center" align="center"
justify="between" justify="between"
@@ -34,9 +35,9 @@ const { locale } = Astro.params;
}} }}
> >
<Heading>BlitzKit</Heading> <Heading>BlitzKit</Heading>
<Text color="gray">Everything World of Tanks Blitz</Text> <Text color="gray">{strings.website.footer.motto}</Text>
<Text size="1" color="gray"> <Text size="1" color="gray">
Unaffiliated with Wargaming or any service {strings.website.footer.copyright}
</Text> </Text>
</Flex> </Flex>
@@ -63,7 +64,7 @@ const { locale } = Astro.params;
sm: 'auto', sm: 'auto',
}} }}
> >
<Heading size="3">About</Heading> <Heading size="3">{strings.website.footer.about.title}</Heading>
<Flex <Flex
direction="column" direction="column"
align={{ align={{
@@ -71,25 +72,26 @@ const { locale } = Astro.params;
sm: 'start', sm: 'start',
}} }}
> >
<LinkI18n {locale} href="/docs/about/credits" color="gray" <LinkI18n {locale} href="/docs/about/credits" color="gray">
>Credits</LinkI18n {strings.website.footer.about.credits}
> </LinkI18n>
<Link <Link
href="https://discord.gg/nDt7AjGJQH" href="https://discord.gg/nDt7AjGJQH"
target="_blank" target="_blank"
color="gray" color="gray"
> >
Discord {strings.website.footer.about.discord}
</Link> </Link>
<Link <Link
href="https://github.com/tresabhi/blitzkit" href="https://github.com/tresabhi/blitzkit"
target="_blank" target="_blank"
color="gray" color="gray"
> >
GitHub {strings.website.footer.about.github}
</Link> </Link>
<LinkI18n {locale} href="/docs/patches" color="gray">Patches</LinkI18n <LinkI18n {locale} href="/docs/patches" color="gray">
> {strings.website.footer.about.patches}
</LinkI18n>
</Flex> </Flex>
</Flex> </Flex>
@@ -105,13 +107,13 @@ const { locale } = Astro.params;
sm: 'auto', sm: 'auto',
}} }}
> >
<Heading size="3">Legal</Heading> <Heading size="3">{strings.website.footer.legal.title}</Heading>
<Flex direction="column"> <Flex direction="column">
<LinkI18n {locale} href="/docs/legal/privacy-policy" color="gray"> <LinkI18n {locale} href="/docs/legal/privacy-policy" color="gray">
Privacy policy {strings.website.footer.legal.privacy}
</LinkI18n> </LinkI18n>
<LinkI18n {locale} href="/docs/legal/terms-of-service" color="gray"> <LinkI18n {locale} href="/docs/legal/terms-of-service" color="gray">
Terms of service {strings.website.footer.legal.terms}
</LinkI18n> </LinkI18n>
</Flex> </Flex>
</Flex> </Flex>
@@ -1,9 +1,14 @@
--- ---
import { Code, Flex, Heading, Text } from '@radix-ui/themes'; import { Code, Flex, Heading, Text } from '@radix-ui/themes';
import { resolveBranchName } from '../core/blitzkit/branches'; import { resolveBranchName } from '../core/blitzkit/branches';
import { DEFAULT_LOCALE } from '@blitzkit/core';
import { getStrings } from '../core/i18n/getStrings';
import { literals } from '../core/i18n/literals';
const branchName = resolveBranchName(); const { locale } = Astro.params;
const branchName = resolveBranchName(locale);
const isBranchNamed = branchName !== undefined; const isBranchNamed = branchName !== undefined;
const strings = getStrings(locale);
--- ---
<style> <style>
@@ -141,7 +146,9 @@ const isBranchNamed = branchName !== undefined;
</Flex> </Flex>
</Heading> </Heading>
<Text color="gray" align="center" size="4">By TrèsAbhi ❤️</Text> <Text color="gray" align="center" size="4"
>{literals(strings.website.home.hero.credit, ['TrèsAbhi'])}</Text
>
</Flex> </Flex>
</Flex> </Flex>
</Flex> </Flex>
@@ -3,7 +3,8 @@ import { EyeOpenIcon } from '@radix-ui/react-icons';
import { Box, Flex, Heading, Text } from '@radix-ui/themes'; import { Box, Flex, Heading, Text } from '@radix-ui/themes';
import { google } from 'googleapis'; import { google } from 'googleapis';
import { awaitableTankDefinitions } from '../core/awaitables/tankDefinitions'; import { awaitableTankDefinitions } from '../core/awaitables/tankDefinitions';
import { LocaleProvider } from '../hooks/useLocale'; import { literals } from '../core/i18n/literals';
import { LocaleProvider, useLocale } from '../hooks/useLocale';
import { TankopediaPersistent } from '../stores/tankopediaPersistent'; import { TankopediaPersistent } from '../stores/tankopediaPersistent';
import { TankCard } from './TankCard'; import { TankCard } from './TankCard';
@@ -79,13 +80,15 @@ export function HomePageHotTanks({ locale }: HomePageHotTanksProps) {
} }
function Content() { function Content() {
const { locale, strings } = useLocale();
return ( return (
<Flex direction="column" pt="4" pb="8"> <Flex direction="column" pt="4" pb="8">
<Heading align="center" size="5"> <Heading align="center" size="5">
Popular tanks {strings.website.home.hot_tanks.title}
</Heading> </Heading>
<Text color="gray" align="center" mb="5" size="2"> <Text color="gray" align="center" mb="5" size="2">
Last 7 days {literals(strings.website.home.hot_tanks.subtitle, ['7'])}
</Text> </Text>
<Flex justify="center" gap="4" wrap="wrap"> <Flex justify="center" gap="4" wrap="wrap">
@@ -96,7 +99,7 @@ function Content() {
discriminator={ discriminator={
<Flex align="center" gap="1" justify="center"> <Flex align="center" gap="1" justify="center">
<EyeOpenIcon /> <EyeOpenIcon />
{Math.round(row.views * (30 / 7)).toLocaleString('en-US')} {Math.round(row.views * (30 / 7)).toLocaleString(locale)}
</Flex> </Flex>
} }
/> />
+18 -9
View File
@@ -19,14 +19,15 @@ import {
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import { resolveBranchName } from '../core/blitzkit/branches'; import { resolveBranchName } from '../core/blitzkit/branches';
import { homeTool, TOOLS } from '../constants/tools'; import { homeTool, TOOLS } from '../constants/tools';
import { NavbarAccount } from './NavbarAccount';
import { LinkI18n } from './LinkI18n'; import { LinkI18n } from './LinkI18n';
import { getStrings } from '../core/i18n/getStrings';
const toolsFiltered = TOOLS.filter((tool) => !('href' in tool)); const toolsFiltered = TOOLS.filter((tool) => !('href' in tool));
const tools = [homeTool, ...toolsFiltered]; const tools = [homeTool, ...toolsFiltered];
const branchName = resolveBranchName();
const isBranchNamed = branchName !== undefined;
const { locale } = Astro.params; const { locale } = Astro.params;
const branchName = resolveBranchName(locale);
const isBranchNamed = branchName !== undefined;
const strings = getStrings(locale);
--- ---
<style> <style>
@@ -139,14 +140,17 @@ const { locale } = Astro.params;
(branch) => (branch) =>
branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH), branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
); );
const selected = Astro.url.pathname.startsWith( const selected = Astro.url.pathname.includes(`/tools/${tool.id}`);
`/tools/${tool.id}`, const toolStrings =
); strings.website.tools[
(tool.stringsId ??
tool.id) as keyof typeof strings.website.tools
];
if (unavailableOnBranch) return null; if (unavailableOnBranch) return null;
return ( return (
<Fragment key={tool.title}> <Fragment key={tool.id}>
{index > 0 && <Separator orientation="vertical" size="1" />} {index > 0 && <Separator orientation="vertical" size="1" />}
<LinkI18n <LinkI18n
{locale} {locale}
@@ -156,7 +160,7 @@ const { locale } = Astro.params;
href={`/tools/${tool.id}`} href={`/tools/${tool.id}`}
underline={selected ? 'always' : 'hover'} underline={selected ? 'always' : 'hover'}
> >
{tool.title} {toolStrings.name}
</LinkI18n> </LinkI18n>
</Fragment> </Fragment>
); );
@@ -212,6 +216,11 @@ const { locale } = Astro.params;
(branch) => (branch) =>
branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH), branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
); );
const toolStrings =
strings.website.tools[
(tool.stringsId ??
tool.id) as keyof typeof strings.website.tools
];
if (unavailableOnBranch) return null; if (unavailableOnBranch) return null;
@@ -265,7 +274,7 @@ const { locale } = Astro.params;
WebkitBackdropFilter: 'blur(4rem)', WebkitBackdropFilter: 'blur(4rem)',
}} }}
> >
<Text>{tool.title}</Text> <Text>{toolStrings.name}</Text>
</Flex> </Flex>
</LinkI18n> </LinkI18n>
</Flex> </Flex>
@@ -3,6 +3,7 @@ import { assertSecret, imgur, ImgurSize } from '@blitzkit/core';
import { CaretRightIcon } from '@radix-ui/react-icons'; import { CaretRightIcon } from '@radix-ui/react-icons';
import { Box, Button, Code, Flex, Heading, Text } from '@radix-ui/themes'; import { Box, Button, Code, Flex, Heading, Text } from '@radix-ui/themes';
import type { Tool } from '../constants/tools'; import type { Tool } from '../constants/tools';
import { getStrings } from '../core/i18n/getStrings';
import { LinkI18n } from './LinkI18n'; import { LinkI18n } from './LinkI18n';
interface Props { interface Props {
@@ -14,6 +15,11 @@ const disabled = tool.branches?.every(
(branch) => branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH), (branch) => branch !== assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH),
); );
const { locale } = Astro.params; const { locale } = Astro.params;
const strings = getStrings(locale);
const toolStrings =
strings.website.tools[
(tool.stringsId??tool.id ) as keyof typeof strings.website.tools
];
--- ---
<style> <style>
@@ -101,7 +107,7 @@ const { locale } = Astro.params;
size={{ initial: '6', sm: tool.significant ? '7' : '5' }} size={{ initial: '6', sm: tool.significant ? '7' : '5' }}
weight="medium" weight="medium"
> >
{tool.title} {toolStrings.name}
</Heading> </Heading>
<Text <Text
align={{ initial: 'center', sm: 'left' }} align={{ initial: 'center', sm: 'left' }}
@@ -115,7 +121,7 @@ const { locale } = Astro.params;
<Code>{assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH)}</Code> <Code>{assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH)}</Code>
</> </>
) : ( ) : (
tool.description toolStrings.description
) )
} }
</Text> </Text>
@@ -132,7 +138,7 @@ const { locale } = Astro.params;
color={tool.button.color} color={tool.button.color}
style={{ cursor: 'inherit' }} style={{ cursor: 'inherit' }}
> >
{tool.button.text} {toolStrings.button}
<CaretRightIcon /> <CaretRightIcon />
</Button> </Button>
) )
+6 -63
View File
@@ -3,15 +3,12 @@ import type { ComponentProps } from 'react';
export interface Tool { export interface Tool {
id: string; id: string;
stringsId?: string;
image: string; image: string;
title: string;
description: string;
disabled?: boolean; disabled?: boolean;
href?: string; href?: string;
pageDescription?: string;
button: { button: {
highContrast?: boolean; highContrast?: boolean;
text: string;
color: ComponentProps<typeof Button>['color']; color: ComponentProps<typeof Button>['color'];
}; };
significant?: boolean; significant?: boolean;
@@ -20,13 +17,8 @@ export interface Tool {
export const tankopediaTool: Tool = { export const tankopediaTool: Tool = {
id: 'tankopedia', id: 'tankopedia',
title: 'Tankopedia',
description: 'Blitz tank encyclopedia',
pageDescription:
'Statistics, armor, and more for all tanks in World of Tanks Blitz',
image: 'D8j6GZx', image: 'D8j6GZx',
button: { button: {
text: 'Find tanks',
color: 'purple', color: 'purple',
}, },
significant: true, significant: true,
@@ -35,13 +27,8 @@ export const tankopediaTool: Tool = {
// export const ratingTool: Tool = { // export const ratingTool: Tool = {
// id: 'rating', // id: 'rating',
// title: 'Rating',
// description: 'Full rating leaderboard',
// pageDescription:
// 'Live and archived full rating leaderboard for World of Tanks Blitz',
// image: 'WEyMZH3', // image: 'WEyMZH3',
// button: { // button: {
// text: 'View leaderboard',
// color: 'orange', // color: 'orange',
// }, // },
// branches: ['main', 'dev'], // branches: ['main', 'dev'],
@@ -49,13 +36,8 @@ export const tankopediaTool: Tool = {
export const compareTool: Tool = { export const compareTool: Tool = {
id: 'compare', id: 'compare',
title: 'Compare',
description: 'Compare tank statistics',
pageDescription:
'Compare tanks statistics and loadouts in World of Tanks Blitz',
image: 'O6VNl6e', image: 'O6VNl6e',
button: { button: {
text: 'Compare tanks',
color: 'crimson', color: 'crimson',
}, },
branches: ['main', 'dev', 'opentest', 'preview'], branches: ['main', 'dev', 'opentest', 'preview'],
@@ -64,13 +46,8 @@ export const compareTool: Tool = {
export const sessionTool: Tool = { export const sessionTool: Tool = {
// TODO: rename to tracker // TODO: rename to tracker
id: 'session', id: 'session',
title: 'Session',
description: 'Live session stats',
pageDescription:
'Track your stats in real time as you play World of Tanks Blitz',
image: 'HdG9sTf', image: 'HdG9sTf',
button: { button: {
text: 'Start tracking',
color: 'blue', color: 'blue',
}, },
branches: ['main', 'dev'], branches: ['main', 'dev'],
@@ -78,35 +55,25 @@ export const sessionTool: Tool = {
export const discordTool: Tool = { export const discordTool: Tool = {
id: 'discord', id: 'discord',
title: 'Discord bot',
description: 'Stats right in Discord',
href: 'https://discord.com/application-directory/1097673957865443370', href: 'https://discord.com/application-directory/1097673957865443370',
image: '0bWE5hC', image: '0bWE5hC',
button: { button: {
text: 'Install now',
color: 'indigo', color: 'indigo',
}, },
}; };
export const moreTool: Tool = { export const moreTool: Tool = {
id: 'more', id: 'more',
title: 'More coming soon',
description: "What we're making",
href: 'https://discord.gg/nDt7AjGJQH', href: 'https://discord.gg/nDt7AjGJQH',
image: '1nPm6VI', image: '1nPm6VI',
button: { button: {
text: 'Join Discord',
color: 'plum', color: 'plum',
}, },
}; };
export const playerStatsTool: Tool = { export const playersTool: Tool = {
id: 'player-stats', id: 'players',
title: 'Player stats',
description: 'Periodical player statistics',
pageDescription: 'Periodical player statistics in World of Tanks Blitz',
button: { button: {
text: 'Lookup player',
color: 'blue', color: 'blue',
}, },
image: 'YelSOfT', image: 'YelSOfT',
@@ -116,11 +83,7 @@ export const playerStatsTool: Tool = {
export const performanceTool: Tool = { export const performanceTool: Tool = {
id: 'performance', id: 'performance',
title: 'Performance',
description: 'Tank performance statistics',
pageDescription: 'Best tanks and their performances in World of Tanks Blitz',
button: { button: {
text: 'View tanks',
color: 'jade', color: 'jade',
}, },
image: 'vOKFB03', image: 'vOKFB03',
@@ -129,12 +92,7 @@ export const performanceTool: Tool = {
export const embedTool: Tool = { export const embedTool: Tool = {
id: 'embed', id: 'embed',
title: 'Embed',
description: 'Embeds for streaming',
pageDescription:
'Create embeds for streaming World of Tanks Blitz with extreme customization',
button: { button: {
text: 'Create now',
color: 'red', color: 'red',
}, },
image: 'Q0faRYg', image: 'Q0faRYg',
@@ -142,20 +100,14 @@ export const embedTool: Tool = {
}; };
export const homeTool: Tool = { export const homeTool: Tool = {
id: '', id: 'home',
title: 'Home', button: { color: 'blue' },
button: { color: 'blue', text: '' },
description: '',
image: 'rUPie9G', image: 'rUPie9G',
}; };
export const galleryTool: Tool = { export const galleryTool: Tool = {
id: 'gallery', id: 'gallery',
title: 'Gallery',
description: 'Blitz player avatars',
pageDescription: 'All World of Tanks Blitz player avatars',
button: { button: {
text: 'Browse all',
color: 'gold', color: 'gold',
}, },
image: 'VhKlknk', image: 'VhKlknk',
@@ -164,12 +116,8 @@ export const galleryTool: Tool = {
export const tierListTool: Tool = { export const tierListTool: Tool = {
id: 'tier-list', id: 'tier-list',
title: 'Tier list', stringsId: 'tier_list',
description: 'Rank Blitz tanks',
pageDescription:
'Explore tier lists ranking tanks from best to worst, based on player ratings and statistical performance in World of Tanks Blitz',
button: { button: {
text: 'Rank tanks',
color: 'orange', color: 'orange',
}, },
image: 'oKKc8Xv', image: 'oKKc8Xv',
@@ -178,12 +126,7 @@ export const tierListTool: Tool = {
export const chartsTool: Tool = { export const chartsTool: Tool = {
id: 'charts', id: 'charts',
title: 'Charts',
description: 'Lots of graphs',
pageDescription:
'Charts and graphs on anything and everything about tanks, players, and events in World of Tanks Blitz',
button: { button: {
text: 'Nerd out',
color: 'bronze', color: 'bronze',
}, },
image: '6fhkljA', image: '6fhkljA',
+8 -10
View File
@@ -1,19 +1,17 @@
import { assertSecret } from '@blitzkit/core'; import { assertSecret, DEFAULT_LOCALE, localizedStrings } from '@blitzkit/core';
const BRANCH_NAMES: Record<string, string> = { export function resolveBranchName(locale = DEFAULT_LOCALE) {
dev: 'beta', const strings = localizedStrings[locale];
opentest: 'opentest',
preview: 'preview',
};
export function resolveBranchName() {
const secret = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH); const secret = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
if ( if (
assertSecret(import.meta.env.MODE) === 'development' && assertSecret(import.meta.env.MODE) === 'development' &&
secret === 'dev' secret === 'dev'
) { ) {
return 'dev'; return strings.common.branches.local;
} else { } else {
return BRANCH_NAMES[secret] as string | undefined; return (strings.common.branches as Record<string, string>)[secret] as
| string
| undefined;
} }
} }
@@ -0,0 +1,5 @@
import { DEFAULT_LOCALE, localizedStrings } from '@blitzkit/core';
export function getStrings(locale: string = DEFAULT_LOCALE) {
return localizedStrings[locale];
}
@@ -0,0 +1,5 @@
export function literals(string: string, literals: string[]) {
return literals.reduce((accumulator, literal, index) => {
return accumulator.replace(`%s${index + 1}`, literal);
}, string);
}
+12 -5
View File
@@ -1,8 +1,11 @@
import { DEFAULT_LOCALE } from '@blitzkit/core';
import { createContext, useContext, type ReactNode } from 'react'; import { createContext, useContext, type ReactNode } from 'react';
import { getStrings } from '../core/i18n/getStrings';
const LocaleContext = createContext<{ locale: string | undefined } | null>( const LocaleContext = createContext<{
null, locale: string;
); localeRaw: string | undefined;
} | null>(null);
interface LocaleProviderProps { interface LocaleProviderProps {
locale: string | undefined; locale: string | undefined;
@@ -11,7 +14,9 @@ interface LocaleProviderProps {
export function LocaleProvider({ locale, children }: LocaleProviderProps) { export function LocaleProvider({ locale, children }: LocaleProviderProps) {
return ( return (
<LocaleContext.Provider value={{ locale }}> <LocaleContext.Provider
value={{ locale: locale ?? DEFAULT_LOCALE, localeRaw: locale }}
>
{children} {children}
</LocaleContext.Provider> </LocaleContext.Provider>
); );
@@ -24,5 +29,7 @@ export function useLocale() {
throw new Error('useLocale must be used within a LocaleProvider'); throw new Error('useLocale must be used within a LocaleProvider');
} }
return { locale: context.locale }; const strings = getStrings(context.locale);
return { locale: context.locale, strings };
} }
@@ -2,9 +2,9 @@
import { import {
assertSecret, assertSecret,
DEFAULT_LOCALE, DEFAULT_LOCALE,
localizedStrings,
SUPPORTED_LOCALES, SUPPORTED_LOCALES,
} from '@blitzkit/core'; } from '@blitzkit/core';
import strings from '@blitzkit/core/lang/en.json';
import { Box, Flex, Heading, Link, Text } from '@radix-ui/themes'; import { Box, Flex, Heading, Link, Text } from '@radix-ui/themes';
import type { GetStaticPaths } from 'astro'; import type { GetStaticPaths } from 'astro';
import { AesonPlug } from '../../components/AesonPlug'; import { AesonPlug } from '../../components/AesonPlug';
@@ -23,10 +23,12 @@ import {
tankopediaTool, tankopediaTool,
tierListTool, tierListTool,
} from '../../constants/tools'; } from '../../constants/tools';
import { getStrings } from '../../core/i18n/getStrings';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
const branch = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH); const branch = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
const { locale } = Astro.params; const locale = Astro.params.locale ?? DEFAULT_LOCALE;
const strings = getStrings(Astro.params.locale);
export const getStaticPaths = (() => { export const getStaticPaths = (() => {
return [ return [
@@ -45,6 +47,7 @@ export const getStaticPaths = (() => {
const selected = const selected =
thisLocale === locale || thisLocale === locale ||
(locale === undefined && thisLocale === DEFAULT_LOCALE); (locale === undefined && thisLocale === DEFAULT_LOCALE);
const localeNames = localizedStrings[thisLocale].common.locales;
return ( return (
<> <>
@@ -55,7 +58,7 @@ export const getStaticPaths = (() => {
highContrast={selected} highContrast={selected}
href={`/${thisLocale === DEFAULT_LOCALE ? '' : thisLocale}`} href={`/${thisLocale === DEFAULT_LOCALE ? '' : thisLocale}`}
> >
{strings.common.locales[thisLocale]} {localeNames[thisLocale as keyof typeof localeNames]}
</Link> </Link>
</> </>
); );
@@ -105,33 +108,24 @@ export const getStaticPaths = (() => {
<Box flexGrow="1" /> <Box flexGrow="1" />
</Flex> </Flex>
<Heading align="center" mt="6">Other cool projects</Heading> <Heading align="center" mt="6"
>{strings.website.home.other_projects.title}</Heading
>
<Flex gap="4" mb="6" direction={{ initial: 'column', sm: 'row' }}> <Flex gap="4" mb="6" direction={{ initial: 'column', sm: 'row' }}>
<ToolCard <ToolCard
tool={{ tool={{
button: { button: { color: 'blue' },
text: 'Analyze', id: 'blitz_analysis',
color: 'blue',
},
description: 'Rich historic average stats',
id: 'blitz-analysis',
image: 'j76YXGl', image: 'j76YXGl',
title: 'BlitzAnalysis[]',
href: 'https://blitzanalysiz.com/', href: 'https://blitzanalysiz.com/',
}} }}
/> />
<ToolCard <ToolCard
tool={{ tool={{
button: { button: { color: 'purple', highContrast: true },
color: 'purple',
text: 'Install',
highContrast: true,
},
image: 'zYrrYbR', image: 'zYrrYbR',
description: 'In-game session stats', id: 'blitz_insider',
id: 'blitz-insider',
title: 'Blitz Insider',
href: 'https://discord.gg/UjWf9eGgtR', href: 'https://discord.gg/UjWf9eGgtR',
}} }}
/> />