From 6a958da390c0d19ffccb4bd002f8b7c9707b0ac9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Fri, 28 Nov 2025 14:13:03 -0600 Subject: [PATCH] feat: implement strings --- .vscode/settings.json | 1 + bun.lock | 1 + packages/closed | 2 +- packages/core/src/blitzkit/index.ts | 83 ++++++++++---------- packages/core/src/blitzkit/sluggify.ts | 16 ++++ packages/website-ue/package.json | 5 +- packages/website-ue/src/core/api/abstract.ts | 5 ++ packages/website-ue/src/core/api/server.ts | 70 ++++++++++++++++- 8 files changed, 136 insertions(+), 47 deletions(-) create mode 100644 packages/core/src/blitzkit/sluggify.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 7304e64d5..5736d7d1c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -187,6 +187,7 @@ "searchtanks", "SHAREDEXP", "SKLLD", + "sluggify", "smoothstep", "Smri", "Specularity", diff --git a/bun.lock b/bun.lock index f2cdc0ab2..29085f808 100644 --- a/bun.lock +++ b/bun.lock @@ -209,6 +209,7 @@ "typescript": "^5.9.3", "varuna": "workspace:*", "vite": "^7.1.9", + "yaml": "^2.8.1", }, }, }, diff --git a/packages/closed b/packages/closed index 2c0b66070..dc17bc32e 160000 --- a/packages/closed +++ b/packages/closed @@ -1 +1 @@ -Subproject commit 2c0b660702839f12350810a05be42625da5e45ab +Subproject commit dc17bc32e49d2b2c8d40b446580fb7cc4a47730c diff --git a/packages/core/src/blitzkit/index.ts b/packages/core/src/blitzkit/index.ts index c5c879837..2c289c4ba 100644 --- a/packages/core/src/blitzkit/index.ts +++ b/packages/core/src/blitzkit/index.ts @@ -1,49 +1,50 @@ -export * from './assertSecret'; -export * from './asset'; -export * from './availableProvisions'; -export * from './averageDefinitions'; +export * from "./assertSecret"; +export * from "./asset"; +export * from "./availableProvisions"; +export * from "./averageDefinitions"; // export * from './camouflageDefinitions'; // export * from './checkConsumableProvisionInclusivity'; -export * from './coefficient'; -export * from './consumableDefinitions'; +export * from "./coefficient"; +export * from "./consumableDefinitions"; // export * from './context'; -export * from './createDefaultSkills'; -export * from './degressiveStat'; -export * from './discoveredIdDefinitions'; -export * from './emblemURL'; -export * from './equipmentDefinitions'; -export * from './eventManager'; -export * from './gameDefinitions'; -export * from './getArchivedLatestSeasonNumber'; +export * from "./createDefaultSkills"; +export * from "./degressiveStat"; +export * from "./discoveredIdDefinitions"; +export * from "./emblemURL"; +export * from "./equipmentDefinitions"; +export * from "./eventManager"; +export * from "./gameDefinitions"; +export * from "./getArchivedLatestSeasonNumber"; // export * from './getArchivedRatingInfo'; -export * from './getArchivedRatingLeaderboard'; +export * from "./getArchivedRatingLeaderboard"; // export * from './getArchivedRatingMidnightLeaderboard'; -export * from './getTimeDaysAgo'; -export * from './isExplosive'; -export * from './mapDefinitions'; -export * from './modelDefinitions'; -export * from './normalizeBoundingBox'; +export * from "./getTimeDaysAgo"; +export * from "./isExplosive"; +export * from "./mapDefinitions"; +export * from "./modelDefinitions"; +export * from "./normalizeBoundingBox"; // export * from './parseBkni'; // export * from './patientFetch'; -export * from './patientFetchJSON'; -export * from './progressiveStat'; -export * from './provisionDefinitions'; -export * from './resolveDpm'; -export * from './resolvePenetrationCoefficient'; -export * from './reviews'; -export * from './skillDefinitions'; -export * from './sum'; -export * from './tankDefinitions'; -export * from './tankIcon'; -export * from './tankNames'; -export * from './unionBoundingBox'; -export * from './uniqueGuns'; -export * from './unwrapBlitzkitResponse'; -export * from './votes'; +export * from "./patientFetchJSON"; +export * from "./progressiveStat"; +export * from "./provisionDefinitions"; +export * from "./resolveDpm"; +export * from "./resolvePenetrationCoefficient"; +export * from "./reviews"; +export * from "./skillDefinitions"; +export * from "./sum"; +export * from "./tankDefinitions"; +export * from "./tankIcon"; +export * from "./tankNames"; +export * from "./unionBoundingBox"; +export * from "./uniqueGuns"; +export * from "./unwrapBlitzkitResponse"; +export * from "./votes"; // export * from './withCORSProxy'; -export * from './createDefaultProvisions'; -export * from './gallery'; -export * from './metaSortTank'; -export * from './popularTanks'; -export * from './romanize'; -export * from './youtubers'; +export * from "./createDefaultProvisions"; +export * from "./gallery"; +export * from "./metaSortTank"; +export * from "./popularTanks"; +export * from "./romanize"; +export * from "./sluggify.ts"; +export * from "./youtubers"; diff --git a/packages/core/src/blitzkit/sluggify.ts b/packages/core/src/blitzkit/sluggify.ts new file mode 100644 index 000000000..034df0e81 --- /dev/null +++ b/packages/core/src/blitzkit/sluggify.ts @@ -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 sluggify(value: string) { + let slug = deburr(value).toLowerCase(); + slug = slug.replaceAll(nonAlphanumericRegex, "-"); + slug = slug.replaceAll(multipleDashesRegex, "-"); + slug = slug.replaceAll(trailingDashRegex, ""); + slug = slug.replaceAll(leadingDashRegex, ""); + + return slug; +} diff --git a/packages/website-ue/package.json b/packages/website-ue/package.json index d29a9ec3c..0c6be64ba 100644 --- a/packages/website-ue/package.json +++ b/packages/website-ue/package.json @@ -13,8 +13,8 @@ "@astrojs/node": "^9.4.4", "@astrojs/react": "^4.4.0", "@astrolib/seo": "^1.0.0-beta.8", - "@blitzkit/core": "workspace:*", "@blitzkit/closed": "workspace:*", + "@blitzkit/core": "workspace:*", "@blitzkit/i18n": "workspace:*", "@radix-ui/colors": "^3.0.0", "@radix-ui/react-icons": "^1.3.2", @@ -39,6 +39,7 @@ "three-stdlib": "^2.36.0", "typescript": "^5.9.3", "varuna": "workspace:*", - "vite": "^7.1.9" + "vite": "^7.1.9", + "yaml": "^2.8.1" } } diff --git a/packages/website-ue/src/core/api/abstract.ts b/packages/website-ue/src/core/api/abstract.ts index 4a3bf8b3e..a6159f219 100644 --- a/packages/website-ue/src/core/api/abstract.ts +++ b/packages/website-ue/src/core/api/abstract.ts @@ -2,4 +2,9 @@ import type { Tanks } from "../../protos/tanks"; export abstract class AbstractAPI { abstract tanks(): Promise; + + abstract strings( + locale: string, + prefix?: string + ): Promise>; } diff --git a/packages/website-ue/src/core/api/server.ts b/packages/website-ue/src/core/api/server.ts index b1776c3f3..5afb8e181 100644 --- a/packages/website-ue/src/core/api/server.ts +++ b/packages/website-ue/src/core/api/server.ts @@ -1,23 +1,87 @@ import type { MetadataAccessor } from "@blitzkit/closed"; +import { sluggify } from "@blitzkit/core"; +import type { RemoteStorageComponent } from "@protos/blitz_static_remote_storage_component"; +import { parse } from "yaml"; import type { TanksEntry } from "../../protos/tanks"; import { AbstractAPI } from "./abstract"; +interface LocalizationConfig { + namespaces: string[]; +} + export class ServerAPI extends AbstractAPI { constructor(private metadata: MetadataAccessor) { super(); + } - console.log(Object.keys(metadata.groups).join("\n")); + async strings(locale: string) { + const clientConfig = this.metadata.item("ClientConfigsEntity.prod"); + const localizationResources = clientConfig.LocalizationResources(); + + if (!localizationResources.remote_storage) { + throw new Error("Localization resources not found"); + } + + let remoteStorage: RemoteStorageComponent | undefined = undefined; + + for (const candidateId of localizationResources.remote_storage + .remote_urls) { + const item = this.metadata.item(candidateId); + const candidate = item.RemoteStorage(); + + if ( + remoteStorage === undefined || + candidate.download_speed_weight > remoteStorage.download_speed_weight + ) { + remoteStorage = candidate; + } + } + + if (remoteStorage === undefined) { + throw new Error("No suitable production remote storage found"); + } + + const config = await fetch( + `${remoteStorage.url}/${remoteStorage.relative_path}/config.yaml` + ) + .then((response) => response.text()) + .then((text) => parse(text) as LocalizationConfig); + const strings: Record = {}; + + for (const namespace of config.namespaces) { + Object.assign( + strings, + await fetch( + `${remoteStorage.url}/${remoteStorage.relative_path}/${namespace}/${locale}.yaml` + ) + .then((response) => response.text()) + .then((text) => { + const strings: Record = {}; + const parsed = parse(text) as Record; + + for (const key in parsed) { + strings[key] = parsed[key].replaceAll('\\"', '"'); + } + + return strings; + }) + ); + } + + return strings; } async tanks() { const group = this.metadata.group("TankEntity"); const tanks: TanksEntry[] = []; + const strings = await this.strings("en"); for (const item of group) { const tankCatalog = item.TankCatalog(); + const name = strings[tankCatalog.name!.value]; - const id = item.id; - const slug = tankCatalog.name?.value; + const id = item.name; + const slug = sluggify(name); tanks.push({ id, slug }); }