From e0c897025e031d303b9a74bad6f6ec59e186f3bf Mon Sep 17 00:00:00 2001 From: tresabhi <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Jan 2025 20:56:30 -0600 Subject: [PATCH] feat: localize tankopedia sandbox --- packages/core/lang/en.json | 91 ++++++- .../Armor/components/ShotDisplay.tsx | 6 +- .../Armor/components/ShotDisplayCard.tsx | 115 ++++----- .../Options/components/CustomShellButton.tsx | 82 +++++-- .../Options/components/Thicknesses.tsx | 229 ++++++++++-------- .../HeroSection/components/Options/index.tsx | 83 ++++--- 6 files changed, 398 insertions(+), 208 deletions(-) diff --git a/packages/core/lang/en.json b/packages/core/lang/en.json index 90eaece70..a44b3035e 100644 --- a/packages/core/lang/en.json +++ b/packages/core/lang/en.json @@ -6,6 +6,20 @@ }, "common": { + "units_suffix": { + "m": "%s1m", + "mm": "%s1mm", + "deg": "%s1°", + "hp": "%s1hp" + }, + + "shells": { + "ap": "AP", + "apcr": "APCR", + "heat": "HEAT", + "he": "HE" + }, + "player_count_period": { "total": "Total", "d_120": "Past 120 days", @@ -812,7 +826,82 @@ "description": "Blitz tank encyclopedia", "seo_description": "Statistics, armor, and more for all tanks in World of Tanks Blitz", "tank_seo_title": "%s1 - Tier %s2 %s3 %s4 | %s5 Tankopedia | World of Tanks Blitz (WoTB)", - "tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health." + "tank_seo_description": "%s1 armor, statistics, equipment, provisions, and more. %s2 DPM, %s3hp alpha, %s4mm penetration, %s5kph top speed, %s6hp health.", + "sandbox": { + "poses": { + "hull_down": "Hull down", + "face_hug": "Face hug", + "default": "Default" + }, + + "settings": { + "armor": "Armor", + "green_penetration": "Green penetration", + "hide_model_under_armor": "Hide model under armor", + "opaque": "Opaque", + "dev_wireframe": "DEV: wireframe", + "environment": "Environment", + "show_grid": "Show grid" + }, + + "screenshot": { + "download": "Download", + "copy": "Copy" + }, + + "model": { + "name": "Model" + }, + "dynamic": { + "name": "Dynamic armor", + + "custom_shell": { + "type": "Type", + "caliber": "Caliber", + "penetration": "Penetration", + "normalization": "Normalization", + "ricochet": "Ricochet", + "explosion_radius": "Explosion radius", + "exit": "Exit", + "reset": "Reset" + }, + + "advanced": "Advanced highlighting", + "shooter": "Shooter:", + + "shot_card": { + "element": { + "primary": "Primary", + "spaced": "Spaced", + "external": "External", + "gap": "Gap", + "gun": "Gun", + "track": "Tracks" + }, + "status": { + "penetration": "%s1hp penetration", + "blocked": "Blocked", + "ricochet": "Ricochet", + "splash": "%s1hp splash" + }, + "stats": { + "thickness_and_angle": "%s1mm %s2°", + "nominal": "%s1mm nominal", + "ricochet_loss": "%s1% penetration" + } + } + }, + "static": { + "name": "Static armor", + "primary": "Primary", + "spaced": "Spaced", + "external": "External", + "edit": { + "label": "Edit", + "reset": "Reset" + } + } + } }, "compare": { "name": "Compare", diff --git a/packages/website/src/components/Armor/components/ShotDisplay.tsx b/packages/website/src/components/Armor/components/ShotDisplay.tsx index cc5a966c9..7cdd9027d 100644 --- a/packages/website/src/components/Armor/components/ShotDisplay.tsx +++ b/packages/website/src/components/Armor/components/ShotDisplay.tsx @@ -16,6 +16,7 @@ import { Path, Quaternion, } from 'three'; +import { LocaleProvider, useLocale } from '../../../hooks/useLocale'; import { TankopediaEphemeral, type ShotLayerNonExternal, @@ -45,6 +46,7 @@ export function ShotDisplay() { const splashRadiusWrapper = useRef(null); const splashRadiusMaterial = useRef(null); let animationStartTime: number | null = null; + const { locale } = useLocale(); useEffect(() => { if (shot?.splashRadius !== undefined) { @@ -180,7 +182,9 @@ export function ShotDisplay() { mt={tracerGoingUp ? '9' : '0'} pt={tracerGoingUp ? '4' : '0'} > - + + + diff --git a/packages/website/src/components/Armor/components/ShotDisplayCard.tsx b/packages/website/src/components/Armor/components/ShotDisplayCard.tsx index 0a384daea..7fad8aee1 100644 --- a/packages/website/src/components/Armor/components/ShotDisplayCard.tsx +++ b/packages/website/src/components/Armor/components/ShotDisplayCard.tsx @@ -8,34 +8,18 @@ import { } from '@radix-ui/themes'; import type { ComponentProps } from 'react'; import { radToDeg } from 'three/src/math/MathUtils.js'; +import { literals } from '../../../core/i18n/literals'; import { useLocale } from '../../../hooks/useLocale'; -import type { - Shot, - ShotLayer, - ShotStatus, -} from '../../../stores/tankopediaEphemeral'; +import type { Shot, ShotLayer } from '../../../stores/tankopediaEphemeral'; import { shotStatusColors } from './ShotDisplay'; import { ArmorType } from './SpacedArmorScene'; -import type { ExternalModuleVariant } from './SpacedArmorSceneComponent'; -export const layerTypeNames: Record = { - [ArmorType.Primary]: 'Primary', - [ArmorType.Spaced]: 'Spaced', - [ArmorType.External]: 'External', - null: 'Gap', -}; - -const externalLayerNames: Record = { - gun: 'Gun', - track: 'Tracks', -}; - -const shotStatusNames: Record = { - blocked: 'Blocked', - ricochet: 'Ricochet', - penetration: 'penetration', - splash: 'splash', -}; +export const layerTypeNames = { + [ArmorType.Primary]: 'primary', + [ArmorType.Spaced]: 'spaced', + [ArmorType.External]: 'external', + null: 'gap', +} as const; function LayerEntry({ layerIndex, @@ -50,7 +34,7 @@ function LayerEntry({ layer: ShotLayer; angle?: number; }) { - const { locale } = useLocale(); + const { locale, strings } = useLocale(); return ( @@ -64,20 +48,26 @@ function LayerEntry({ {layer.type === null && ( <> - {Math.round(layer.distance * 1000).toLocaleString(locale)} - mm + {literals(strings.common.units_suffix.mm, [ + Math.round(layer.distance * 1000).toLocaleString(locale), + ])} - {Math.max(-100, Math.round(-50 * layer.distance))}% penetration + {literals( + strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats + .ricochet_loss, + [Math.max(-100, -50 * layer.distance).toFixed(0)], + )} )} {layer.type === ArmorType.External && ( - {Math.round(layer.thickness).toLocaleString(locale)} - mm + {literals(strings.common.units_suffix.mm, [ + Math.round(layer.thickness).toLocaleString(locale), + ])} )} @@ -85,15 +75,24 @@ function LayerEntry({ layer.type === ArmorType.Spaced) && ( <> - {Math.round(layer.thicknessAngled).toLocaleString(locale)} - {`mm${ - angle === undefined ? '' : ` ${Math.round(radToDeg(angle))}°` - }`} + {literals( + angle === undefined + ? strings.common.units_suffix.mm + : strings.website.tools.tankopedia.sandbox.dynamic.shot_card + .stats.thickness_and_angle, + [ + Math.round(layer.thicknessAngled).toLocaleString(locale), + radToDeg(angle ?? 0).toFixed(0), + ], + )} - {Math.round(layer.thickness).toLocaleString(locale)} - mm nominal + {literals( + strings.website.tools.tankopedia.sandbox.dynamic.shot_card.stats + .nominal, + [Math.round(layer.thickness).toLocaleString(locale)], + )} )} @@ -106,7 +105,7 @@ interface ShotDisplayCardProps extends CardProps { } export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) { - const { locale } = useLocale(); + const { locale, strings } = useLocale(); if (!shot) return null; @@ -120,21 +119,23 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) { - {shot.in.status !== 'blocked' && shot.in.status !== 'ricochet' && ( - <> - {Math.round(shot.damage).toLocaleString(locale)} - hp - + {literals( + strings.website.tools.tankopedia.sandbox.dynamic.shot_card.status[ + shot.in.status + ], + [Math.round(shot.damage).toLocaleString(locale)], )} - {` ${shotStatusNames[shot.in.status]} `} {shot.in.layers.map((layer, index) => { const layerName = - layer.type === ArmorType.External - ? externalLayerNames[layer.variant] - : layerTypeNames[`${layer.type}`]; + strings.website.tools.tankopedia.sandbox.dynamic.shot_card + .element[ + layer.type === ArmorType.External + ? layer.variant + : layerTypeNames[`${layer.type}`] + ]; const layerIndex = layer.type === null ? undefined @@ -184,22 +185,22 @@ export function ShotDisplayCard({ shot, ...props }: ShotDisplayCardProps) { - {shot.out.status !== 'blocked' && - shot.out.status !== 'ricochet' && ( - <> - {Math.round(shot.damage).toLocaleString(locale)} - hp - - )} - {` ${shotStatusNames[shot.out.status]} `} + {literals( + strings.website.tools.tankopedia.sandbox.dynamic.shot_card + .status[shot.out.status], + [Math.round(shot.damage).toLocaleString(locale)], + )} {shot.out.layers.map((layer, index) => { const layerName = - layer.type === ArmorType.External - ? externalLayerNames[layer.variant] - : layerTypeNames[`${layer.type}`]; + strings.website.tools.tankopedia.sandbox.dynamic.shot_card + .element[ + layer.type === ArmorType.External + ? layer.variant + : layerTypeNames[`${layer.type}`] + ]; const shiftedIndex = index + shot.out!.layers.length; const layerIndex = layer.type === null diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/CustomShellButton.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/CustomShellButton.tsx index 07e08b576..bda1e04dc 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/CustomShellButton.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/CustomShellButton.tsx @@ -11,6 +11,8 @@ import { } from '@radix-ui/themes'; import { throttle } from 'lodash-es'; import { type ChangeEvent } from 'react'; +import { literals } from '../../../../../../core/i18n/literals'; +import { useLocale } from '../../../../../../hooks/useLocale'; import { Duel } from '../../../../../../stores/duel'; import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral'; @@ -19,6 +21,7 @@ export function CustomShellButton() { const gun = Duel.use((state) => state.protagonist.gun); const antagonistShell = Duel.use((state) => state.antagonist.shell); const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); + const { strings } = useLocale(); return ( - {/* Custom shell */} - {customShell && ( <> - Type + + { + strings.website.tools.tankopedia.sandbox.dynamic + .custom_shell.type + } + { @@ -99,16 +105,29 @@ export function CustomShellButton() { - AP - APCR - HEAT - HE + + {strings.common.shells.ap} + + + {strings.common.shells.apcr} + + + {strings.common.shells.heat} + + + {strings.common.shells.he} + - Caliber + + { + strings.website.tools.tankopedia.sandbox.dynamic + .custom_shell.caliber + } + - mm + + {literals(strings.common.units_suffix.mm, [''])} + - Penetration + + { + strings.website.tools.tankopedia.sandbox.dynamic + .custom_shell.penetration + } + - mm + + {literals(strings.common.units_suffix.mm, [''])} + @@ -168,7 +196,12 @@ export function CustomShellButton() { customShell.type === ShellType.APCR) && ( <> - Normalization + + { + strings.website.tools.tankopedia.sandbox.dynamic + .custom_shell.normalization + } + - {Math.round(customShell.normalization ?? 0)}° + {literals(strings.common.units_suffix.deg, [ + (customShell.normalization ?? 0).toFixed(0), + ])} - Ricochet + + { + strings.website.tools.tankopedia.sandbox.dynamic + .custom_shell.ricochet + } + - {Math.round(customShell.ricochet ?? 90)}° + {literals(strings.common.units_suffix.deg, [ + (customShell.ricochet ?? 90).toFixed(0), + ])} diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx index e818b5f08..67e709548 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/components/Thicknesses.tsx @@ -1,6 +1,8 @@ import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons'; -import { Box, Button, Checkbox, Flex, Text } from '@radix-ui/themes'; +import { Button, Checkbox, Flex, Text } from '@radix-ui/themes'; import { Suspense } from 'react'; +import { literals } from '../../../../../../core/i18n/literals'; +import { useLocale } from '../../../../../../hooks/useLocale'; import { TankopediaEphemeral } from '../../../../../../stores/tankopediaEphemeral'; import { TankopediaPersistent } from '../../../../../../stores/tankopediaPersistent'; import type { ThicknessRange } from '../../../../../Armor/components/StaticArmor'; @@ -24,6 +26,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) { const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); const editStatic = TankopediaEphemeral.use((state) => state.editStatic); const tankopediaEphemeralStore = TankopediaEphemeral.useStore(); + const { strings } = useLocale(); return ( - { - mutateTankopediaPersistent((draft) => { - draft.showPrimaryArmor = !draft.showPrimaryArmor; - }); - mutateTankopediaEphemeral((draft) => { - draft.highlightArmor = undefined; - }); - }} - > - - - {(thicknessRange.value * 1.5).toFixed(0)} - - + + { + mutateTankopediaPersistent((draft) => { + draft.showPrimaryArmor = !draft.showPrimaryArmor; + }); + mutateTankopediaEphemeral((draft) => { + draft.highlightArmor = undefined; + }); + }} + > + + + {literals(strings.common.units_suffix.mm, [ + (thicknessRange.value * 1.5).toFixed(0), + ])} + + + {strings.website.tools.tankopedia.sandbox.static.primary} + + + {literals(strings.common.units_suffix.mm, ['0'])} + + + + {showPrimaryArmor ? : } - Primary - - 0 - - { + mutateTankopediaPersistent((draft) => { + draft.showSpacedArmor = !draft.showSpacedArmor; + }); + mutateTankopediaEphemeral((draft) => { + draft.highlightArmor = undefined; + }); }} - /> - + > + + + {literals(strings.common.units_suffix.mm, [ + thicknessRange.value.toFixed(0), + ])} + + + {strings.website.tools.tankopedia.sandbox.static.spaced} + + + {literals(strings.common.units_suffix.mm, ['0'])} + + - { - mutateTankopediaPersistent((draft) => { - draft.showSpacedArmor = !draft.showSpacedArmor; - }); - mutateTankopediaEphemeral((draft) => { - draft.highlightArmor = undefined; - }); - }} - > - - - {thicknessRange.value.toFixed(0)} - - + {showSpacedArmor ? : } - Spaced - - 0 - - { + mutateTankopediaPersistent((draft) => { + draft.showExternalModules = !draft.showExternalModules; + }); + mutateTankopediaEphemeral((draft) => { + draft.highlightArmor = undefined; + }); }} - /> - + > + + + {strings.website.tools.tankopedia.sandbox.static.external} + + - { - mutateTankopediaPersistent((draft) => { - draft.showExternalModules = !draft.showExternalModules; - }); - mutateTankopediaEphemeral((draft) => { - draft.highlightArmor = undefined; - }); - }} - > - - + {showExternalModules ? : } - External + + + + + + + { + mutateTankopediaEphemeral((draft) => { + draft.editStatic = !draft.editStatic; + draft.highlightArmor = undefined; + }); + }} + > + + {strings.website.tools.tankopedia.sandbox.static.edit.label} + + - - - - - - { - mutateTankopediaEphemeral((draft) => { - draft.editStatic = !draft.editStatic; - draft.highlightArmor = undefined; - }); - }} - > - Edit - - diff --git a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx index bce2ac859..14adbba94 100644 --- a/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx +++ b/packages/website/src/components/Tankopedia/HeroSection/components/Options/index.tsx @@ -30,9 +30,11 @@ import { } from '@radix-ui/themes'; import { Suspense, useState, type RefObject } from 'react'; import { Pose, poseEvent } from '../../../../../core/blitzkit/pose'; +import { literals } from '../../../../../core/i18n/literals'; import { useEquipment } from '../../../../../hooks/useEquipment'; import { useFullScreen } from '../../../../../hooks/useFullScreen'; import { useFullscreenAvailability } from '../../../../../hooks/useFullscreenAvailability'; +import { useLocale } from '../../../../../hooks/useLocale'; import { App } from '../../../../../stores/app'; import { Duel } from '../../../../../stores/duel'; import { @@ -87,6 +89,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) { const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets); const mutateTankopediaPersistent = TankopediaPersistent.useMutation(); const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); + const { strings } = useLocale(); return ( <> @@ -124,13 +127,14 @@ export function Options({ thicknessRange, canvas }: OptionsProps) { > {!hasCustomShell && ( - {( - resolvePenetrationCoefficient( - hasCalibratedShells, - antagonistShell.type, - ) * antagonistShell.penetration.near - ).toFixed(0)} - mm + {literals(strings.common.units_suffix.mm, [ + ( + resolvePenetrationCoefficient( + hasCalibratedShells, + antagonistShell.type, + ) * antagonistShell.penetration.near + ).toFixed(0), + ])} )} {antagonistUniqueGuns.length > 1 && ( @@ -198,10 +202,11 @@ export function Options({ thicknessRange, canvas }: OptionsProps) { } secondaryDiscriminator={ - {Math.round( - gun.gun_type!.value.base.shells[0].caliber, - )} - mm + {literals(strings.common.units_suffix.mm, [ + gun.gun_type!.value.base.shells[0].caliber.toFixed( + 0, + ), + ])} } /> @@ -341,13 +346,13 @@ export function Options({ thicknessRange, canvas }: OptionsProps) { > - Advanced highlighting + {strings.website.tools.tankopedia.sandbox.dynamic.advanced} - Shooter: + {strings.website.tools.tankopedia.sandbox.dynamic.shooter} - + - + - + @@ -441,15 +452,15 @@ export function Options({ thicknessRange, canvas }: OptionsProps) { poseEvent.emit(Pose.HullDown)}> - Hull down + {strings.website.tools.tankopedia.sandbox.poses.hull_down} poseEvent.emit(Pose.FaceHug)}> - Face hug + {strings.website.tools.tankopedia.sandbox.poses.face_hug} poseEvent.emit(Pose.Default)}> - Default + {strings.website.tools.tankopedia.sandbox.poses.default} @@ -464,7 +475,9 @@ export function Options({ thicknessRange, canvas }: OptionsProps) { {display === TankopediaDisplay.DynamicArmor && ( <> - Armor + + {strings.website.tools.tankopedia.sandbox.settings.armor} + - Green penetration + { + strings.website.tools.tankopedia.sandbox.settings + .green_penetration + } - Hide tank model under armor + { + strings.website.tools.tankopedia.sandbox.settings + .hide_model_under_armor + } - Opaque + {strings.website.tools.tankopedia.sandbox.settings.opaque} {developerMode && ( @@ -508,13 +527,18 @@ export function Options({ thicknessRange, canvas }: OptionsProps) { }); }} > - DEV: Wireframe + { + strings.website.tools.tankopedia.sandbox.settings + .dev_wireframe + } )} )} - Environment + + {strings.website.tools.tankopedia.sandbox.settings.environment} + - Show grid + {strings.website.tools.tankopedia.sandbox.settings.show_grid} @@ -557,7 +581,10 @@ export function Options({ thicknessRange, canvas }: OptionsProps) { }} > - Download + { + strings.website.tools.tankopedia.sandbox.screenshot + .download + } @@ -576,7 +603,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) { }} > - Copy + {strings.website.tools.tankopedia.sandbox.screenshot.copy}