feat: localize performance

This commit is contained in:
tresabhi committed 2025-01-07 12:47:55 -06:00
1 parent 7dfdf4c11b
commit 9de41529d1
15 files changed
+134 -76

No files matched your search

+34
View File
@@ -6,6 +6,16 @@
}, },
"common": { "common": {
"player_count_period": {
"total": "Total",
"d_120": "Past 120 days",
"d_90": "Past 90 days",
"d_60": "Past 60 days",
"d_30": "Past month",
"d_7": "Past week",
"d_1": "Yesterday"
},
"branches": { "branches": {
"local": "dev", "local": "dev",
"dev": "beta", "dev": "beta",
@@ -738,6 +748,30 @@
"body": "Estimated stats based on %s1 players. Updated %s2 ago.", "body": "Estimated stats based on %s1 players. Updated %s2 ago.",
"hours": "%s1 hours", "hours": "%s1 hours",
"minutes": "%s1 minutes" "minutes": "%s1 minutes"
},
"player_count": {
"label": "Player count"
},
"table": {
"tanks": {
"header": "Tank",
"total": "Total"
},
"stats": {
"winrate": "Winrate",
"players": "Players",
"damage": "Damage",
"survival": "Survival",
"xp": "XP",
"kills": "Kills",
"spots": "Spots",
"accuracy": "Accuracy",
"shots": "Shots",
"hits": "Hits",
"damage_ratio": "Dmg. ratio",
"damage_taken": "Dmg. taken",
"capture_points": "Cap points"
}
} }
}, },
"gallery": { "gallery": {
+6 -5
View File
@@ -1,11 +1,8 @@
import { merge } from 'lodash-es'; import { merge } from 'lodash-es';
import type en from '../../lang/en.json'; import type en from '../../lang/en.json';
import { assertSecret, DEFAULT_LOCALE, SUPPORTED_LOCALES } from '../blitzkit'; import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '../blitzkit';
import { TranslationTree } from './translator'; import { TranslationTree } from './translator';
const PUBLIC_DEBUG_MISSING_I18N = assertSecret(
import.meta.env.PUBLIC_DEBUG_MISSING_I18N,
);
const files = import.meta.glob('../../lang/*.json', { eager: true }); const files = import.meta.glob('../../lang/*.json', { eager: true });
export const localizedStrings: Record<string, typeof en> = {}; export const localizedStrings: Record<string, typeof en> = {};
@@ -25,7 +22,11 @@ SUPPORTED_LOCALES.forEach((locale) => {
const strings = files[`../../lang/${locale}.json`]; const strings = files[`../../lang/${locale}.json`];
const mergedStrings = merge({}, defaultStrings, strings); const mergedStrings = merge({}, defaultStrings, strings);
if (PUBLIC_DEBUG_MISSING_I18N === 'true') nuke(mergedStrings); /**
* For some reason, this won't load on the server on the first render so I am
* not asserting this one lol
*/
if (import.meta.env.PUBLIC_DEBUG_MISSING_I18N === 'true') nuke(mergedStrings);
localizedStrings[locale] = mergedStrings as typeof en; localizedStrings[locale] = mergedStrings as typeof en;
}); });
@@ -8,6 +8,7 @@ import {
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import type { ComponentProps } from 'react'; import type { ComponentProps } from 'react';
import { radToDeg } from 'three/src/math/MathUtils.js'; import { radToDeg } from 'three/src/math/MathUtils.js';
import { useLocale } from '../../../hooks/useLocale';
import type { import type {
Shot, Shot,
ShotLayer, ShotLayer,
@@ -49,6 +50,8 @@ function LayerEntry({
layer: ShotLayer; layer: ShotLayer;
angle?: number; angle?: number;
}) { }) {
const { locale } = useLocale();
return ( return (
<Flex align="center" gap="2"> <Flex align="center" gap="2">
<Text size="1" color="gray" style={{ width: 12 }}> <Text size="1" color="gray" style={{ width: 12 }}>
@@ -61,7 +64,7 @@ function LayerEntry({
{layer.type === null && ( {layer.type === null && (
<> <>
<Text size="2" color={shotStatusColor} style={{ width: 64 }}> <Text size="2" color={shotStatusColor} style={{ width: 64 }}>
{Math.round(layer.distance * 1000).toLocaleString()} {Math.round(layer.distance * 1000).toLocaleString(locale)}
<Text size="1">mm</Text> <Text size="1">mm</Text>
</Text> </Text>
@@ -73,7 +76,7 @@ function LayerEntry({
{layer.type === ArmorType.External && ( {layer.type === ArmorType.External && (
<Text size="2" color={shotStatusColor} style={{ width: 64 }}> <Text size="2" color={shotStatusColor} style={{ width: 64 }}>
{Math.round(layer.thickness).toLocaleString()} {Math.round(layer.thickness).toLocaleString(locale)}
<Text size="1">mm</Text> <Text size="1">mm</Text>
</Text> </Text>
)} )}
@@ -82,14 +85,14 @@ function LayerEntry({
layer.type === ArmorType.Spaced) && ( layer.type === ArmorType.Spaced) && (
<> <>
<Text size="2" color={shotStatusColor} style={{ width: 80 }}> <Text size="2" color={shotStatusColor} style={{ width: 80 }}>
{Math.round(layer.thicknessAngled).toLocaleString()} {Math.round(layer.thicknessAngled).toLocaleString(locale)}
<Text size="1">{`mm${ <Text size="1">{`mm${
angle === undefined ? '' : ` ${Math.round(radToDeg(angle))}°` angle === undefined ? '' : ` ${Math.round(radToDeg(angle))}°`
}`}</Text> }`}</Text>
</Text> </Text>
<Text size="2" color="gray"> <Text size="2" color="gray">
{Math.round(layer.thickness).toLocaleString()} {Math.round(layer.thickness).toLocaleString(locale)}
<Text size="1">mm</Text> nominal <Text size="1">mm</Text> nominal
</Text> </Text>
</> </>
@@ -103,6 +106,8 @@ interface ShotDisplayCardProps extends CardProps {
} }
export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) { export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
const { locale } = useLocale();
if (!shot) return null; if (!shot) return null;
const outTitleColor = shot.out const outTitleColor = shot.out
@@ -117,7 +122,7 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
<Text color={inTitleColor} weight="bold"> <Text color={inTitleColor} weight="bold">
{shot.in.status !== 'blocked' && shot.in.status !== 'ricochet' && ( {shot.in.status !== 'blocked' && shot.in.status !== 'ricochet' && (
<> <>
{Math.round(shot.damage).toLocaleString()} {Math.round(shot.damage).toLocaleString(locale)}
<Text size="1">hp</Text> <Text size="1">hp</Text>
</> </>
)} )}
@@ -182,7 +187,7 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) {
{shot.out.status !== 'blocked' && {shot.out.status !== 'blocked' &&
shot.out.status !== 'ricochet' && ( shot.out.status !== 'ricochet' && (
<> <>
{Math.round(shot.damage).toLocaleString()} {Math.round(shot.damage).toLocaleString(locale)}
<Text size="1">hp</Text> <Text size="1">hp</Text>
</> </>
)} )}
@@ -16,6 +16,7 @@ import {
} from '@radix-ui/themes'; } from '@radix-ui/themes';
import { times } from 'lodash-es'; import { times } from 'lodash-es';
import { useState } from 'react'; import { useState } from 'react';
import { useLocale } from '../../hooks/useLocale';
import './index.css'; import './index.css';
const DAY_TITLES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; const DAY_TITLES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
@@ -37,6 +38,7 @@ export function DatePicker({
const [yearOffset, setYearOffset] = useState(0); const [yearOffset, setYearOffset] = useState(0);
const [monthOffset, setMonthOffset] = useState(0); const [monthOffset, setMonthOffset] = useState(0);
const date = new Date(year + yearOffset, month + monthOffset, day); const date = new Date(year + yearOffset, month + monthOffset, day);
const { locale } = useLocale();
const daysInLastMonth = new Date( const daysInLastMonth = new Date(
year + yearOffset, year + yearOffset,
@@ -94,11 +96,7 @@ export function DatePicker({
</IconButton> </IconButton>
<Flex gap="1"> <Flex gap="1">
<Text> <Text>{date.toLocaleString(locale, { month: 'long' })}</Text>
{date.toLocaleString(undefined, {
month: 'long',
})}
</Text>
<Popover.Root onOpenChange={() => setYearScroll(0)}> <Popover.Root onOpenChange={() => setYearScroll(0)}>
<Popover.Trigger> <Popover.Trigger>
@@ -2,6 +2,7 @@ import { Flex, Text } from '@radix-ui/themes';
import fuzzysort from 'fuzzysort'; import fuzzysort from 'fuzzysort';
import { times } from 'lodash-es'; import { times } from 'lodash-es';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useLocale } from '../../hooks/useLocale';
import { GalleryEphemeral } from '../../stores/galleryEphemeral'; import { GalleryEphemeral } from '../../stores/galleryEphemeral';
import { GalleryCard } from './Card'; import { GalleryCard } from './Card';
@@ -20,6 +21,7 @@ const PREVIEW_COUNT = 28;
export function GalleryList({ avatars }: GalleryListProps) { export function GalleryList({ avatars }: GalleryListProps) {
const search = GalleryEphemeral.use((state) => state.search); const search = GalleryEphemeral.use((state) => state.search);
const [loadedCards, setLoadedCards] = useState(DEFAULT_LOADED); const [loadedCards, setLoadedCards] = useState(DEFAULT_LOADED);
const { locale } = useLocale();
const filtered = useMemo(() => { const filtered = useMemo(() => {
setLoadedCards(DEFAULT_LOADED); setLoadedCards(DEFAULT_LOADED);
@@ -35,7 +37,7 @@ export function GalleryList({ avatars }: GalleryListProps) {
return ( return (
<> <>
<Text align="center" color="gray"> <Text align="center" color="gray">
{filtered.length.toLocaleString()} avatars {filtered.length.toLocaleString(locale)} avatars
</Text> </Text>
<Flex wrap="wrap" gap="4" justify="center"> <Flex wrap="wrap" gap="4" justify="center">
@@ -4,21 +4,23 @@ import {
CaretUpIcon, CaretUpIcon,
} from '@radix-ui/react-icons'; } from '@radix-ui/react-icons';
import { Button, Flex, Table } from '@radix-ui/themes'; import { Button, Flex, Table } from '@radix-ui/themes';
import { useLocale } from '../../hooks/useLocale';
import { TankPerformanceSort } from '../../stores/tankPerformanceSort'; import { TankPerformanceSort } from '../../stores/tankPerformanceSort';
import { import { TankPerformanceSortTypeNamesArray } from '../../stores/tankPerformanceSort/constants';
TankPerformanceSortTypeNames,
TankPerformanceSortTypeNamesArray,
} from '../../stores/tankPerformanceSort/constants';
import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell'; import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell';
export function Header() { export function Header() {
const sort = TankPerformanceSort.use(); const sort = TankPerformanceSort.use();
const store = TankPerformanceSort.useStore(); const store = TankPerformanceSort.useStore();
const { strings } = useLocale();
return ( return (
<Table.Header> <Table.Header>
<Table.Row align="center"> <Table.Row align="center">
<StickyColumnHeaderCell>Tank</StickyColumnHeaderCell> <StickyColumnHeaderCell>
{strings.website.tools.performance.table.tanks.header}
</StickyColumnHeaderCell>
{TankPerformanceSortTypeNamesArray.map((type) => { {TankPerformanceSortTypeNamesArray.map((type) => {
const isSelected = sort.type === type; const isSelected = sort.type === type;
@@ -42,7 +44,7 @@ export function Header() {
} }
}} }}
> >
{TankPerformanceSortTypeNames[type]} {strings.website.tools.performance.table.stats[type]}
{!isSelected && <CaretSortIcon />} {!isSelected && <CaretSortIcon />}
{isSelected && ( {isSelected && (
@@ -1,4 +1,5 @@
import { Flex, Select, Text } from '@radix-ui/themes'; import { Flex, Select, Text } from '@radix-ui/themes';
import { useLocale } from '../../hooks/useLocale';
import { import {
TankPerformanceEphemeral, TankPerformanceEphemeral,
type PlayerCountPeriod, type PlayerCountPeriod,
@@ -9,10 +10,11 @@ export function PlayerCountControl() {
(state) => state.playerCountPeriod, (state) => state.playerCountPeriod,
); );
const mutateTankPerformanceSort = TankPerformanceEphemeral.useMutation(); const mutateTankPerformanceSort = TankPerformanceEphemeral.useMutation();
const { strings } = useLocale();
return ( return (
<Flex justify="center" align="center" gap="2"> <Flex justify="center" align="center" gap="2">
<Text>Player count</Text> <Text>{strings.website.tools.performance.player_count.label}</Text>
<Select.Root <Select.Root
value={playerCountPeriod} value={playerCountPeriod}
onValueChange={(period) => { onValueChange={(period) => {
@@ -23,13 +25,27 @@ export function PlayerCountControl() {
> >
<Select.Trigger /> <Select.Trigger />
<Select.Content> <Select.Content>
<Select.Item value="total">Total</Select.Item> <Select.Item value="total">
<Select.Item value="d_120">Past 120 days</Select.Item> {strings.common.player_count_period.total}
<Select.Item value="d_90">Past 90 days</Select.Item> </Select.Item>
<Select.Item value="d_60">Past 60 days</Select.Item> <Select.Item value="d_120">
<Select.Item value="d_30">This month</Select.Item> {strings.common.player_count_period.d_120}
<Select.Item value="d_7">This week</Select.Item> </Select.Item>
<Select.Item value="d_1">Yesterday</Select.Item> <Select.Item value="d_90">
{strings.common.player_count_period.d_90}
</Select.Item>
<Select.Item value="d_60">
{strings.common.player_count_period.d_60}
</Select.Item>
<Select.Item value="d_30">
{strings.common.player_count_period.d_30}
</Select.Item>
<Select.Item value="d_7">
{strings.common.player_count_period.d_7}
</Select.Item>
<Select.Item value="d_1">
{strings.common.player_count_period.d_1}
</Select.Item>
</Select.Content> </Select.Content>
</Select.Root> </Select.Root>
</Flex> </Flex>
@@ -3,6 +3,7 @@ import { Table } from '@radix-ui/themes';
import { memo } from 'react'; import { memo } from 'react';
import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions'; import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions';
import { useAveragesExclusionRatio } from '../../hooks/useAveragesExclusionRatio'; import { useAveragesExclusionRatio } from '../../hooks/useAveragesExclusionRatio';
import { useLocale } from '../../hooks/useLocale';
import { TankPerformanceEphemeral } from '../../stores/tankPerformanceEphemeral'; import { TankPerformanceEphemeral } from '../../stores/tankPerformanceEphemeral';
import { TankRowHeaderCell } from '../TankRowHeaderCell'; import { TankRowHeaderCell } from '../TankRowHeaderCell';
@@ -19,6 +20,7 @@ export const TankRow = memo<TankRowProps>(
const playerCountPeriod = TankPerformanceEphemeral.use( const playerCountPeriod = TankPerformanceEphemeral.use(
(state) => state.playerCountPeriod, (state) => state.playerCountPeriod,
); );
const { locale } = useLocale();
return ( return (
<Table.Row> <Table.Row>
@@ -29,13 +31,14 @@ export const TankRow = memo<TankRowProps>(
</Table.Cell> </Table.Cell>
<Table.Cell align="center"> <Table.Cell align="center">
{formatCompact( {formatCompact(
locale,
Math.round(ratio * averages.samples[playerCountPeriod]), Math.round(ratio * averages.samples[playerCountPeriod]),
)} )}
</Table.Cell> </Table.Cell>
<Table.Cell align="center"> <Table.Cell align="center">
{Math.round( {Math.round(
averages.mu.damage_dealt / averages.mu.battles, averages.mu.damage_dealt / averages.mu.battles,
).toLocaleString()} ).toLocaleString(locale)}
</Table.Cell> </Table.Cell>
<Table.Cell align="center"> <Table.Cell align="center">
{Math.round( {Math.round(
@@ -44,7 +47,9 @@ export const TankRow = memo<TankRowProps>(
% %
</Table.Cell> </Table.Cell>
<Table.Cell align="center"> <Table.Cell align="center">
{Math.round(averages.mu.xp / averages.mu.battles).toLocaleString()} {Math.round(averages.mu.xp / averages.mu.battles).toLocaleString(
locale,
)}
</Table.Cell> </Table.Cell>
<Table.Cell align="center"> <Table.Cell align="center">
{(averages.mu.frags / averages.mu.battles).toFixed(2)} {(averages.mu.frags / averages.mu.battles).toFixed(2)}
@@ -67,7 +72,7 @@ export const TankRow = memo<TankRowProps>(
<Table.Cell align="center"> <Table.Cell align="center">
{Math.round( {Math.round(
averages.mu.damage_received / averages.mu.battles, averages.mu.damage_received / averages.mu.battles,
).toLocaleString()} ).toLocaleString(locale)}
</Table.Cell> </Table.Cell>
<Table.Cell align="center"> <Table.Cell align="center">
{(averages.mu.capture_points / averages.mu.battles).toFixed(2)} {(averages.mu.capture_points / averages.mu.battles).toFixed(2)}
@@ -7,6 +7,7 @@ import { Table } from '@radix-ui/themes';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions'; import { awaitableAverageDefinitions } from '../../core/awaitables/averageDefinitions';
import { useAveragesExclusionRatio } from '../../hooks/useAveragesExclusionRatio'; import { useAveragesExclusionRatio } from '../../hooks/useAveragesExclusionRatio';
import { useLocale } from '../../hooks/useLocale';
import { TankPerformanceEphemeral } from '../../stores/tankPerformanceEphemeral'; import { TankPerformanceEphemeral } from '../../stores/tankPerformanceEphemeral';
import { StickyRowHeaderCell } from '../StickyRowHeaderCell'; import { StickyRowHeaderCell } from '../StickyRowHeaderCell';
@@ -25,6 +26,7 @@ export const Total = memo<TotalProps>(
const playerCountPeriod = TankPerformanceEphemeral.use( const playerCountPeriod = TankPerformanceEphemeral.use(
(state) => state.playerCountPeriod, (state) => state.playerCountPeriod,
); );
const { strings, locale } = useLocale();
const ratio = useAveragesExclusionRatio(); const ratio = useAveragesExclusionRatio();
const sum = useCallback( const sum = useCallback(
(slice: (tank: BlitzkitStats) => number) => { (slice: (tank: BlitzkitStats) => number) => {
@@ -54,20 +56,23 @@ export const Total = memo<TotalProps>(
return ( return (
<Table.Row> <Table.Row>
<StickyRowHeaderCell style={{ overflow: 'hidden', display: 'flex' }}> <StickyRowHeaderCell style={{ overflow: 'hidden', display: 'flex' }}>
Total {strings.website.tools.performance.table.tanks.total}
</StickyRowHeaderCell> </StickyRowHeaderCell>
<Table.Cell align="center">{(winrate * 100).toFixed(1)}%</Table.Cell> <Table.Cell align="center">{(winrate * 100).toFixed(1)}%</Table.Cell>
<Table.Cell align="center"> <Table.Cell align="center">
{formatCompact(ratio * averageDefinitions.samples[playerCountPeriod])} {formatCompact(
locale,
ratio * averageDefinitions.samples[playerCountPeriod],
)}
</Table.Cell> </Table.Cell>
<Table.Cell align="center"> <Table.Cell align="center">
{Math.round(damage).toLocaleString()} {Math.round(damage).toLocaleString(locale)}
</Table.Cell> </Table.Cell>
<Table.Cell align="center"> <Table.Cell align="center">
{Math.round(survival * 100).toFixed(1)}% {Math.round(survival * 100).toFixed(1)}%
</Table.Cell> </Table.Cell>
<Table.Cell align="center"> <Table.Cell align="center">
{Math.round(xp).toLocaleString()} {Math.round(xp).toLocaleString(locale)}
</Table.Cell> </Table.Cell>
<Table.Cell align="center">{kills.toFixed(2)}</Table.Cell> <Table.Cell align="center">{kills.toFixed(2)}</Table.Cell>
<Table.Cell align="center">{spots.toFixed(2)}</Table.Cell> <Table.Cell align="center">{spots.toFixed(2)}</Table.Cell>
@@ -76,7 +81,7 @@ export const Total = memo<TotalProps>(
<Table.Cell align="center">{hits.toFixed(1)}</Table.Cell> <Table.Cell align="center">{hits.toFixed(1)}</Table.Cell>
<Table.Cell align="center">{damageRatio.toFixed(2)}</Table.Cell> <Table.Cell align="center">{damageRatio.toFixed(2)}</Table.Cell>
<Table.Cell align="center"> <Table.Cell align="center">
{Math.round(damageReceived).toLocaleString()} {Math.round(damageReceived).toLocaleString(locale)}
</Table.Cell> </Table.Cell>
<Table.Cell align="center">{capturePoints.toFixed(2)}</Table.Cell> <Table.Cell align="center">{capturePoints.toFixed(2)}</Table.Cell>
</Table.Row> </Table.Row>
@@ -187,7 +187,7 @@ export function MetaSection() {
} price`} } price`}
> >
<Flex align="center" gap="1"> <Flex align="center" gap="1">
{protagonist.price.value.toLocaleString()} {protagonist.price.value.toLocaleString(locale)}
<img <img
style={{ width: '1em', height: '1em' }} style={{ width: '1em', height: '1em' }}
alt={TankPriceType[protagonist.price.type]} alt={TankPriceType[protagonist.price.type]}
@@ -203,7 +203,7 @@ export function MetaSection() {
</Listing> </Listing>
<Listing label="Sale price"> <Listing label="Sale price">
<Flex align="center" gap="1"> <Flex align="center" gap="1">
{(protagonist.price.value / 2).toLocaleString()} {(protagonist.price.value / 2).toLocaleString(locale)}
<img <img
style={{ width: '1em', height: '1em' }} style={{ width: '1em', height: '1em' }}
alt={TankPriceType[protagonist.price.type]} alt={TankPriceType[protagonist.price.type]}
@@ -223,7 +223,7 @@ export function MetaSection() {
{( {(
protagonist.research_cost.research_cost_type! protagonist.research_cost.research_cost_type!
.value as number .value as number
).toLocaleString()} ).toLocaleString(locale)}
<img <img
style={{ width: '1em', height: '1em' }} style={{ width: '1em', height: '1em' }}
alt="xp" alt="xp"
@@ -3,6 +3,7 @@ import { CaretLeftIcon, CaretRightIcon, PlusIcon } from '@radix-ui/react-icons';
import { Flex, Heading, IconButton, ScrollArea, Text } from '@radix-ui/themes'; import { Flex, Heading, IconButton, ScrollArea, Text } from '@radix-ui/themes';
import { Fragment, useEffect, useMemo, useRef, useState } from 'react'; import { Fragment, useEffect, useMemo, useRef, useState } from 'react';
import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions'; import { awaitableTankDefinitions } from '../../../core/awaitables/tankDefinitions';
import { useLocale } from '../../../hooks/useLocale';
import { Duel } from '../../../stores/duel'; import { Duel } from '../../../stores/duel';
import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral'; import { TankopediaEphemeral } from '../../../stores/tankopediaEphemeral';
import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps'; import type { MaybeSkeletonComponentProps } from '../../../types/maybeSkeletonComponentProps';
@@ -16,6 +17,7 @@ const tankDefinitions = await awaitableTankDefinitions;
export const XP_MULTIPLIERS = [1, 2, 3, 4, 5, 10]; export const XP_MULTIPLIERS = [1, 2, 3, 4, 5, 10];
export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) { export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
const { locale } = useLocale();
const master = Duel.use((state) => state.protagonist.tank); const master = Duel.use((state) => state.protagonist.tank);
const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier); const xpMultiplier = TankopediaEphemeral.use((state) => state.xpMultiplier);
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
@@ -105,7 +107,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
objectPosition: 'center', objectPosition: 'center',
}} }}
/> />
{Math.round(totalXp / xpMultiplier).toLocaleString()} {Math.round(totalXp / xpMultiplier).toLocaleString(locale)}
</Flex> </Flex>
</Text> </Text>
@@ -121,7 +123,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
objectPosition: 'center', objectPosition: 'center',
}} }}
/> />
{totalCredits.toLocaleString()} {totalCredits.toLocaleString(locale)}
</Flex> </Flex>
</Text> </Text>
</Flex> </Flex>
@@ -39,6 +39,7 @@ import { StickyTableRoot } from '../../../../components/StickyTableRoot';
import { TankRowHeaderCell } from '../../../../components/TankRowHeaderCell'; import { TankRowHeaderCell } from '../../../../components/TankRowHeaderCell';
import { awaitableAverageDefinitions } from '../../../../core/awaitables/averageDefinitions'; import { awaitableAverageDefinitions } from '../../../../core/awaitables/averageDefinitions';
import { awaitableTankDefinitions } from '../../../../core/awaitables/tankDefinitions'; import { awaitableTankDefinitions } from '../../../../core/awaitables/tankDefinitions';
import { useLocale } from '../../../../hooks/useLocale';
import { Session, type SessionTracking } from '../../../../stores/session'; import { Session, type SessionTracking } from '../../../../stores/session';
const [tankDefinitions, averageDefinitions] = await Promise.all([ const [tankDefinitions, averageDefinitions] = await Promise.all([
@@ -55,6 +56,7 @@ export function Page() {
} }
function Content() { function Content() {
const { locale } = useLocale();
const [showSearch, setShowSearch] = useState(false); const [showSearch, setShowSearch] = useState(false);
const [searching, setSearching] = useState(false); const [searching, setSearching] = useState(false);
const [searchResults, setSearchResults] = useState<AccountListWithServer>([]); const [searchResults, setSearchResults] = useState<AccountListWithServer>([]);
@@ -265,7 +267,7 @@ function Content() {
<Flex mt="2" mb="2" direction="column" gap="2"> <Flex mt="2" mb="2" direction="column" gap="2">
<Heading size="5">Tracking {accountInfo?.nickname}</Heading> <Heading size="5">Tracking {accountInfo?.nickname}</Heading>
<Text color="gray"> <Text color="gray">
Since {new Date(session.player.since).toLocaleString()} Since {new Date(session.player.since).toLocaleString(locale)}
</Text> </Text>
</Flex> </Flex>
@@ -1,14 +1,14 @@
--- ---
import { import {
asset, asset,
createDefaultProvisions, createDefaultProvisions,
createDefaultSkills, createDefaultSkills,
fetchEquipmentDefinitions, fetchEquipmentDefinitions,
fetchModelDefinitions, fetchModelDefinitions,
fetchProvisionDefinitions, fetchProvisionDefinitions,
fetchSkillDefinitions, fetchSkillDefinitions,
fetchTankDefinitions, fetchTankDefinitions,
TIER_ROMAN_NUMERALS, TIER_ROMAN_NUMERALS,
} from '@blitzkit/core'; } from '@blitzkit/core';
import strings from '@blitzkit/core/lang/en-US.json'; import strings from '@blitzkit/core/lang/en-US.json';
import type { GetStaticPaths } from 'astro'; import type { GetStaticPaths } from 'astro';
@@ -31,8 +31,8 @@ export const getStaticPaths = (async () => {
.flat(); .flat();
}) satisfies GetStaticPaths; }) satisfies GetStaticPaths;
const { id, locale } = Astro.params; const {id} = Astro.params;
const locale = Astro.params.locale ?? DEFAULT_LOCALE;
const tankDefinitions = await fetchTankDefinitions(); const tankDefinitions = await fetchTankDefinitions();
const skillDefinitions = await fetchSkillDefinitions(); const skillDefinitions = await fetchSkillDefinitions();
const provisionDefinitions = await fetchProvisionDefinitions(); const provisionDefinitions = await fetchProvisionDefinitions();
@@ -77,7 +77,7 @@ const description = `${
name name
} armor, statistics, equipment, provisions, and more. ${Math.round( } armor, statistics, equipment, provisions, and more. ${Math.round(
characteristics.dpm, characteristics.dpm,
).toLocaleString()} DPM, ${characteristics.damage.toFixed(0)}hp alpha, ${characteristics.penetration.toFixed( ).toLocaleString(locale)} DPM, ${characteristics.damage.toFixed(0)}hp alpha, ${characteristics.penetration.toFixed(
0, 0,
)}mm penetration, ${characteristics.speedForwards.toFixed( )}mm penetration, ${characteristics.speedForwards.toFixed(
0, 0,
@@ -1,21 +1,6 @@
import en from '@blitzkit/core/lang/en.json';
import type { TankPerformanceSortType } from '.'; import type { TankPerformanceSortType } from '.';
export const TankPerformanceSortTypeNames = {
winrate: 'Winrate',
players: 'Players',
damage: 'Damage',
survival: 'Survival',
xp: 'XP',
kills: 'Kills',
spots: 'Spots',
accuracy: 'Accuracy',
shots: 'Shots',
hits: 'Hits',
damageRatio: 'Dmg. ratio',
damageTaken: 'Dmg. taken',
capturePoints: 'Cap points',
} as const;
export const TankPerformanceSortTypeNamesArray = Object.keys( export const TankPerformanceSortTypeNamesArray = Object.keys(
TankPerformanceSortTypeNames, en.website.tools.performance.table.stats,
) as TankPerformanceSortType[]; ) as TankPerformanceSortType[];
@@ -1,8 +1,9 @@
import type en from '@blitzkit/core/lang/en.json';
import { create } from 'zustand'; import { create } from 'zustand';
import { createContextualStore } from '../../core/zustand/createContextualStore'; import { createContextualStore } from '../../core/zustand/createContextualStore';
import { TankPerformanceSortTypeNames } from './constants';
export type TankPerformanceSortType = keyof typeof TankPerformanceSortTypeNames; export type TankPerformanceSortType =
keyof typeof en.website.tools.performance.table.stats;
export interface TankPerformanceSort { export interface TankPerformanceSort {
type: TankPerformanceSortType; type: TankPerformanceSortType;