feat: strings

This commit is contained in:
TrèsAbhi committed 2025-05-25 15:18:22 -05:00
1 parent a2f1593a0e
commit 179ce62a55
17 files changed
+400 -35

No files matched your search

+8 -1
View File
@@ -1,4 +1,11 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*", "../core/src/blitzkit/supportedLocales.ts", "../i18n/src/unwrapper.ts"]
"include": [
"src/**/*",
"../core/src/blitzkit/supportedLocales.ts",
"../i18n/src/unwrapper.ts"
],
"compilerOptions": {
"baseUrl": "../../"
}
}
+1
View File
@@ -46,4 +46,5 @@ export * from './gallery';
export * from './metaSortTank';
export * from './popularTanks';
export * from './romanize';
export * from './slugify';
export * from './youtubers';
+16
View File
@@ -0,0 +1,16 @@
import { deburr } from 'lodash-es';
const nonAlphanumericRegex = /[^a-z0-9]/g;
const multipleDashesRegex = /--+/g;
const trailingDashRegex = /-$/g;
const leadingDashRegex = /^-/g;
export function slugify(name: string) {
let slug = deburr(name).toLowerCase();
slug = slug.replaceAll(nonAlphanumericRegex, '-');
slug = slug.replaceAll(multipleDashesRegex, '-');
slug = slug.replaceAll(trailingDashRegex, '');
slug = slug.replaceAll(leadingDashRegex, '');
return slug;
}
+4 -1
View File
@@ -1,4 +1,7 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*", "../i18n/src/strings.ts"]
"include": ["src/**/*", "../i18n/src/strings.ts"],
"compilerOptions": {
"baseUrl": "../../"
}
}
@@ -22,6 +22,7 @@ import {
ResearchCost,
ShellType,
SkillDefinitions,
slugify,
TankClass,
TankDefinitions,
TankPrice,
@@ -37,7 +38,6 @@ import {
SUPPORTED_LOCALES,
} from '@blitzkit/i18n';
import { readdir } from 'fs/promises';
import { deburr } from 'lodash-es';
import { parse as parsePath } from 'path';
import { Vector3Tuple } from 'three';
import { parse as parseYaml } from 'yaml';
@@ -49,11 +49,6 @@ import { DATA } from './constants';
import { Avatar } from './skillIcons';
import { TankParameters } from './tankIcons';
const nonAlphanumericRegex = /[^a-z0-9]/g;
const multipleDashesRegex = /--+/g;
const trailingDashRegex = /-$/g;
const leadingDashRegex = /^-/g;
const nationSlugDiscriminators = {
china: 'cn',
european: 'eu',
@@ -740,12 +735,7 @@ export async function definitions() {
? getString(tank.shortUserString)
: undefined) ?? getString(tank.userString)
).locales.en;
let slug = deburr(name).toLowerCase();
slug = slug.replaceAll(nonAlphanumericRegex, '-');
slug = slug.replaceAll(multipleDashesRegex, '-');
slug = slug.replaceAll(trailingDashRegex, '');
slug = slug.replaceAll(leadingDashRegex, '');
const slug = slugify(name);
idToNation[tankId] = nation;
+4 -1
View File
@@ -1,4 +1,7 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*"]
"include": ["src/**/*"],
"compilerOptions": {
"baseUrl": "../../"
}
}
@@ -1,16 +1,29 @@
import { TankClass } from '@blitzkit/core';
import { TankClass as TankClass_dava } from '@blitzkit/core';
import { TankClass } from '@protos/blitz_static_tank_component';
import { QuestionMarkIcon } from '@radix-ui/react-icons';
import type { ComponentProps, ReactNode } from 'react';
import { ClassHeavy } from './components/ClassHeavy';
import { ClassLight } from './components/ClassLight';
import { ClassMedium } from './components/ClassMedium';
import { ClassTankDestroyer } from './components/ClassTankDestroyer';
export const classIcons: Record<
export const classIcons_ue: Record<
TankClass,
((props: ComponentProps<'svg'>) => ReactNode) | typeof QuestionMarkIcon
> = {
[TankClass.TANK_CLASS_UNSPECIFIED]: QuestionMarkIcon,
[TankClass.TANK_CLASS_TANK_DESTROYER]: ClassTankDestroyer,
[TankClass.TANK_CLASS_HEAVY]: ClassHeavy,
[TankClass.TANK_CLASS_LIGHT]: ClassLight,
[TankClass.TANK_CLASS_MEDIUM]: ClassMedium,
};
export const classIcons: Record<
TankClass_dava,
(props: ComponentProps<'svg'>) => ReactNode
> = {
[TankClass.TANK_DESTROYER]: ClassTankDestroyer,
[TankClass.HEAVY]: ClassHeavy,
[TankClass.LIGHT]: ClassLight,
[TankClass.MEDIUM]: ClassMedium,
[TankClass_dava.TANK_CLASS_TANK_DESTROYER]: ClassTankDestroyer,
[TankClass_dava.TANK_CLASS_HEAVY]: ClassHeavy,
[TankClass_dava.TANK_CLASS_LIGHT]: ClassLight,
[TankClass_dava.TANK_CLASS_MEDIUM]: ClassMedium,
};
@@ -0,0 +1,181 @@
import {
Nation,
TankClass,
TankType,
Tier,
} from '@protos/blitz_static_tank_component';
import { Box, Code, Flex } from '@radix-ui/themes';
import { asset } from 'packages/core/src';
import { Var } from '../../core/radix/var';
import { useLocale } from '../../hooks/useLocale';
import { App } from '../../stores/app';
import { TankopediaEphemeral_ue } from '../../stores/tankopediaEphemeral_ue';
import { classIcons_ue } from '../ClassIcon';
import { Listing } from '../Tankopedia/MetaSection/components/Listing';
const NATIONAL_BANNER_POSITION_OVERRIDES: Record<string, string> = {
germany: '50% 35%',
france: '50% 35%',
other: '50% 35%',
};
export function MetaSection() {
const developerMode = App.useDeferred((state) => state.developerMode, false);
const tank = TankopediaEphemeral_ue.use((state) => state.tank);
const ClassIcon = classIcons_ue[tank.tank_class];
const { locale, strings } = useLocale();
return (
<Flex justify="center" mt="-9">
<Box
style={{
background: `url(/assets/images/national-wallpapers/${tank.nation}.jpg)`,
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
backgroundPosition:
NATIONAL_BANNER_POSITION_OVERRIDES[tank.nation] ?? 'center',
}}
flexGrow="1"
maxWidth="120rem"
>
<Box
style={{
background: `url(${asset(`flags/scratched/${tank.nation}.webp`)})`,
backgroundRepeat: 'no-repeat',
backgroundSize: 'min(53rem, 80vw)',
backgroundPosition: '-8rem 50%',
}}
>
<Flex
justify="center"
align="center"
py="6rem"
style={{
background: `linear-gradient(${Var('black-a8')}, ${Var('black-a8')}, ${Var('gray-1')})`,
}}
>
<Flex direction="column" align="center" gap="6">
<Flex
gap={{ initial: '2', sm: '8' }}
direction={{ initial: 'column', sm: 'row' }}
>
<Flex direction="column" width="100%" gap="2">
<Listing label={strings.website.tools.tankopedia.meta.nation}>
<Flex align="center" gap="1">
<img
style={{ width: '1em', height: '1em' }}
alt={Nation[tank.nation]}
src={asset(`flags/circle/${tank.nation}.webp`)}
/>
{Nation[tank.nation]}
</Flex>
</Listing>
<Listing label={strings.website.tools.tankopedia.meta.class}>
<Flex align="center" gap="1">
<ClassIcon width="1em" height="1em" />
{TankClass[tank.tank_class]}
</Flex>
</Listing>
<Listing label={strings.website.tools.tankopedia.meta.tier}>
{Tier[tank.tier]}
</Listing>
<Listing
label={strings.website.tools.tankopedia.meta.type}
color={
tank.tank_type === TankType.TANK_TYPE_COLLECTIBLE
? 'blue'
: tank.tank_type === TankType.TANK_TYPE_PREMIUM
? 'amber'
: undefined
}
>
{TankType[tank.tank_type]}
</Listing>
</Flex>
<Flex direction="column" width="100%" gap="2">
{developerMode && (
<Listing
label={strings.website.tools.tankopedia.meta.dev_id}
>
<Code>TANK_ID</Code>
</Listing>
)}
{tank.tank_type === TankType.TANK_TYPE_PREMIUM && (
<Listing
label={strings.website.tools.tankopedia.meta.purchase}
>
<Flex align="center" gap="1">
{tank.price.value / 400}
<img
style={{ width: '1em', height: '1em' }}
alt="gold"
src={asset('icons/currencies/gold.webp')}
/>
</Flex>
</Listing>
)}
<Listing
label={
protagonist.type === TankType.RESEARCHABLE
? strings.website.tools.tankopedia.meta.purchase
: strings.website.tools.tankopedia.meta.restoration
}
>
<Flex align="center" gap="1">
{protagonist.price.value.toLocaleString(locale)}
<img
style={{ width: '1em', height: '1em' }}
alt={TankPriceType[protagonist.price.type]}
src={asset(
`icons/currencies/${
protagonist.price.type === TankPriceType.GOLD
? 'gold'
: 'silver'
}.webp`,
)}
/>
</Flex>
</Listing>
<Listing label={strings.website.tools.tankopedia.meta.sale}>
<Flex align="center" gap="1">
{(protagonist.price.value / 2).toLocaleString(locale)}
<img
style={{ width: '1em', height: '1em' }}
alt={TankPriceType[protagonist.price.type]}
src={asset(
`icons/currencies/${
protagonist.price.type === TankPriceType.GOLD
? 'gold'
: 'silver'
}.webp`,
)}
/>
</Flex>
</Listing>
{protagonist.research_cost && (
<Listing
label={strings.website.tools.tankopedia.meta.research}
>
<Flex align="center" gap="1">
{(
protagonist.research_cost.research_cost_type!
.value as number
).toLocaleString(locale)}
<img
style={{ width: '1em', height: '1em' }}
alt="xp"
src={asset('icons/currencies/xp.webp')}
/>
</Flex>
</Listing>
)}
</Flex>
</Flex>
</Flex>
</Flex>
</Box>
</Box>
</Flex>
);
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { DEFAULT_LOCALE, STRINGS, type SupportedLocale } from '@blitzkit/i18n';
import { STRINGS, type SupportedLocale } from '@blitzkit/i18n';
export function getStrings(locale: string = DEFAULT_LOCALE) {
export function getStrings(locale: string) {
if (locale in STRINGS) return STRINGS[locale as SupportedLocale];
throw new Error(`Unsupported locale: ${locale}`);
}
@@ -1,7 +1,8 @@
---
import { AstroSeo } from '@astrolib/seo';
import { assertSecret } from '@blitzkit/core';
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from '@blitzkit/i18n';
import { DEFAULT_LOCALE } from '@blitzkit/i18n';
import locales from '@blitzkit/i18n/locales.json';
import { literals } from '@blitzkit/i18n/src/literals';
import { Box, Flex, Heading, Link } from '@radix-ui/themes';
import type { GetStaticPaths } from 'astro';
@@ -25,15 +26,20 @@ import BaseLayout from '../../layouts/BaseLayout.astro';
const PUBLIC_ASSET_BRANCH = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
const locale = Astro.params.locale ?? DEFAULT_LOCALE;
const strings = getStrings(Astro.params.locale);
const strings = getStrings(Astro.props.locale);
export const getStaticPaths = (() => {
return [
...SUPPORTED_LOCALES.filter((locale) => locale !== DEFAULT_LOCALE).map(
(locale) => ({ params: { locale } }),
),
{ params: { locale: undefined } },
];
// return [
// ...SUPPORTED_LOCALES.filter((locale) => locale !== DEFAULT_LOCALE).map(
// (locale) => ({ params: { locale } }),
// ),
// { params: { locale: undefined } },
// ];
return locales.supported.map(({ locale, blitz }) => ({
params: { locale: locale === locales.default ? undefined : locale },
props: { locale, blitzLocale: blitz ?? locale },
}));
}) satisfies GetStaticPaths;
---
@@ -79,7 +85,9 @@ export const getStaticPaths = (() => {
>
{literals(strings.website.home.help_translate, [
'BlitzKit',
strings.common.locales[locale],
strings.common.locales[
locale as keyof typeof strings.common.locales
],
])}
</Link>
</Flex>
@@ -0,0 +1,29 @@
import type { CompensationComponent } from '@protos/blitz_static_compensation_component';
import { type TankCatalogComponent } from '@protos/blitz_static_tank_component';
import { PageWrapper } from 'packages/website/src/components/PageWrapper';
import { MetaSection } from 'packages/website/src/components/Tankopedia_ue/MetaSection';
import {
LocaleProvider,
type LocaleAcceptorProps,
} from 'packages/website/src/hooks/useLocale';
import { App } from 'packages/website/src/stores/app';
import { TankopediaEphemeral_ue } from 'packages/website/src/stores/tankopediaEphemeral_ue';
interface PageProps extends LocaleAcceptorProps {
tank: TankCatalogComponent;
compensation: CompensationComponent;
}
export function Page({ tank, compensation, locale }: PageProps) {
return (
<LocaleProvider locale={locale}>
<App.Provider>
<TankopediaEphemeral_ue.Provider data={{ tank, compensation }}>
<PageWrapper p="0" maxWidth="unset" color="purple" gap="9" pb="9">
<MetaSection />
</PageWrapper>
</TankopediaEphemeral_ue.Provider>
</App.Provider>
</LocaleProvider>
);
}
@@ -0,0 +1,43 @@
---
import type {
CatalogItemsComponent,
TankCatalogComponent,
} from '@blitzkit/closed/src/unreal/protos';
import type { GetStaticPaths } from 'astro';
import { metadata } from '../../../../core/blitz/metadata';
import BaseLayout from '../../../../layouts/BaseLayout.astro';
import { getStaticPaths as parentGetStaticPaths } from '../index.astro';
import { Page } from './_index';
export const getStaticPaths = (async () => {
const tanks = await metadata
.get('CatalogItemsGroupEntity.all_tanks')
.then((components) =>
components.get<CatalogItemsComponent>('catalogItemsGroupComponent'),
);
const parentPaths = parentGetStaticPaths();
return tanks.catalog_items
.map((item) => {
const id = item.replace('TankEntity.', '');
return parentPaths.map((parent) => ({
params: { ...parent.params, id },
props: { item },
}));
})
.flat();
}) satisfies GetStaticPaths;
const { id, locale } = Astro.params;
const { item } = Astro.props;
const tank = await metadata
.get(item)
.then((components) =>
components.get<TankCatalogComponent>('tankCatalogComponent'),
);
---
<BaseLayout>
<Page client:load {locale} {tank} />
</BaseLayout>
@@ -0,0 +1,46 @@
---
import { slugify } from '@blitzkit/core';
import locales from '@blitzkit/i18n/locales.json';
import type { CatalogItemsComponent } from '@protos/blitz_static_catalog_items_group_component';
import { TankCatalogComponent } from '@protos/blitz_static_tank_component';
import { metadata } from '../../../core/blitz/metadata';
export { getStaticPaths } from '../index.astro';
const { blitzLocale } = Astro.props;
const allTanks = await metadata
.get('CatalogItemsGroupEntity.all_tanks')
.then((components) =>
components.get<CatalogItemsComponent>('catalogItemsGroupComponent'),
);
const gameStrings = await metadata
.strings(blitzLocale)
.then((strings) => strings.subset(['TankEntity']));
const gameStringsDefault = await metadata
.strings(locales.default)
.then((strings) => strings.subset(['TankEntity']));
const tanks: { tank: TankCatalogComponent; slug: string; id: string }[] = [];
for (const tankComponentId of allTanks.catalog_items) {
const tank = await metadata.get(tankComponentId).then((components) => {
return components.get<TankCatalogComponent>('tankCatalogComponent');
});
const nameEn = gameStringsDefault[tank.name!.value];
const slug = slugify(nameEn);
tanks.push({ tank, slug, id: tankComponentId });
}
---
{
tanks.map(({ tank, slug, id }) => {
return (
<>
<a href={`/tanks-ue/${slug}`}>
{gameStrings[tank.name!.value] ?? `MISSING NAME (${id})`}
</a>
<br />
</>
);
})
}
@@ -0,0 +1,18 @@
import type { CompensationComponent } from '@protos/blitz_static_compensation_component';
import type { TankCatalogComponent } from '@protos/blitz_static_tank_component';
import { create } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
import { createContextualStore } from '../core/zustand/createContextualStore';
interface TankopediaEphemeral_ue {
tank: TankCatalogComponent;
compensation: CompensationComponent;
}
export const TankopediaEphemeral_ue = createContextualStore(
(data: TankopediaEphemeral_ue) => {
return create<TankopediaEphemeral_ue>()(
subscribeWithSelector<TankopediaEphemeral_ue>(() => data),
);
},
);
+2 -1
View File
@@ -3,6 +3,7 @@
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react",
"types": ["vite/client"]
"types": ["vite/client"],
"baseUrl": "../../"
}
}
+7 -1
View File
@@ -14,11 +14,17 @@
"allowJs": true,
"incremental": true,
"isolatedModules": true,
"plugins": [
{
"name": "next"
}
]
],
"baseUrl": "./",
"paths": {
"@protos/*": ["submodules/blitzkit-closed/src/unreal/protos/*"]
}
},
"exclude": ["node_modules/**/*", "dist", ".next"]
}