mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-22 03:07:04 +02:00
feat: strings
This commit is contained in:
1 parent
a2f1593a0e
commit
179ce62a55
17 files changed
+400
-35
No files matched your search
@@ -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": "../../"
|
||||
}
|
||||
}
|
||||
@@ -46,4 +46,5 @@ export * from './gallery';
|
||||
export * from './metaSortTank';
|
||||
export * from './popularTanks';
|
||||
export * from './romanize';
|
||||
export * from './slugify';
|
||||
export * from './youtubers';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -3,6 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react",
|
||||
"types": ["vite/client"]
|
||||
"types": ["vite/client"],
|
||||
"baseUrl": "../../"
|
||||
}
|
||||
}
|
||||
Submodule submodules/blitzkit-closed updated: 6ee56809b8...25f8838ba5.
+7
-1
@@ -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"]
|
||||
}
|
||||
Reference in new issue
Block a user