diff --git a/bun.lockb b/bun.lockb index e945d6256..4ce3d1158 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 53ba6a734..e0e67b83e 100644 --- a/package.json +++ b/package.json @@ -45,14 +45,20 @@ "js-yaml": "^4.1.0", "lodash-es": "^4.17.21", "protobufjs": "^7.5.2", + "react": "^19.1.0", + "react-dom": "^19.1.0", "quicklime": "^1.2.0", "sharp": "^0.33.5" }, "devDependencies": { + "@astrojs/react": "^4.3.0", "@types/bun": "latest", "@types/js-yaml": "^4.0.9", "@types/lodash-es": "^4.17.12", + "@types/react": "^19.1.5", + "@types/react-dom": "^19.1.5", "@types/node": "^22.14.1", + "astro": "^5.8.0", "prettier": "^3.5.3", "prisma": "^6.6.0", "typescript": "^5.8.3" diff --git a/packages/bot/package.json b/packages/bot/package.json index aae56319d..99bcfd6a8 100644 --- a/packages/bot/package.json +++ b/packages/bot/package.json @@ -9,7 +9,6 @@ "satori": "^0.10.14", "@resvg/resvg-js": "^2.6.2", "discord.js": "^14.16.3", - "react": "^18.3.1", "@radix-ui/colors": "^3.0.0", "fuzzysort": "^2.0.4", "bepaint": "^0.1.0-beta.3", diff --git a/packages/core/src/blitz/catalogItemAccessor.ts b/packages/core/src/blitz/catalogItemAccessor.ts new file mode 100644 index 000000000..dde396517 --- /dev/null +++ b/packages/core/src/blitz/catalogItemAccessor.ts @@ -0,0 +1,28 @@ +import { CatalogItem } from '@protos/blitz_items'; +import { Any } from '@protos/google/protobuf/any'; +import { MessageFns } from '../protos'; + +export class CatalogItemAccessor { + private components: Record = {}; + + constructor(item: CatalogItem) { + for (const component of item.components) { + if ( + component.value === undefined || + component.value.component === undefined + ) { + continue; + } + + this.components[component.key] = component.value.component; + } + } + + get(Message: MessageFns, component: string) { + if (component in this.components) { + return Message.decode(this.components[component].value); + } + + throw new Error(`Unknown component: ${component}`); + } +} diff --git a/packages/core/src/blitz/index.ts b/packages/core/src/blitz/index.ts index e6695539c..d80a0f4ed 100644 --- a/packages/core/src/blitz/index.ts +++ b/packages/core/src/blitz/index.ts @@ -1,4 +1,5 @@ export * from './canSplash'; +export * from './catalogItemAccessor'; export * from './clans'; export * from './classes'; export * from './constants'; diff --git a/packages/core/src/blitz/metadataAccessor.ts b/packages/core/src/blitz/metadataAccessor.ts index 8a07c2c8d..3d1ae7889 100644 --- a/packages/core/src/blitz/metadataAccessor.ts +++ b/packages/core/src/blitz/metadataAccessor.ts @@ -1,10 +1,38 @@ +import { CatalogItem } from '@protos/blitz_items'; import { Metadata } from '@protos/blitz_metadata'; +import { CatalogItemAccessor } from './catalogItemAccessor'; -export class MetadataAccessor { - constructor() {} +export class UnpackedMetadataAccessor { + private items: Record = {}; - loadMetadata(metadata: Metadata) { - for (const catalogItem of metadata.catalog_items) { + constructor(metadata: Metadata) { + for (const item of metadata.catalog_items) { + if (item.catalog_id in this.items) { + throw new Error(`Duplicate catalog ID: ${item.catalog_id}`); + } + + this.items[item.catalog_id] = new CatalogItemAccessor(item); } } + + get(item: string) { + if (item in this.items) return this.items[item]; + + throw new Error(`Unknown catalog ID: ${item}`); + } +} + +export class DiscreteMetadataAccessor { + constructor(private base: string) {} + + async get(item: string) { + const response = await fetch(`${this.base}/${item}.pb`); + + if (!response.ok) throw new Error(`Unknown catalog ID: ${item}`); + + const buffer = await response.arrayBuffer(); + const catalogItem = CatalogItem.decode(new Uint8Array(buffer)); + + return new CatalogItemAccessor(catalogItem); + } } diff --git a/packages/core/src/blitzkit/tankNames.ts b/packages/core/src/blitzkit/tankNames.ts index 26dea0712..f33566892 100644 --- a/packages/core/src/blitzkit/tankNames.ts +++ b/packages/core/src/blitzkit/tankNames.ts @@ -1,5 +1,5 @@ +import locales from '@blitzkit/i18n/locales.json' with { type: 'json' }; import { deburr } from 'lodash-es'; -import { SUPPORTED_LOCALES } from '../../../i18n/src/strings'; import { I18nString } from '../protos'; import { fetchCamouflageDefinitions } from './camouflageDefinitions'; import { fetchTankDefinitions } from './tankDefinitions'; @@ -26,8 +26,8 @@ export async function fetchTankNames() { searchableNameDeburr, camouflages: tank.camouflages ?.map((id) => - SUPPORTED_LOCALES.map( - (locale) => + locales.supported.map( + ({ locale }) => camouflageDefinitions.camouflages[id]?.name.locales[locale], ), ) @@ -42,9 +42,11 @@ export async function fetchTankNames() { } export const SEARCH_KEYS = [ - ...SUPPORTED_LOCALES.map((locale) => [ - `searchableName.locales.${locale}`, - `searchableNameDeburr.locales.${locale}`, - ]).flat(), + ...locales.supported + .map(({ locale }) => [ + `searchableName.locales.${locale}`, + `searchableNameDeburr.locales.${locale}`, + ]) + .flat(), 'camouflages', ]; diff --git a/packages/i18n/src/strings.ts b/packages/i18n/src/strings.ts index 25c022efd..326bd28b9 100644 --- a/packages/i18n/src/strings.ts +++ b/packages/i18n/src/strings.ts @@ -1,89 +1,8 @@ -import { merge } from 'ts-deepmerge'; -import en from '../strings/en.json'; -import es from '../strings/es.json'; -import fr from '../strings/fr.json'; -import ja from '../strings/ja.json'; -import pl from '../strings/pl.json'; -import pt from '../strings/pt.json'; -import ru from '../strings/ru.json'; -import uk from '../strings/uk.json'; -import zh from '../strings/zh.json'; +import type en from '../strings/en.json'; export type BlitzKitStrings = typeof en; -const stringsRaw = { en, es, fr, ja, pl, pt, ru, uk, zh }; - -export const SUPPORTED_LOCALES = Object.keys(stringsRaw) as SupportedLocale[]; - -export const DEFAULT_LOCALE = SUPPORTED_LOCALES[0]; - -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(''); -} - -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); - } -} - -const stringsPartial: Partial> = {}; - -for (const locale in stringsRaw) { - if (locale === DEFAULT_LOCALE) { - stringsPartial[DEFAULT_LOCALE] = stringsRaw[DEFAULT_LOCALE]; - continue; - } - - const localeStrings = stringsRaw[locale as SupportedLocale]; - stringsPartial[locale as SupportedLocale] = merge( - stringsRaw[DEFAULT_LOCALE], - localeStrings, - ); - - if (import.meta.env.PUBLIC_DEBUG_MISSING_I18N === 'true') { - nuke(stringsPartial[locale as SupportedLocale]!); - } -} - -export const STRINGS = stringsPartial as Record< - SupportedLocale, - BlitzKitStrings ->; - -export type SupportedLocale = keyof typeof stringsRaw; - -export const SUPPORTED_LOCALE_BLITZ_MAP: Record = { - en: 'en', - es: 'es', - fr: 'fr', - ja: 'ja', - pt: 'pt', - ru: 'ru', - uk: 'uk', - zh: 'zh-Hans', - pl: 'pl', -}; - -export const SUPPORTED_LOCALE_FLAGS: Record = { +export const SUPPORTED_LOCALE_FLAGS: Record = { en: 'πŸ‡ΊπŸ‡Έ', es: 'πŸ‡ͺπŸ‡Έ', fr: 'πŸ‡«πŸ‡·', @@ -94,7 +13,3 @@ export const SUPPORTED_LOCALE_FLAGS: Record = { zh: 'πŸ‡¨πŸ‡³', pl: 'πŸ‡΅πŸ‡±', }; - -export type TranslationTree = { [key: string]: TranslationNode }; - -export type TranslationNode = string | TranslationTree; diff --git a/packages/i18n/src/unwrapper.ts b/packages/i18n/src/unwrapper.ts index 7c18ae5ba..9a70fa7a9 100644 --- a/packages/i18n/src/unwrapper.ts +++ b/packages/i18n/src/unwrapper.ts @@ -1,5 +1,5 @@ import { I18nString } from '@blitzkit/core'; -import { DEFAULT_LOCALE } from './strings'; +import locales from '../locales.json' with { type: 'json' }; export function unwrapper(locale: string) { return function (i18nString: I18nString) { @@ -7,6 +7,6 @@ export function unwrapper(locale: string) { return i18nString.locales[locale]; } - return i18nString.locales[DEFAULT_LOCALE]; + return i18nString.locales[locales.default]; }; } diff --git a/packages/scripts/src/buildAssets/glossary.ts b/packages/scripts/src/buildAssets/glossary.ts index 4b50d7ff6..d49774527 100644 --- a/packages/scripts/src/buildAssets/glossary.ts +++ b/packages/scripts/src/buildAssets/glossary.ts @@ -1,5 +1,5 @@ import { Avatar, encodePBBuffer, fetchGlossary, Gallery } from '@blitzkit/core'; -import locales from '@blitzkit/i18n/locales.json'; +import locales from '@blitzkit/i18n/locales.json' with { type: 'json' }; import { extname } from 'path'; import ProgressBar from 'progress'; import { commitAssets } from '../core/github/commitAssets'; diff --git a/packages/website/astro.config.mjs b/packages/website/astro.config.mjs index 45bd1c5dc..003437b48 100644 --- a/packages/website/astro.config.mjs +++ b/packages/website/astro.config.mjs @@ -3,13 +3,13 @@ import partytown from '@astrojs/partytown'; import react from '@astrojs/react'; import sitemap from '@astrojs/sitemap'; -import { SUPPORTED_LOCALES } from '@blitzkit/i18n'; +import locales from '@blitzkit/i18n/locales.json' with { type: 'json' }; import { defineConfig } from 'astro/config'; export default defineConfig({ i18n: { - defaultLocale: 'en', - locales: [...SUPPORTED_LOCALES], + defaultLocale: locales.default, + locales: locales.supported.map(({ locale }) => locale), routing: { prefixDefaultLocale: false, }, diff --git a/packages/website/package.json b/packages/website/package.json index f6cae1d5d..a962c474e 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -12,19 +12,19 @@ "dependencies": { "@astrojs/partytown": "^2.1.4", "@astrolib/seo": "^1.0.0-beta.8", + "@blitzkit/closed": "workspace:*", "@blitzkit/core": "workspace:*", "@blitzkit/i18n": "workspace:*", - "@blitzkit/closed": "workspace:*", "@fontsource/inter": "^5.2.5", "@nanostores/persistent": "^0.10.2", "@nanostores/react": "^0.7.3", "@nivo/bar": "^0.87.0", "@nivo/line": "^0.88.0", "@radix-ui/colors": "^3.0.0", - "@radix-ui/react-icons": "^1.3.0", - "@radix-ui/themes": "^3.1.4", - "@react-three/drei": "^9.115.0", - "@react-three/fiber": "^8.17.10", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/themes": "^3.2.1", + "@react-three/drei": "^10.0.8", + "@react-three/fiber": "^9.1.2", "compare-versions": "^6.1.1", "echarts": "^5.5.1", "immer": "^10.1.1", @@ -32,9 +32,7 @@ "markdown-to-jsx": "^7.5.0", "nanostores": "^0.11.3", "quicklime": "^1.2.0", - "react": "^18.3.1", "react-device-detect": "^2.2.3", - "react-dom": "^18.3.1", "react-promise-suspense": "^0.3.4", "three": "^0.169.0", "urlon": "^3.1.0", @@ -47,10 +45,7 @@ "@playform/compress": "^0.1.9", "@playform/inline": "^0.1.2", "@types/js-cookie": "^3.0.6", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", "@types/three": "^0.160.0", - "astro": "^4.16.7", - "astro-compressor": "^1.0.0" + "astro": "^4.16.7" } } diff --git a/packages/website/src/components/Gallery/List.tsx b/packages/website/src/components/Gallery/List.tsx index f6638f3a6..a87186cdf 100644 --- a/packages/website/src/components/Gallery/List.tsx +++ b/packages/website/src/components/Gallery/List.tsx @@ -1,4 +1,4 @@ -import locales from '@blitzkit/i18n/locales.json'; +import locales from '@blitzkit/i18n/locales.json' with { type: 'json' }; import { literals } from '@blitzkit/i18n/src/literals'; import { Flex, Text } from '@radix-ui/themes'; import fuzzysort from 'fuzzysort'; diff --git a/packages/website/src/components/HomeHero.astro b/packages/website/src/components/HomeHero.astro index d4b799e14..b5f02f6b0 100644 --- a/packages/website/src/components/HomeHero.astro +++ b/packages/website/src/components/HomeHero.astro @@ -7,10 +7,10 @@ import type { BlitzKitStrings } from 'packages/i18n/src'; interface Props { locale: string; - strings: BlitzKitStrings; } -const { strings, locale } = Astro.props; +const { locale } = Astro.props; +const strings = await getStrings(locale); const branchName = resolveBranchName(locale); const isBranchNamed = branchName !== undefined; --- diff --git a/packages/website/src/components/TanksEmbed.tsx b/packages/website/src/components/TanksEmbed.tsx index 6172801d7..fe515aa66 100644 --- a/packages/website/src/components/TanksEmbed.tsx +++ b/packages/website/src/components/TanksEmbed.tsx @@ -6,7 +6,6 @@ import { type CompositeStats, type CompositeStatsKey, } from '@blitzkit/core'; -import strings from '@blitzkit/i18n/strings/en.json'; import { amberDark, blueDark } from '@radix-ui/colors'; import { Flex, Text, type FlexProps } from '@radix-ui/themes'; import { times } from 'lodash-es'; @@ -53,7 +52,7 @@ export function BreakdownEmbedCard({ }: BreakdownEmbedCardProps) { const { useEmbedState, useRichText } = useEmbedStateCurry(); - const { unwrap } = useLocale(); + const { unwrap, strings } = useLocale(); return ( { + console.log(data); + }, []); + return 'TestComponent'; } diff --git a/packages/website/src/core/blitz/discreteMetadata.ts b/packages/website/src/core/blitz/discreteMetadata.ts new file mode 100644 index 000000000..ad2b97025 --- /dev/null +++ b/packages/website/src/core/blitz/discreteMetadata.ts @@ -0,0 +1,3 @@ +import { DiscreteMetadataAccessor } from 'packages/core/src'; + +export const discreteMetadata = new DiscreteMetadataAccessor('/api/catalog'); diff --git a/packages/website/src/core/blitz/metadata.ts b/packages/website/src/core/blitz/unpackedMetadata.ts similarity index 55% rename from packages/website/src/core/blitz/metadata.ts rename to packages/website/src/core/blitz/unpackedMetadata.ts index b7fd2b008..6fd18d1ba 100644 --- a/packages/website/src/core/blitz/metadata.ts +++ b/packages/website/src/core/blitz/unpackedMetadata.ts @@ -1,11 +1,8 @@ -import { - MetadataAccessor, - requestAutoProxyClient, - unpackMetadata, -} from '@blitzkit/closed'; +import { requestAutoProxyClient, unpackMetadata } from '@blitzkit/closed'; import type { GetPackedMetadataAsyncResponse } from '@blitzkit/closed/src/unreal/protos/auto_legacy_proxy_client'; +import { UnpackedMetadataAccessor } from 'packages/core/src'; -export const metadata = new MetadataAccessor( +export const unpackedMetadata = new UnpackedMetadataAccessor( await requestAutoProxyClient( 'GetPackedMetadataAsyncRequest', ).then(unpackMetadata), diff --git a/packages/website/src/core/i18n/getStrings.ts b/packages/website/src/core/i18n/getStrings.ts index 64b7395ab..af0dfde56 100644 --- a/packages/website/src/core/i18n/getStrings.ts +++ b/packages/website/src/core/i18n/getStrings.ts @@ -1,6 +1,8 @@ -import { STRINGS, type SupportedLocale } from '@blitzkit/i18n'; +import en from '@strings/en.json' with { type: 'json' }; +import { merge } from 'lodash-es'; +import type { BlitzKitStrings } from 'packages/i18n/src'; -export function getStrings(locale: string) { - if (locale in STRINGS) return STRINGS[locale as SupportedLocale]; - throw new Error(`Unsupported locale: ${locale}`); +export async function getStrings(locale: string) { + const strings = await import(`../../../../i18n/strings/${locale}.json`); + return merge({}, en, strings) as BlitzKitStrings; } diff --git a/packages/website/src/hooks/useLocale.tsx b/packages/website/src/hooks/useLocale.tsx index a7189ad3b..864eae90b 100644 --- a/packages/website/src/hooks/useLocale.tsx +++ b/packages/website/src/hooks/useLocale.tsx @@ -1,41 +1,30 @@ -import { type BlitzKitStrings } from '@blitzkit/i18n'; -import { createContext, useContext, type ReactNode } from 'react'; - -interface LocaleContextContent { - locale: string; - strings: BlitzKitStrings; - gameStrings: Record; -} - -type LocaleContextData = Omit & { - gameStrings?: Record; -}; - -const LocaleContext = createContext(null); +import locales from '@blitzkit/i18n/locales.json' with { type: 'json' }; +import type { BlitzKitStrings } from 'packages/i18n/src'; +import { createContext, use, useContext } from 'react'; +const LocaleContext = createContext(null); interface LocaleProviderProps { - data: LocaleContextData; - children: ReactNode; + locale: string; + children: React.ReactNode; } -export function LocaleProvider({ data, children }: LocaleProviderProps) { +export function LocaleProvider({ locale, children }: LocaleProviderProps) { + const isSupported = locales.supported.some( + (supported) => supported.locale === locale, + ); + + if (!isSupported) throw new Error(`Unsupported locale: ${locale}`); + return ( - - {children} - + {children} ); } export function useLocale() { - const context = useContext(LocaleContext); + const locale = useContext(LocaleContext); + const strings = use( + fetch(`/api/strings/${locale}.json`).then((response) => response.json()), + ); - if (!context) { - throw new Error('useLocale must be used within a LocaleProvider'); - } - - return context; -} - -export interface LocaleAcceptorProps { - localeContext: LocaleContextContent; + return { locale, strings }; } diff --git a/packages/website/src/layouts/BaseLayout.astro b/packages/website/src/layouts/BaseLayout.astro index 7e753bc47..9b8dabf4f 100644 --- a/packages/website/src/layouts/BaseLayout.astro +++ b/packages/website/src/layouts/BaseLayout.astro @@ -7,16 +7,17 @@ import Navbar from '../components/Navbar.astro'; import Party3 from '../components/Party3.astro'; import './base.css'; import type { BlitzKitStrings } from 'packages/i18n/src'; +import { getStrings } from '../core/i18n/getStrings'; export interface Props { noFooter?: boolean; noNavbar?: boolean; integrated?: boolean; locale: string; - strings: BlitzKitStrings; } -const { noFooter, noNavbar, integrated, locale, strings } = Astro.props; +const { noFooter, noNavbar, integrated, locale } = Astro.props; +const strings = await getStrings(locale); --- @@ -30,25 +31,26 @@ const { noFooter, noNavbar, integrated, locale, strings } = Astro.props;